Page MenuHomePhorge

D8420.1765300199.diff
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None

D8420.1765300199.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,24 @@
// @flow
import { getRolePermissionBlobs } from 'lib/permissions/thread-permissions.js';
+import {
+ guaranteedCommunityPermissions,
+ 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 type {
+ RoleInfo,
+ RoleModificationRequest,
+} from 'lib/types/thread-types.js';
+import { ServerError } from 'lib/utils/errors.js';
+import { getKeysByValue } from 'lib/utils/objects.js';
import createIDs from './id-creator.js';
import { dbQuery, SQL } from '../database/database.js';
+import { checkThreadPermission } from '../fetchers/thread-permission-fetchers.js';
+import type { Viewer } from '../session/viewer.js';
type InitialRoles = {
+default: RoleInfo,
@@ -59,4 +72,57 @@
};
}
-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 =>
+ getKeysByValue(configurableCommunityPermissions, permission),
+ )
+ .flat();
+
+ const rolePermissions = [
+ ...guaranteedCommunityPermissions,
+ ...configuredPermissions,
+ ];
+ 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
@@ -9,6 +9,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,
@@ -26,6 +27,7 @@
type ThreadFetchMediaRequest,
type ToggleMessagePinRequest,
type ToggleMessagePinResult,
+ type RoleModificationRequest,
rawThreadInfoValidator,
} from 'lib/types/thread-types.js';
import { serverUpdateInfoValidator } from 'lib/types/update-types.js';
@@ -44,6 +46,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';
@@ -354,6 +357,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,
@@ -365,4 +387,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 } from 'tcomb';
+import t, { type TDict, type TEnums } from 'tcomb';
import { values } from '../utils/objects.js';
import { tBool, tShape } from '../utils/validation-utils.js';
@@ -101,6 +101,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 calendarEditPermission = {
title: 'Edit calendar',
@@ -314,6 +319,35 @@
[descendantManageInviteLinks]: userSurfacedPermissions.MANAGE_INVITE_LINKS,
});
+export const guaranteedCommunityPermissions: $ReadOnlyArray<string> = [
+ // know_of | descendant_open_know_of
+ threadPermissions.KNOW_OF,
+ threadPermissionPropagationPrefixes.DESCENDANT +
+ threadPermissionFilterPrefixes.OPEN +
+ threadPermissions.KNOW_OF,
+
+ // voiced
+ threadPermissionPropagationPrefixes.DESCENDANT + 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 @@
type ThreadRolePermissionsBlob,
threadPermissionsInfoValidator,
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';
@@ -431,6 +432,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;
diff --git a/lib/utils/objects.js b/lib/utils/objects.js
--- a/lib/utils/objects.js
+++ b/lib/utils/objects.js
@@ -116,4 +116,15 @@
);
}
-export { findMaximumDepth, values, hash, assertObjectsAreEqual, deepDiff };
+function getKeysByValue<K, T>(obj: ObjectMap<K, T>, value: T): K[] {
+ return Object.keys(obj).filter((key: K) => obj[key] === value);
+}
+
+export {
+ findMaximumDepth,
+ values,
+ hash,
+ assertObjectsAreEqual,
+ deepDiff,
+ getKeysByValue,
+};

File Metadata

Mime Type
text/plain
Expires
Tue, Dec 9, 5:09 PM (4 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5853710
Default Alt Text
D8420.1765300199.diff (9 KB)

Event Timeline