Page MenuHomePhorge

D8420.1765126903.diff
No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None

D8420.1765126903.diff

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,11 +1,25 @@
// @flow
import { getRolePermissionBlobs } from 'lib/permissions/thread-permissions.js';
+import {
+ universalCommunityPermissions,
+ userSurfacedPermissionsSet,
+ configurableCommunityPermissions,
+ threadPermissions,
+} from 'lib/types/thread-permission-types.js';
import type { ThreadType } from 'lib/types/thread-types-enum.js';
-import type { RoleInfo } from 'lib/types/thread-types.js';
+import { threadTypes } from 'lib/types/thread-types-enum.js';
+import type {
+ RoleInfo,
+ RoleModificationRequest,
+} from 'lib/types/thread-types.js';
+import { ServerError } from 'lib/utils/errors.js';
import createIDs from './id-creator.js';
import { dbQuery, SQL } from '../database/database.js';
+import { fetchThreadInfos } from '../fetchers/thread-fetchers.js';
+import { checkThreadPermission } from '../fetchers/thread-permission-fetchers.js';
+import type { Viewer } from '../session/viewer.js';
type InitialRoles = {
+default: RoleInfo,
@@ -59,4 +73,71 @@
};
}
-export { createInitialRolesForNewThread };
+async function modifyRole(
+ viewer: Viewer,
+ request: RoleModificationRequest,
+): Promise<void> {
+ const hasPermission = await checkThreadPermission(
+ viewer,
+ request.community,
+ threadPermissions.CHANGE_ROLE,
+ );
+ if (!hasPermission) {
+ throw new ServerError('invalid_credentials');
+ }
+
+ const { community, name, permissions, action } = request;
+
+ for (const permission of permissions) {
+ if (!userSurfacedPermissionsSet.has(permission)) {
+ throw new ServerError('invalid_parameters');
+ }
+ }
+
+ 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])),
+ );
+
+ const row = [id, community, name, permissionsBlob, time];
+
+ let query = SQL``;
+ if (action === 'create_role') {
+ query = SQL`
+ INSERT INTO roles (id, thread, name, permissions, creation_time)
+ VALUES (${row})
+ `;
+ } else if (action === 'edit_role') {
+ throw new ServerError("unimplemented: can't edit roles yet");
+ }
+
+ await dbQuery(query);
+}
+
+export { createInitialRolesForNewThread, modifyRole };
diff --git a/keyserver/src/endpoints.js b/keyserver/src/endpoints.js
--- a/keyserver/src/endpoints.js
+++ b/keyserver/src/endpoints.js
@@ -59,6 +59,7 @@
threadFetchMediaResponder,
threadJoinResponder,
toggleMessagePinResponder,
+ roleModificationResponder,
} from './responders/thread-responders.js';
import {
userSubscriptionUpdateResponder,
@@ -203,6 +204,10 @@
responder: logOutResponder,
requiredPolicies: [],
},
+ modify_community_role: {
+ responder: roleModificationResponder,
+ requiredPolicies: baseLegalPolicies,
+ },
policy_acknowledgment: {
responder: policyAcknowledgmentResponder,
requiredPolicies: [],
diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js
--- a/keyserver/src/responders/thread-responders.js
+++ b/keyserver/src/responders/thread-responders.js
@@ -8,6 +8,7 @@
rawMessageInfoValidator,
messageTruncationStatusesValidator,
} from 'lib/types/message-types.js';
+import { userSurfacedPermissionValidator } from 'lib/types/thread-permission-types.js';
import { threadTypes } from 'lib/types/thread-types-enum.js';
import {
type ThreadDeletionRequest,
@@ -25,6 +26,7 @@
type ThreadFetchMediaRequest,
type ToggleMessagePinRequest,
type ToggleMessagePinResult,
+ type RoleModificationRequest,
} from 'lib/types/thread-types.js';
import { serverUpdateInfoValidator } from 'lib/types/update-types.js';
import { userInfosValidator } from 'lib/types/user-types.js';
@@ -42,6 +44,7 @@
entryQueryInputValidator,
verifyCalendarQueryThreadIDs,
} from './entry-responders.js';
+import { modifyRole } from '../creators/role-creator.js';
import { createThread } from '../creators/thread-creator.js';
import { deleteThread } from '../deleters/thread-deleters.js';
import { fetchMediaForThread } from '../fetchers/upload-fetchers.js';
@@ -346,6 +349,25 @@
);
}
+const roleModificationRequestInputValidator = tShape<RoleModificationRequest>({
+ community: tID,
+ name: t.String,
+ permissions: t.list(userSurfacedPermissionValidator),
+ action: t.enums.of(['create_role', 'edit_role']),
+});
+
+async function roleModificationResponder(
+ viewer: Viewer,
+ input: mixed,
+): Promise<void> {
+ const request = await validateInput(
+ viewer,
+ roleModificationRequestInputValidator,
+ input,
+ );
+ await modifyRole(viewer, request);
+}
+
export {
threadDeletionResponder,
roleUpdateResponder,
@@ -357,4 +379,5 @@
threadFetchMediaResponder,
newThreadRequestInputValidator,
toggleMessagePinResponder,
+ roleModificationResponder,
};
diff --git a/lib/actions/thread-actions.js b/lib/actions/thread-actions.js
--- a/lib/actions/thread-actions.js
+++ b/lib/actions/thread-actions.js
@@ -14,6 +14,7 @@
ThreadFetchMediaResult,
ToggleMessagePinRequest,
ToggleMessagePinResult,
+ RoleModificationRequest,
} from '../types/thread-types.js';
import type { CallServerEndpoint } from '../utils/call-server-endpoint.js';
import { values } from '../utils/objects.js';
@@ -193,6 +194,14 @@
};
};
+const modifyCommunityRole =
+ (
+ callServerEndpoint: CallServerEndpoint,
+ ): ((request: RoleModificationRequest) => Promise<void>) =>
+ async request => {
+ await callServerEndpoint('modify_community_role', request);
+ };
+
export {
deleteThreadActionTypes,
deleteThread,
@@ -211,4 +220,5 @@
fetchThreadMedia,
toggleMessagePinActionTypes,
toggleMessagePin,
+ modifyCommunityRole,
};
diff --git a/lib/types/endpoints.js b/lib/types/endpoints.js
--- a/lib/types/endpoints.js
+++ b/lib/types/endpoints.js
@@ -71,6 +71,7 @@
GET_SESSION_PUBLIC_KEYS: 'get_session_public_keys',
JOIN_THREAD: 'join_thread',
LEAVE_THREAD: 'leave_thread',
+ MODIFY_COMMUNITY_ROLE: 'modify_community_role',
REMOVE_MEMBERS: 'remove_members',
REQUEST_ACCESS: 'request_access',
RESTORE_ENTRY: 'restore_entry',
diff --git a/lib/types/thread-permission-types.js b/lib/types/thread-permission-types.js
--- a/lib/types/thread-permission-types.js
+++ b/lib/types/thread-permission-types.js
@@ -1,7 +1,7 @@
// @flow
import invariant from 'invariant';
-import t, { type TDict, type TUnion } from 'tcomb';
+import t, { type TDict, type TUnion, type TEnums } from 'tcomb';
import { values } from '../utils/objects.js';
import { tBool, tShape, tID } from '../utils/validation-utils.js';
@@ -114,6 +114,11 @@
MANAGE_INVITE_LINKS: 'manage_invite_links',
});
export type UserSurfacedPermission = $Values<typeof userSurfacedPermissions>;
+export const userSurfacedPermissionsSet: $ReadOnlySet<UserSurfacedPermission> =
+ new Set(values(userSurfacedPermissions));
+export const userSurfacedPermissionValidator: TEnums = t.enums.of(
+ values(userSurfacedPermissions),
+);
const editCalendarPermission = {
title: 'Edit calendar',
@@ -372,6 +377,37 @@
[userSurfacedPermissions.MANAGE_INVITE_LINKS]: manageInviteLinksPermissions,
});
+export const universalCommunityPermissions: $ReadOnlyArray<string> = [
+ // know_of | descendant_open_know_of
+ threadPermissions.KNOW_OF,
+ threadPermissionPropagationPrefixes.DESCENDANT +
+ threadPermissionFilterPrefixes.OPEN +
+ threadPermissions.KNOW_OF,
+
+ // descendant_open_voiced
+ threadPermissionPropagationPrefixes.DESCENDANT +
+ threadPermissionFilterPrefixes.OPEN +
+ threadPermissions.VOICED,
+
+ // visible | descendant_open_visible
+ threadPermissions.VISIBLE,
+ threadPermissionPropagationPrefixes.DESCENDANT +
+ threadPermissionFilterPrefixes.OPEN +
+ threadPermissions.VISIBLE,
+
+ // join_thread | child_open_join_thread | descendant_opentoplevel_join_thread
+ threadPermissions.JOIN_THREAD,
+ threadPermissionPropagationPrefixes.CHILD +
+ threadPermissionFilterPrefixes.OPEN +
+ threadPermissions.JOIN_THREAD,
+ threadPermissionPropagationPrefixes.DESCENDANT +
+ threadPermissionFilterPrefixes.OPEN_TOP_LEVEL +
+ threadPermissions.JOIN_THREAD,
+
+ threadPermissions.CREATE_SIDEBARS,
+ threadPermissions.LEAVE_THREAD,
+];
+
export type ThreadPermissionInfo =
| { +value: true, +source: string }
| { +value: false, +source: null };
diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js
--- a/lib/types/thread-types.js
+++ b/lib/types/thread-types.js
@@ -24,6 +24,7 @@
threadPermissionsInfoValidator,
type ThreadRolePermissionsBlob,
threadRolePermissionsBlobValidator,
+ type UserSurfacedPermission,
} from './thread-permission-types.js';
import { type ThreadType, threadTypeValidator } from './thread-types-enum.js';
import type { ClientUpdateInfo, ServerUpdateInfo } from './update-types.js';
@@ -396,6 +397,13 @@
+threadID: string,
};
+export type RoleModificationRequest = {
+ +community: string,
+ +name: string,
+ +permissions: $ReadOnlyArray<UserSurfacedPermission>,
+ +action: 'create_role' | 'edit_role',
+};
+
// We can show a max of 3 sidebars inline underneath their parent in the chat
// tab. If there are more, we show a button that opens a modal to see the rest
export const maxReadSidebars = 3;

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 7, 5:01 PM (9 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5845108
Default Alt Text
D8420.1765126903.diff (10 KB)

Event Timeline