Page MenuHomePhorge

D14505.1769054706.diff
No OneTemporary

Size
17 KB
Referenced Files
None
Subscribers
None

D14505.1769054706.diff

diff --git a/lib/permissions/minimally-encoded-thread-permissions.js b/lib/permissions/minimally-encoded-thread-permissions.js
--- a/lib/permissions/minimally-encoded-thread-permissions.js
+++ b/lib/permissions/minimally-encoded-thread-permissions.js
@@ -1,23 +1,40 @@
// @flow
import invariant from 'invariant';
+import _isEqual from 'lodash/fp/isEqual.js';
+import _mapValues from 'lodash/fp/mapValues.js';
import {
- parseThreadPermissionString,
constructThreadPermissionString,
+ parseThreadPermissionString,
} from './prefixes.js';
+import { specialRoles } from './special-roles.js';
import {
+ getAllThreadPermissions,
+ getRolePermissionBlobs,
+ makePermissionsBlob,
+ makePermissionsForChildrenBlob,
+} from './thread-permissions.js';
+import type { ThreadStoreOperation } from '../ops/thread-store-ops.js';
+import { getChildThreads } from '../selectors/thread-selectors.js';
+import type {
+ MinimallyEncodedThickMemberInfo,
+ ThickRawThreadInfo,
+} from '../types/minimally-encoded-thread-permissions-types.js';
+import {
+ assertThreadPermission,
+ assertThreadPermissionFilterPrefix,
+ assertThreadPermissionMembershipPrefix,
+ assertThreadPermissionPropagationPrefix,
type ThreadPermission,
type ThreadPermissionInfo,
+ threadPermissions,
+ type ThreadPermissionsBlob,
type ThreadPermissionsInfo,
type ThreadRolePermissionsBlob,
- threadPermissions,
- assertThreadPermission,
- assertThreadPermissionPropagationPrefix,
- assertThreadPermissionFilterPrefix,
- assertThreadPermissionMembershipPrefix,
} from '../types/thread-permission-types.js';
-import { entries, invertObjectToMap } from '../utils/objects.js';
+import type { RawThreadInfos } from '../types/thread-types.js';
+import { entries, invertObjectToMap, values } from '../utils/objects.js';
import type { TRegex } from '../utils/validation-utils.js';
import { tRegex } from '../utils/validation-utils.js';
@@ -246,6 +263,213 @@
]),
);
+function updateRolesAndPermissions(
+ threads: RawThreadInfos,
+ rolePermissionsUpdater: ThreadRolePermissionsBlob => ThreadRolePermissionsBlob,
+): {
+ +operations: $ReadOnlyArray<ThreadStoreOperation>,
+} {
+ const updatedThreads = { ...threads };
+
+ const childThreads = getChildThreads(threads);
+ const threadChildrenIDs = _mapValues(
+ threadChildren => threadChildren.map(thread => thread.id),
+ childThreads,
+ );
+ function updateRoles(threadID: string) {
+ const threadInfo = updatedThreads[threadID];
+
+ const roles = { ...threadInfo.roles };
+ for (const roleID in roles) {
+ const role = roles[roleID];
+
+ const rolePermissionBlobs = getRolePermissionBlobs(threadInfo.type);
+ let updatedPermissionsBlob;
+ if (threadInfo.thick) {
+ // Each thick threads has exactly one role - for its members. We
+ // don't allow managing this role, which means we can simply get the
+ // computed permissions from the rolePermissionBlobs.
+ updatedPermissionsBlob = rolePermissionBlobs.Members;
+ } else if (
+ role.specialRole === specialRoles.ADMIN_ROLE &&
+ rolePermissionBlobs.Admins
+ ) {
+ // We don't allow managing the admins role, so in this case we can
+ // also use the result of getRolePermissionBlobs that should contain
+ // all the required changes.
+ updatedPermissionsBlob = rolePermissionBlobs.Admins;
+ } else {
+ // We allow admins to manage non-admin roles, which means we can't
+ // simply rely on the result of getRolePermissionBlobs - it doesn't
+ // contain any changes made by admins. In this case, we're running the
+ // rolePermissionsUpdater that updates the role accordingly. It is
+ // similar to what we do on the keyserver during a migration in
+ // addNewUserSurfacedPermission inside migration-config.js.
+ updatedPermissionsBlob = rolePermissionsUpdater(
+ decodeThreadRolePermissionsBitmaskArray(role.permissions),
+ );
+ }
+ const encodedUpdatedPermissions = threadRolePermissionsBlobToBitmaskArray(
+ updatedPermissionsBlob,
+ );
+ roles[roleID] = {
+ ...role,
+ permissions: encodedUpdatedPermissions,
+ };
+ updatedThreads[threadID] = {
+ ...threadInfo,
+ roles,
+ };
+ }
+ }
+
+ type MemberToThreadPermissions = {
+ [member: string]: ?ThreadPermissionsBlob,
+ };
+
+ function updateThickThreadMembers(
+ threadInfo: ThickRawThreadInfo,
+ memberToThreadPermissionsFromParent: ?MemberToThreadPermissions,
+ ): {
+ +members: $ReadOnlyArray<MinimallyEncodedThickMemberInfo>,
+ +memberToThreadPermissionsForChildren: MemberToThreadPermissions,
+ } {
+ const updatedMembers = [];
+ const memberToThreadPermissionsForChildren: MemberToThreadPermissions = {};
+ for (const member of threadInfo.members) {
+ const { id, role } = member;
+
+ const rolePermissions = role
+ ? decodeThreadRolePermissionsBitmaskArray(
+ threadInfo.roles[role].permissions,
+ )
+ : null;
+ const permissionsFromParent = memberToThreadPermissionsFromParent?.[id];
+
+ const computedPermissions = makePermissionsBlob(
+ rolePermissions,
+ permissionsFromParent,
+ threadInfo.id,
+ threadInfo.type,
+ );
+
+ updatedMembers.push({
+ ...member,
+ permissions: permissionsToBitmaskHex(
+ getAllThreadPermissions(computedPermissions, threadInfo.id),
+ ),
+ });
+
+ memberToThreadPermissionsForChildren[member.id] =
+ makePermissionsForChildrenBlob(computedPermissions);
+ }
+ return {
+ members: updatedMembers,
+ memberToThreadPermissionsForChildren,
+ };
+ }
+
+ function recursivelyUpdateThickThreadMemberPermissions(
+ threadID: string,
+ memberToThreadPermissionsFromParent: ?MemberToThreadPermissions,
+ ) {
+ const threadInfo = updatedThreads[threadID];
+ if (!threadInfo.thick) {
+ // We don't update members of thin threads because we aren't storing
+ // their permissions inside their member info. Member permissions are
+ // instead computed on the fly based on the roles. We're planning to
+ // use the same approach for thick threads in the future.
+ return;
+ }
+
+ const { members: updatedMembers, memberToThreadPermissionsForChildren } =
+ updateThickThreadMembers(threadInfo, memberToThreadPermissionsFromParent);
+ updatedThreads[threadID] = {
+ ...threadInfo,
+ members: updatedMembers,
+ };
+ for (const childID of threadChildrenIDs[threadID]) {
+ recursivelyUpdateThickThreadMemberPermissions(
+ childID,
+ memberToThreadPermissionsForChildren,
+ );
+ }
+ }
+
+ function recursivelyUpdateCurrentUserPermissions(
+ threadID: string,
+ permissionsFromParent: ?ThreadPermissionsBlob,
+ ) {
+ const threadInfo = updatedThreads[threadID];
+ const { currentUser, roles } = threadInfo;
+ const { role } = currentUser;
+
+ const rolePermissions = role
+ ? decodeThreadRolePermissionsBitmaskArray(roles[role].permissions)
+ : null;
+ const computedPermissions = makePermissionsBlob(
+ rolePermissions,
+ permissionsFromParent,
+ threadInfo.id,
+ threadInfo.type,
+ );
+
+ updatedThreads[threadID] = {
+ ...threadInfo,
+ currentUser: {
+ ...currentUser,
+ permissions: permissionsToBitmaskHex(
+ getAllThreadPermissions(computedPermissions, threadInfo.id),
+ ),
+ },
+ };
+
+ for (const childID of threadChildrenIDs[threadID]) {
+ recursivelyUpdateCurrentUserPermissions(
+ childID,
+ makePermissionsForChildrenBlob(computedPermissions),
+ );
+ }
+ }
+
+ for (const thread of values(threads)) {
+ if (!thread.parentThreadID) {
+ // We don't need to update these recursively because roles don't
+ // cascade from parents - modifying a parent thread role doesn't have
+ // any impact on children roles. This stays in contrast to how our
+ // permissions work - setting a parent role can affect which
+ // permissions are granted in the children threads, because we're
+ // propagating the permissions (based on a couple of strategies).
+ updateRoles(thread.id);
+ }
+ }
+ const rootThreadIDs = values(threads)
+ .filter(thread => !thread.parentThreadID)
+ .map(thread => thread.id);
+ rootThreadIDs.forEach(threadID =>
+ recursivelyUpdateThickThreadMemberPermissions(threadID, null),
+ );
+ rootThreadIDs.forEach(threadID =>
+ recursivelyUpdateCurrentUserPermissions(threadID, null),
+ );
+
+ const operations = values(updatedThreads)
+ .filter(
+ updatedThread => !_isEqual(updatedThread, threads[updatedThread.id]),
+ )
+ .map(thread => ({
+ type: 'replace',
+ payload: {
+ id: thread.id,
+ threadInfo: thread,
+ },
+ }));
+
+ return {
+ operations,
+ };
+}
+
export {
permissionsToBitmaskHex,
threadPermissionsFromBitmaskHex,
@@ -254,6 +478,7 @@
decodeRolePermissionBitmask,
threadRolePermissionsBlobToBitmaskArray,
decodeThreadRolePermissionsBitmaskArray,
+ updateRolesAndPermissions,
tHexEncodedRolePermission,
tHexEncodedPermissionsBitmask,
};
diff --git a/lib/selectors/thread-selectors.js b/lib/selectors/thread-selectors.js
--- a/lib/selectors/thread-selectors.js
+++ b/lib/selectors/thread-selectors.js
@@ -180,28 +180,29 @@
},
);
+function getChildThreads<T: RawThreadInfo | ThreadInfo>(threadInfos: {
+ +[id: string]: T,
+}): { +[id: string]: Array<T> } {
+ const result: {
+ [string]: T[],
+ } = {};
+ for (const id in threadInfos) {
+ const threadInfo = threadInfos[id];
+ const parentThreadID = threadInfo.parentThreadID;
+ if (parentThreadID === null || parentThreadID === undefined) {
+ continue;
+ }
+ if (result[parentThreadID] === undefined) {
+ result[parentThreadID] = ([]: T[]);
+ }
+ result[parentThreadID].push(threadInfo);
+ }
+ return result;
+}
+
const childThreadInfos: (state: BaseAppState<>) => {
+[id: string]: $ReadOnlyArray<ThreadInfo>,
-} = createSelector(
- threadInfoSelector,
- (threadInfos: { +[id: string]: ThreadInfo }) => {
- const result: {
- [string]: ThreadInfo[],
- } = {};
- for (const id in threadInfos) {
- const threadInfo = threadInfos[id];
- const parentThreadID = threadInfo.parentThreadID;
- if (parentThreadID === null || parentThreadID === undefined) {
- continue;
- }
- if (result[parentThreadID] === undefined) {
- result[parentThreadID] = ([]: ThreadInfo[]);
- }
- result[parentThreadID].push(threadInfo);
- }
- return result;
- },
-);
+} = createSelector(threadInfoSelector, getChildThreads);
const containedThreadInfos: (state: BaseAppState<>) => {
+[id: string]: $ReadOnlyArray<ThreadInfo>,
@@ -587,4 +588,5 @@
threadInfosSelectorForThreadType,
thickRawThreadInfosSelector,
unreadThickThreadIDsSelector,
+ getChildThreads,
};
diff --git a/native/redux/persist.js b/native/redux/persist.js
--- a/native/redux/persist.js
+++ b/native/redux/persist.js
@@ -2,6 +2,7 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import invariant from 'invariant';
+import _keyBy from 'lodash/fp/keyBy.js';
import { Platform } from 'react-native';
import Orientation from 'react-native-orientation-locker';
import { createTransform } from 'redux-persist';
@@ -56,6 +57,7 @@
convertUserInfosToReplaceUserOps,
userStoreOpsHandlers,
} from 'lib/ops/user-store-ops.js';
+import { updateRolesAndPermissions } from 'lib/permissions/minimally-encoded-thread-permissions.js';
import { patchRawThreadInfosWithSpecialRole } from 'lib/permissions/special-roles.js';
import { filterThreadIDsInFilterList } from 'lib/reducers/calendar-filters-reducer.js';
import { highestLocalIDSelector } from 'lib/selectors/local-id-selectors.js';
@@ -103,6 +105,10 @@
} from 'lib/types/report-types.js';
import { defaultConnectionInfo } from 'lib/types/socket-types.js';
import { defaultGlobalThemeInfo } from 'lib/types/theme-types.js';
+import {
+ userSurfacedPermissions,
+ type ThreadRolePermissionsBlob,
+} from 'lib/types/thread-permission-types.js';
import type {
ClientDBThreadInfo,
LegacyRawThreadInfo,
@@ -126,9 +132,11 @@
MigrationsManifest,
} from 'lib/utils/migration-utils.js';
import { entries } from 'lib/utils/objects.js';
+import { toggleUserSurfacedPermission } from 'lib/utils/role-utils.js';
import {
deprecatedConvertClientDBThreadInfoToRawThreadInfo,
convertRawThreadInfoToClientDBThreadInfo,
+ convertClientDBThreadInfoToRawThreadInfo,
} from 'lib/utils/thread-ops-utils.js';
import { getUUID } from 'lib/utils/uuid.js';
@@ -1517,6 +1525,32 @@
},
ops: {},
}): MigrationFunction<NavInfo, AppState>),
+ [87]: (async (state: AppState) => {
+ const clientDBThreadInfos = commCoreModule.getAllThreadsSync();
+ const rawThreadInfos = clientDBThreadInfos.map(
+ convertClientDBThreadInfoToRawThreadInfo,
+ );
+ const keyedRawThreadInfos = _keyBy('id')(rawThreadInfos);
+
+ // This function also results in setting DELETE_OWN_MESSAGES and
+ // DELETE_ALL_MESSAGES for admins. It is added automatically based on the
+ // result of getRolePermissionBlobs call.
+ const { operations } = updateRolesAndPermissions(
+ keyedRawThreadInfos,
+ (permissions: ThreadRolePermissionsBlob) =>
+ toggleUserSurfacedPermission(
+ permissions,
+ userSurfacedPermissions.DELETE_OWN_MESSAGES,
+ ),
+ );
+
+ return {
+ state,
+ ops: {
+ threadStoreOperations: operations,
+ },
+ };
+ }: MigrationFunction<NavInfo, AppState>),
});
// NOTE: renaming this object, and especially the `version` property
@@ -1527,7 +1561,7 @@
storage: AsyncStorage,
blacklist: persistBlacklist,
debug: __DEV__,
- version: 86,
+ version: 87,
transforms: [
messageStoreMessagesBlocklistTransform,
reportStoreTransform,
diff --git a/web/redux/persist-constants.js b/web/redux/persist-constants.js
--- a/web/redux/persist-constants.js
+++ b/web/redux/persist-constants.js
@@ -3,6 +3,6 @@
const rootKey = 'root';
const rootKeyPrefix = 'persist:';
const completeRootKey = `${rootKeyPrefix}${rootKey}`;
-const storeVersion = 86;
+const storeVersion = 87;
export { rootKey, rootKeyPrefix, completeRootKey, storeVersion };
diff --git a/web/redux/persist.js b/web/redux/persist.js
--- a/web/redux/persist.js
+++ b/web/redux/persist.js
@@ -12,15 +12,16 @@
type ReplaceKeyserverOperation,
} from 'lib/ops/keyserver-store-ops.js';
import {
- messageStoreOpsHandlers,
- type ReplaceMessageStoreLocalMessageInfoOperation,
type ClientDBMessageStoreOperation,
type MessageStoreOperation,
+ messageStoreOpsHandlers,
+ type ReplaceMessageStoreLocalMessageInfoOperation,
} from 'lib/ops/message-store-ops.js';
import type {
ClientDBThreadStoreOperation,
ThreadStoreOperation,
} from 'lib/ops/thread-store-ops.js';
+import { updateRolesAndPermissions } from 'lib/permissions/minimally-encoded-thread-permissions.js';
import { patchRawThreadInfoWithSpecialRole } from 'lib/permissions/special-roles.js';
import { createUpdateDBOpsForThreadStoreThreadInfos } from 'lib/shared/redux/client-db-utils.js';
import { deprecatedUpdateRolesAndPermissions } from 'lib/shared/redux/deprecated-update-roles-and-permissions.js';
@@ -36,6 +37,8 @@
import { defaultConnectionInfo } from 'lib/types/socket-types.js';
import type { StoreOperations } from 'lib/types/store-ops-types.js';
import { defaultGlobalThemeInfo } from 'lib/types/theme-types.js';
+import type { ThreadRolePermissionsBlob } from 'lib/types/thread-permission-types.js';
+import { userSurfacedPermissions } from 'lib/types/thread-permission-types.js';
import type {
ClientDBThreadInfo,
RawThreadInfos,
@@ -45,14 +48,15 @@
import { isDev } from 'lib/utils/dev-utils.js';
import { stripMemberPermissionsFromRawThreadInfos } from 'lib/utils/member-info-utils.js';
import {
- generateIDSchemaMigrationOpsForDrafts,
convertDraftStoreToNewIDSchema,
createAsyncMigrate,
- type StorageMigrationFunction,
+ generateIDSchemaMigrationOpsForDrafts,
type MigrationFunction,
type MigrationsManifest,
+ type StorageMigrationFunction,
} from 'lib/utils/migration-utils.js';
import { entries, values } from 'lib/utils/objects.js';
+import { toggleUserSurfacedPermission } from 'lib/utils/role-utils.js';
import {
convertClientDBThreadInfoToRawThreadInfo,
convertRawThreadInfoToClientDBThreadInfo,
@@ -732,6 +736,58 @@
},
ops: {},
}): MigrationFunction<WebNavInfo, AppState>),
+ [87]: (async (state: AppState) => {
+ const sharedWorker = await getCommSharedWorker();
+ const isDatabaseSupported = await sharedWorker.isSupported();
+
+ if (!isDatabaseSupported) {
+ return {
+ state,
+ ops: {},
+ };
+ }
+
+ const stores = await sharedWorker.schedule({
+ type: workerRequestMessageTypes.GET_CLIENT_STORE,
+ });
+
+ const clientDBThreadInfos: ?$ReadOnlyArray<ClientDBThreadInfo> =
+ stores?.store?.threads;
+
+ if (
+ clientDBThreadInfos === null ||
+ clientDBThreadInfos === undefined ||
+ clientDBThreadInfos.length === 0
+ ) {
+ return {
+ state,
+ ops: {},
+ };
+ }
+
+ const rawThreadInfos = clientDBThreadInfos.map(
+ convertClientDBThreadInfoToRawThreadInfo,
+ );
+
+ const keyedRawThreadInfos = _keyBy('id')(rawThreadInfos);
+ // This function also results in setting DELETE_OWN_MESSAGES and
+ // DELETE_ALL_MESSAGES for admins. It is added automatically based on the
+ // result of getRolePermissionBlobs call.
+ const { operations } = updateRolesAndPermissions(
+ keyedRawThreadInfos,
+ (permissions: ThreadRolePermissionsBlob) =>
+ toggleUserSurfacedPermission(
+ permissions,
+ userSurfacedPermissions.DELETE_OWN_MESSAGES,
+ ),
+ );
+ return {
+ state,
+ ops: {
+ threadStoreOperations: operations,
+ },
+ };
+ }: MigrationFunction<WebNavInfo, AppState>),
};
const persistConfig: PersistConfig = {

File Metadata

Mime Type
text/plain
Expires
Thu, Jan 22, 4:05 AM (10 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5973916
Default Alt Text
D14505.1769054706.diff (17 KB)

Event Timeline