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,14 @@ // @flow -import { getRolePermissionBlobs } from 'lib/permissions/thread-permissions.js'; import { - universalCommunityPermissions, + getRolePermissionBlobs, + getThreadPermissionBlobFromUserSurfacedPermissions, +} 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, @@ -105,32 +105,16 @@ const [id] = await createIDs('roles', 1); const time = Date.now(); - const configuredPermissions = permissions - .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 permissionsBlob = JSON.stringify( - Object.fromEntries(rolePermissions.map(permission => [permission, true])), + getThreadPermissionBlobFromUserSurfacedPermissions( + permissions, + threadInfo.type, + ), ); const row = [id, community, name, permissionsBlob, time]; 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 @@ -1,13 +1,17 @@ // @flow +import invariant from 'invariant'; + import { parseThreadPermissionString, includeThreadPermissionForThreadType, } from './prefixes.js'; import { + configurableCommunityPermissions, threadPermissionFilterPrefixes, threadPermissionPropagationPrefixes, threadPermissions, + userSurfacedPermissions, } from '../types/thread-permission-types.js'; import type { ThreadPermission, @@ -15,6 +19,7 @@ ThreadPermissionsBlob, ThreadPermissionsInfo, ThreadRolePermissionsBlob, + UserSurfacedPermission, } from '../types/thread-permission-types.js'; import { type ThreadType, threadTypes } from '../types/thread-types-enum.js'; @@ -153,6 +158,27 @@ } } +function getThreadPermissionBlobFromUserSurfacedPermissions( + communityUserSurfacedPermissions: $ReadOnlyArray, + threadType: ThreadType, +): 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 +191,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, -}; +const baseMemberUserSurfacedPermissions = [ + userSurfacedPermissions.REACT_TO_MESSAGES, + userSurfacedPermissions.EDIT_MESSAGES, + userSurfacedPermissions.ADD_MEMBERS, +]; +const baseVoicedUserSurfacedPermissions = [ + userSurfacedPermissions.EDIT_CALENDAR, + userSurfacedPermissions.CREATE_AND_EDIT_CHANNELS, +]; 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, - }; - - 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 = @@ -298,6 +303,17 @@ }; } +const nonCommunityVoicedPermissions = { + [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 getRolePermissionBlobs(threadType: ThreadType): RolePermissionBlobs { if (threadType === threadTypes.SIDEBAR) { const memberPermissions = { @@ -386,7 +402,7 @@ [threadPermissions.REMOVE_MEMBERS]: true, [threadPermissions.EDIT_PERMISSIONS]: true, ...subthreadBasePermissions, - ...voicedPermissions, + ...nonCommunityVoicedPermissions, }; return { Members: memberPermissions, @@ -405,11 +421,53 @@ return getRolePermissionBlobsForCommunity(threadType); } +// ESLint doesn't recognize that invariant always throws +// eslint-disable-next-line consistent-return +function getUniversalCommunityRootPermissionsBlob( + threadType: ThreadType, +): 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 if (threadType === threadTypes.COMMUNITY_ROOT) { + return { + ...baseUniversalCommunityPermissions, + [threadPermissions.VOICED]: true, + }; + } + + invariant(false, 'invalid threadType parameter'); +} + export { permissionLookup, getAllThreadPermissions, makePermissionsBlob, makePermissionsForChildrenBlob, getRoleForPermissions, + getThreadPermissionBlobFromUserSurfacedPermissions, getRolePermissionBlobs, + getUniversalCommunityRootPermissionsBlob, };