diff --git a/keyserver/src/creators/role-creator.js b/keyserver/src/creators/role-creator.js --- a/keyserver/src/creators/role-creator.js +++ b/keyserver/src/creators/role-creator.js @@ -1,14 +1,15 @@ // @flow -import { getRolePermissionBlobs } from 'lib/permissions/thread-permissions.js'; import { - universalCommunityPermissions, + getRolePermissionBlobs, + getUniversalCommunityRootPermissionsBlob, +} from 'lib/permissions/thread-permissions.js'; +import { userSurfacedPermissionsSet, configurableCommunityPermissions, threadPermissions, } from 'lib/types/thread-permission-types.js'; import type { ThreadType } from 'lib/types/thread-types-enum.js'; -import { threadTypes } from 'lib/types/thread-types-enum.js'; import type { RoleInfo, RoleModificationRequest, @@ -109,30 +110,23 @@ .map(permission => [...configurableCommunityPermissions[permission]]) .flat(); - const rolePermissions = [ - ...universalCommunityPermissions, - ...configuredPermissions, - ]; - - // For communities of the type `COMMUNITY_ANNOUNCEMENT_ROOT`, the ability for - // the role to be voiced needs to be configured (i.e. the parameters should - // include the user-facing permission VOICED_IN_ANNOUNCEMENT_CHANNELS). This - // means we do not give 'voiced' permissions by default to all new roles. As - // a result, if the thread type is `COMMUNITY_ROOT`, we want to ensure that - // the role has the voiced permission. const { threadInfos } = await fetchThreadInfos(viewer, { threadID: community, }); const threadInfo = threadInfos[community]; - if (threadInfo.type === threadTypes.COMMUNITY_ROOT) { - rolePermissions.push(threadPermissions.VOICED); - } + const universalCommunityPermissions = + getUniversalCommunityRootPermissionsBlob(threadInfo.type); - const permissionsBlob = JSON.stringify( - Object.fromEntries(rolePermissions.map(permission => [permission, true])), + const rolePermissions = Object.fromEntries( + configuredPermissions.map(permission => [permission, true]), ); + const permissionsBlob = JSON.stringify({ + ...universalCommunityPermissions, + ...rolePermissions, + }); + const row = [id, community, name, permissionsBlob, time]; let query = SQL``; diff --git a/keyserver/src/scripts/validate-role-permissions.js b/keyserver/src/scripts/validate-role-permissions.js --- a/keyserver/src/scripts/validate-role-permissions.js +++ b/keyserver/src/scripts/validate-role-permissions.js @@ -1,10 +1,12 @@ // @flow -import { getRolePermissionBlobs } from 'lib/permissions/thread-permissions.js'; +import { + getRolePermissionBlobs, + getUniversalCommunityRootPermissionsBlob, +} from 'lib/permissions/thread-permissions.js'; import { configurableCommunityPermissions, userSurfacedPermissions, - universalCommunityPermissions, } from 'lib/types/thread-permission-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { deepDiff, values } from 'lib/utils/objects.js'; @@ -35,6 +37,9 @@ const threadType = result.type; const threadDefaultRole = result.default_role.toString(); + const universalCommunityPermissions = + getUniversalCommunityRootPermissionsBlob(threadType); + // Get the 'expected permissions' set for the role. If the role is // default (Members) or Admins, these permission blobs can be retrieved // by calling getRolePermissionBlobs with the threadType. Otherwise, the @@ -48,9 +53,7 @@ } else if (roleName === 'Admins') { baseExpectedPermissionBlob = expectedPermissionBlobs.Admins; } else if (roleName) { - baseExpectedPermissionBlob = Object.fromEntries( - universalCommunityPermissions.map(permission => [permission, true]), - ); + baseExpectedPermissionBlob = universalCommunityPermissions; } else { baseExpectedPermissionBlob = {}; } diff --git a/lib/permissions/thread-permissions.js b/lib/permissions/thread-permissions.js --- a/lib/permissions/thread-permissions.js +++ b/lib/permissions/thread-permissions.js @@ -5,9 +5,11 @@ includeThreadPermissionForThreadType, } from './prefixes.js'; import { + configurableCommunityPermissions, threadPermissionFilterPrefixes, threadPermissionPropagationPrefixes, threadPermissions, + userSurfacedPermissions, } from '../types/thread-permission-types.js'; import type { ThreadPermission, @@ -15,8 +17,13 @@ ThreadPermissionsBlob, ThreadPermissionsInfo, ThreadRolePermissionsBlob, + UserSurfacedPermission, } from '../types/thread-permission-types.js'; -import { type ThreadType, threadTypes } from '../types/thread-types-enum.js'; +import { + type ThreadType, + type CommunityRootThreadType, + threadTypes, +} from '../types/thread-types-enum.js'; function permissionLookup( permissions: ?ThreadPermissionsBlob | ?ThreadPermissionsInfo, @@ -153,6 +160,27 @@ } } +function getThreadPermissionBlobFromUserSurfacedPermissions( + communityUserSurfacedPermissions: Array, + threadType: CommunityRootThreadType, +): ThreadRolePermissionsBlob { + const mappedUserSurfacedPermissions = communityUserSurfacedPermissions + .map(permission => [...configurableCommunityPermissions[permission]]) + .flat(); + + const userSurfacedPermissionsObj = Object.fromEntries( + mappedUserSurfacedPermissions.map(p => [p, true]), + ); + + const universalCommunityPermissions = + getUniversalCommunityRootPermissionsBlob(threadType); + + return { + ...universalCommunityPermissions, + ...userSurfacedPermissionsObj, + }; +} + export type RolePermissionBlobs = { +Members: ThreadRolePermissionsBlob, +Admins?: ThreadRolePermissionsBlob, @@ -165,57 +193,36 @@ const TOP_LEVEL_DESCENDANT = DESCENDANT + TOP_LEVEL; const OPEN_TOP_LEVEL_DESCENDANT = DESCENDANT + OPEN_TOP_LEVEL; -const voicedPermissions = { - [threadPermissions.VOICED]: true, - [threadPermissions.EDIT_ENTRIES]: true, - [threadPermissions.EDIT_THREAD_NAME]: true, - [threadPermissions.EDIT_THREAD_COLOR]: true, - [threadPermissions.EDIT_THREAD_DESCRIPTION]: true, - [threadPermissions.EDIT_THREAD_AVATAR]: true, - [threadPermissions.CREATE_SUBCHANNELS]: true, - [threadPermissions.ADD_MEMBERS]: true, -}; - function getRolePermissionBlobsForCommunity( threadType: ThreadType, ): RolePermissionBlobs { - const openDescendantKnowOf = OPEN_DESCENDANT + threadPermissions.KNOW_OF; - const openDescendantVisible = OPEN_DESCENDANT + threadPermissions.VISIBLE; - const openTopLevelDescendantJoinThread = - OPEN_TOP_LEVEL_DESCENDANT + threadPermissions.JOIN_THREAD; - const openChildJoinThread = OPEN_CHILD + threadPermissions.JOIN_THREAD; - const openChildAddMembers = OPEN_CHILD + threadPermissions.ADD_MEMBERS; - - const genesisMemberPermissions = { - [threadPermissions.KNOW_OF]: true, - [threadPermissions.VISIBLE]: true, - [openDescendantKnowOf]: true, - [openDescendantVisible]: true, - [openTopLevelDescendantJoinThread]: true, - }; - const baseMemberPermissions = { - ...genesisMemberPermissions, - [threadPermissions.REACT_TO_MESSAGE]: true, - [threadPermissions.EDIT_MESSAGE]: true, - [threadPermissions.LEAVE_THREAD]: true, - [threadPermissions.CREATE_SIDEBARS]: true, - [threadPermissions.ADD_MEMBERS]: true, - [openChildJoinThread]: true, - [openChildAddMembers]: true, - }; + const baseMemberUserSurfacedPermissions = [ + userSurfacedPermissions.REACT_TO_MESSAGES, + userSurfacedPermissions.EDIT_MESSAGES, + userSurfacedPermissions.ADD_MEMBERS, + ]; + const baseVoicedUserSurfacedPermissions = [ + userSurfacedPermissions.EDIT_CALENDAR, + userSurfacedPermissions.CREATE_AND_EDIT_CHANNELS, + ]; - let memberPermissions; - if (threadType === threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT) { - memberPermissions = baseMemberPermissions; - } else if (threadType === threadTypes.GENESIS) { - memberPermissions = genesisMemberPermissions; + let memberUserSurfacedPermissions; + if (threadType === threadTypes.GENESIS) { + memberUserSurfacedPermissions = []; + } else if (threadType === threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT) { + memberUserSurfacedPermissions = baseMemberUserSurfacedPermissions; } else { - memberPermissions = { - ...baseMemberPermissions, - ...voicedPermissions, - }; + memberUserSurfacedPermissions = [ + ...baseMemberUserSurfacedPermissions, + ...baseVoicedUserSurfacedPermissions, + ]; } + const memberPermissions = getThreadPermissionBlobFromUserSurfacedPermissions( + memberUserSurfacedPermissions, + threadType, + ); + const descendantKnowOf = DESCENDANT + threadPermissions.KNOW_OF; const descendantVisible = DESCENDANT + threadPermissions.VISIBLE; const topLevelDescendantJoinThread = @@ -299,6 +306,17 @@ } function getRolePermissionBlobs(threadType: ThreadType): RolePermissionBlobs { + const voicedPermissions = { + [threadPermissions.VOICED]: true, + [threadPermissions.EDIT_ENTRIES]: true, + [threadPermissions.EDIT_THREAD_NAME]: true, + [threadPermissions.EDIT_THREAD_COLOR]: true, + [threadPermissions.EDIT_THREAD_DESCRIPTION]: true, + [threadPermissions.EDIT_THREAD_AVATAR]: true, + [threadPermissions.CREATE_SUBCHANNELS]: true, + [threadPermissions.ADD_MEMBERS]: true, + }; + if (threadType === threadTypes.SIDEBAR) { const memberPermissions = { [threadPermissions.VOICED]: true, @@ -405,6 +423,42 @@ return getRolePermissionBlobsForCommunity(threadType); } +function getUniversalCommunityRootPermissionsBlob( + threadType: CommunityRootThreadType, +): ThreadRolePermissionsBlob { + const openDescendantKnowOf = OPEN_DESCENDANT + threadPermissions.KNOW_OF; + const openDescendantVisible = OPEN_DESCENDANT + threadPermissions.VISIBLE; + const openChildJoinThread = OPEN_CHILD + threadPermissions.JOIN_THREAD; + const openTopLevelDescendantJoinThread = + OPEN_TOP_LEVEL_DESCENDANT + threadPermissions.JOIN_THREAD; + + const genesisUniversalCommunityPermissions = { + [threadPermissions.KNOW_OF]: true, + [threadPermissions.VISIBLE]: true, + [openDescendantKnowOf]: true, + [openDescendantVisible]: true, + [openTopLevelDescendantJoinThread]: true, + }; + + const baseUniversalCommunityPermissions = { + ...genesisUniversalCommunityPermissions, + [threadPermissions.CREATE_SIDEBARS]: true, + [threadPermissions.LEAVE_THREAD]: true, + [openChildJoinThread]: true, + }; + + if (threadType === threadTypes.GENESIS) { + return genesisUniversalCommunityPermissions; + } else if (threadType === threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT) { + return baseUniversalCommunityPermissions; + } else { + return { + ...baseUniversalCommunityPermissions, + [threadPermissions.VOICED]: true, + }; + } +} + export { permissionLookup, getAllThreadPermissions, @@ -412,4 +466,5 @@ makePermissionsForChildrenBlob, getRoleForPermissions, getRolePermissionBlobs, + getUniversalCommunityRootPermissionsBlob, }; diff --git a/lib/types/thread-types-enum.js b/lib/types/thread-types-enum.js --- a/lib/types/thread-types-enum.js +++ b/lib/types/thread-types-enum.js @@ -59,6 +59,11 @@ values(threadTypes), ); +export type CommunityRootThreadType = + | typeof threadTypes.COMMUNITY_ROOT + | typeof threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT + | typeof threadTypes.GENESIS; + export const communityThreadTypes: $ReadOnlyArray = Object.freeze([ threadTypes.COMMUNITY_ROOT, threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT,