Page MenuHomePhabricator

D9675.diff
No OneTemporary

D9675.diff

diff --git a/keyserver/src/scripts/validate-role-permissions.js b/keyserver/src/scripts/validate-role-permissions.js
new file mode 100644
--- /dev/null
+++ b/keyserver/src/scripts/validate-role-permissions.js
@@ -0,0 +1,162 @@
+// @flow
+
+import { getRolePermissionBlobs } 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';
+
+import { main } from './utils.js';
+import { SQL, dbQuery } from '../database/database.js';
+
+async function validateRolePermissions() {
+ // Get all roles for existing communities since custom roles are at a
+ // community-level rather than a thread-level.
+ const fetchRolesQuery = SQL`
+ SELECT r.id, r.name, r.permissions, r.thread, t.type, t.default_role
+ FROM roles r
+ INNER JOIN threads t
+ ON t.id = r.thread
+ WHERE t.type IN (${[
+ threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT,
+ threadTypes.COMMUNITY_ROOT,
+ ]})
+ `;
+ const [results] = await dbQuery(fetchRolesQuery);
+
+ for (const result of results) {
+ const roleID = result.id.toString();
+ const roleName = result.name;
+ const existingRolePermissions = JSON.parse(result.permissions);
+ const threadID = result.thread.toString();
+ const threadType = result.type;
+ const threadDefaultRole = result.default_role.toString();
+
+ // 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
+ // role is a custom role and the expected permissions are the universal
+ // community permissions assuming the role has not been edited.
+ // The case of a role being edited is handled below.
+ const expectedPermissionBlobs = getRolePermissionBlobs(threadType);
+ let baseExpectedPermissionBlob;
+ if (roleID === threadDefaultRole) {
+ baseExpectedPermissionBlob = expectedPermissionBlobs.Members;
+ } else if (roleName === 'Admins') {
+ baseExpectedPermissionBlob = expectedPermissionBlobs.Admins;
+ } else if (roleName) {
+ baseExpectedPermissionBlob = Object.fromEntries(
+ universalCommunityPermissions.map(permission => [permission, true]),
+ );
+ } else {
+ baseExpectedPermissionBlob = {};
+ }
+ console.log('====================================');
+
+ // Ideally, this should never happen, but we'll skip over this in case.
+ if (!baseExpectedPermissionBlob) {
+ console.log(
+ `Skipping role ${roleName} with ID (${roleID}) in thread ${threadID}`,
+ );
+ continue;
+ }
+
+ // Deep diff seems to compare objects one-way (so deepDiff(a, b) !==
+ // deepDiff(b, a)). This means that if a key is not in `a` but not in `b`,
+ // the diff will not include that key. As a result, we need to compare both
+ // ways to ensure that we're not missing any permission discrepancies.
+ const expectedPermissionsToExistingPermissions = deepDiff(
+ baseExpectedPermissionBlob,
+ existingRolePermissions,
+ );
+ const existingPermissionsToExpectedPermissions = deepDiff(
+ existingRolePermissions,
+ baseExpectedPermissionBlob,
+ );
+
+ console.log(
+ `Validating: Role Name (${roleName}) | Role ID (${roleID}) | ` +
+ `Thread Type (${threadType}) | Thread ID (${threadID})\n`,
+ );
+ console.log(
+ `deepDiff(baseExpectedPermissionBlob, existingRolePermissions) = ${JSON.stringify(
+ expectedPermissionsToExistingPermissions,
+ null,
+ 2,
+ )}\n`,
+ );
+ console.log(
+ `deepDiff(existingRolePermissions, baseExpectedPermissionBlob) = ${JSON.stringify(
+ existingPermissionsToExpectedPermissions,
+ null,
+ 2,
+ )}\n`,
+ );
+
+ // Now, we want to see if the permission discrepancies are due to the user
+ // editing the role. To do this, we need to identify any permission
+ // discrepancies that could be linked to a specific user-surfaced
+ // permission. This could be useful in manually parsing through the
+ // script results to 'write off' discrepancies as user role edits.
+ const userSurfacedExpectedPermissionsToExistingPermissions = new Set();
+ const userSurfacedExistingPermissionsToExpectedPermissions = new Set();
+
+ for (const permission of values(userSurfacedPermissions)) {
+ const permissionSet = Array.from(
+ configurableCommunityPermissions[permission],
+ );
+ for (const p of permissionSet) {
+ if (expectedPermissionsToExistingPermissions[p] === true) {
+ userSurfacedExpectedPermissionsToExistingPermissions.add(permission);
+ }
+ if (existingPermissionsToExpectedPermissions[p] === true) {
+ userSurfacedExistingPermissionsToExpectedPermissions.add(permission);
+ }
+ }
+ }
+
+ const expectedPermissionsToExistingPermissionsValues = values(
+ expectedPermissionsToExistingPermissions,
+ );
+ const existingPermissionsToExpectedPermissionsValues = values(
+ existingPermissionsToExpectedPermissions,
+ );
+
+ if (
+ expectedPermissionsToExistingPermissionsValues.length > 0 ||
+ existingPermissionsToExpectedPermissionsValues.length > 0
+ ) {
+ console.log(
+ `Potential permission discrepancies for role ${roleName} that ` +
+ `could be linked back to user surfaced permissions (i.e. not an ` +
+ `actual discrepancy, but rather a user edited a role): \n`,
+ );
+
+ if (expectedPermissionsToExistingPermissionsValues.length > 0) {
+ console.log(
+ `userSurfacedExpectedPermissionsToExistingPermissions = ${JSON.stringify(
+ [...userSurfacedExpectedPermissionsToExistingPermissions],
+ null,
+ 2,
+ )}\n`,
+ );
+ }
+ if (existingPermissionsToExpectedPermissionsValues.length > 0) {
+ console.log(
+ `userSurfacedExistingPermissionsToExpectedPermissions = ${JSON.stringify(
+ [...userSurfacedExistingPermissionsToExpectedPermissions],
+ null,
+ 2,
+ )}`,
+ );
+ }
+ }
+ }
+
+ console.log('====================================');
+}
+
+main([validateRolePermissions]);

File Metadata

Mime Type
text/plain
Expires
Fri, Dec 27, 6:19 AM (9 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2711286
Default Alt Text
D9675.diff (6 KB)

Event Timeline