diff --git a/app/gen-server/lib/homedb/GroupsManager.ts b/app/gen-server/lib/homedb/GroupsManager.ts new file mode 100644 index 0000000000..0257f4ff2c --- /dev/null +++ b/app/gen-server/lib/homedb/GroupsManager.ts @@ -0,0 +1,275 @@ +import * as roles from "app/common/roles"; +import { AclRule } from "app/gen-server/entity/AclRule"; +import { Document } from "app/gen-server/entity/Document"; +import { Group } from "app/gen-server/entity/Group"; +import { GroupDescriptor, NonGuestGroup, Resource } from "app/gen-server/lib/homedb/Interfaces"; +import { Organization } from "app/gen-server/entity/Organization"; +import { Permissions } from 'app/gen-server/lib/Permissions'; +import { User } from "app/gen-server/entity/User"; +import { Workspace } from "app/gen-server/entity/Workspace"; + +import { EntityManager } from "typeorm"; + +/** + * Class responsible for Groups and Roles Management. + * + * It's only meant to be used by HomeDBManager. If you want to use one of its (instance or static) methods, + * please make an indirection which passes through HomeDBManager. + */ +export class GroupsManager { + // All groups. + public get defaultGroups(): GroupDescriptor[] { + return this._defaultGroups; + } + + // Groups whose permissions are inherited from parent resource to child resources. + public get defaultBasicGroups(): GroupDescriptor[] { + return this._defaultGroups + .filter(_grpDesc => _grpDesc.nestParent); + } + + // Groups that are common to all resources. + public get defaultCommonGroups(): GroupDescriptor[] { + return this._defaultGroups + .filter(_grpDesc => !_grpDesc.orgOnly); + } + + public get defaultGroupNames(): roles.Role[] { + return this._defaultGroups.map(_grpDesc => _grpDesc.name); + } + + public get defaultBasicGroupNames(): roles.BasicRole[] { + return this.defaultBasicGroups + .map(_grpDesc => _grpDesc.name) as roles.BasicRole[]; + } + + public get defaultNonGuestGroupNames(): roles.NonGuestRole[] { + return this._defaultGroups + .filter(_grpDesc => _grpDesc.name !== roles.GUEST) + .map(_grpDesc => _grpDesc.name) as roles.NonGuestRole[]; + } + + public get defaultCommonGroupNames(): roles.NonMemberRole[] { + return this.defaultCommonGroups + .map(_grpDesc => _grpDesc.name) as roles.NonMemberRole[]; + } + + // Returns a map of userIds to the user's strongest default role on the given resource. + // The resource's aclRules, groups, and memberUsers must be populated. + public static getMemberUserRoles(res: Resource, allowRoles: T[]): {[userId: string]: T} { + // Add the users to a map to ensure uniqueness. (A user may be present in + // more than one group) + const userMap: {[userId: string]: T} = {}; + (res.aclRules as AclRule[]).forEach((aclRule: AclRule) => { + const role = aclRule.group.name as T; + if (allowRoles.includes(role)) { + // Map the users to remove sensitive information from the result and + // to add the group names. + aclRule.group.memberUsers.forEach((u: User) => { + // If the user is already present in another group, use the more + // powerful role name. + userMap[u.id] = userMap[u.id] ? roles.getStrongestRole(userMap[u.id], role) : role; + }); + } + }); + return userMap; + } + + /** + * Five aclRules, each with one group (with the names 'owners', 'editors', 'viewers', + * 'guests', and 'members') are created by default on every new entity (Organization, + * Workspace, Document). These special groups are documented in the _defaultGroups + * constant below. + * + * When a child resource is created under a parent (i.e. when a new Workspace is created + * under an Organization), special groups with a truthy 'nestParent' property are set up + * to include in their memberGroups a single group on initialization - the parent's + * corresponding special group. Special groups with a falsy 'nextParent' property are + * empty on intialization. + * + * NOTE: The groups are ordered from most to least permissive, and should remain that way. + * TODO: app/common/roles already contains an ordering of the default roles. Usage should + * be consolidated. + */ + private readonly _defaultGroups: GroupDescriptor[] = [{ + name: roles.OWNER, + permissions: Permissions.OWNER, + nestParent: true + }, { + name: roles.EDITOR, + permissions: Permissions.EDITOR, + nestParent: true + }, { + name: roles.VIEWER, + permissions: Permissions.VIEW, + nestParent: true + }, { + name: roles.GUEST, + permissions: Permissions.VIEW, + nestParent: false + }, { + name: roles.MEMBER, + permissions: Permissions.VIEW, + nestParent: false, + orgOnly: true + }]; + + /** + * Helper for adjusting acl inheritance rules. Given an array of top-level groups from the + * resource of interest, and an array of inherited groups belonging to the parent resource, + * moves the inherited groups to the group with the destination name or lower, if their + * permission level is lower. If the destination group name is omitted, the groups are + * moved to their original inheritance locations. If the destination group name is null, + * the groups are all removed and there is no access inheritance to this resource. + * Returns the updated array of top-level groups. These returned groups should be saved + * to update the group inheritance in the database. + * + * For all passed-in groups, their .memberGroups will be reset. For + * the basic roles (owner | editor | viewer), these will get updated + * to include inheritedGroups, with roles reduced to dest when dest + * is given. All of the basic roles must be present among + * groups. Any non-basic roles present among inheritedGroups will be + * ignored. + * + * Does not modify inheritedGroups. + */ + public moveInheritedGroups( + groups: NonGuestGroup[], inheritedGroups: Group[], dest?: roles.BasicRole|null + ): void { + // Limit scope to those inheritedGroups that have basic roles (viewers, editors, owners). + inheritedGroups = inheritedGroups.filter(group => roles.isBasicRole(group.name)); + + // NOTE that the special names constant is ordered from least to most permissive. + const reverseDefaultNames = this.defaultBasicGroupNames.reverse(); + + // The destination must be a reserved inheritance group or null. + if (dest && !reverseDefaultNames.includes(dest)) { + throw new Error('moveInheritedGroups called with invalid destination name'); + } + + // Mapping from group names to top-level groups + const topGroups: {[groupName: string]: NonGuestGroup} = {}; + groups.forEach(grp => { + // Note that this has a side effect of initializing the memberGroups arrays. + grp.memberGroups = []; + topGroups[grp.name] = grp; + }); + + // The destFunc maps from an inherited group to its required top-level group name. + const destFunc = (inherited: Group) => + dest === null ? null : reverseDefaultNames.find(sp => sp === inherited.name || sp === dest); + + // Place inherited groups (this has the side-effect of updating member groups) + inheritedGroups.forEach(grp => { + if (!roles.isBasicRole(grp.name)) { + // We filtered out such groups at the start of this method, but just in case... + throw new Error(`${grp.name} is not an inheritable group`); + } + const moveTo = destFunc(grp); + if (moveTo) { + topGroups[moveTo].memberGroups.push(grp); + } + }); + } + + /** + * Update the set of users in a group. TypeORM's .save() method appears to be + * unreliable for a ManyToMany relation with a table with a multi-column primary + * key, so we make the update using explicit deletes and inserts. + */ + public async setGroupUsers(manager: EntityManager, groupId: number, usersBefore: User[], + usersAfter: User[]) { + const userIdsBefore = new Set(usersBefore.map(u => u.id)); + const userIdsAfter = new Set(usersAfter.map(u => u.id)); + const toDelete = [...userIdsBefore].filter(id => !userIdsAfter.has(id)); + const toAdd = [...userIdsAfter].filter(id => !userIdsBefore.has(id)); + if (toDelete.length > 0) { + await manager.createQueryBuilder() + .delete() + .from('group_users') + .whereInIds(toDelete.map(id => ({user_id: id, group_id: groupId}))) + .execute(); + } + if (toAdd.length > 0) { + await manager.createQueryBuilder() + .insert() + // Since we are adding new records in group_users, we may get a duplicate key error if two documents + // are added at the same time (even in transaction, since we are not blocking the whole table). + .orIgnore() + .into('group_users') + .values(toAdd.map(id => ({user_id: id, group_id: groupId}))) + .execute(); + } + } + + /** + * Returns a name to group mapping for the standard groups. Useful when adding a new child + * entity. Finds and includes the correct parent groups as member groups. + */ + public createGroups(inherit?: Organization|Workspace, ownerId?: number): {[name: string]: Group} { + const groupMap: {[name: string]: Group} = {}; + this.defaultGroups.forEach(groupProps => { + if (!groupProps.orgOnly || !inherit) { + // Skip this group if it's an org only group and the resource inherits from a parent. + const group = new Group(); + group.name = groupProps.name; + if (inherit) { + this.setInheritance(group, inherit); + } + groupMap[groupProps.name] = group; + } + }); + // Add the owner explicitly to the owner group. + if (ownerId) { + const ownerGroup = groupMap[roles.OWNER]; + const user = new User(); + user.id = ownerId; + ownerGroup.memberUsers = [user]; + } + return groupMap; + } + + // Sets the given group to inherit the groups in the given parent resource. + public setInheritance(group: Group, parent: Organization|Workspace) { + // Add the parent groups to the group + const groupProps = this.defaultGroups.find(special => special.name === group.name); + if (!groupProps) { + throw new Error(`Non-standard group passed to _addInheritance: ${group.name}`); + } + if (groupProps.nestParent) { + const parentGroups = (parent.aclRules as AclRule[]).map((_aclRule: AclRule) => _aclRule.group); + const inheritGroup = parentGroups.find((_parentGroup: Group) => _parentGroup.name === group.name); + if (!inheritGroup) { + throw new Error(`Special group ${group.name} not found in ${parent.name} for inheritance`); + } + group.memberGroups = [inheritGroup]; + } + } + + // Returns the most permissive default role that does not have more permissions than the passed + // in argument. + public getRoleFromPermissions(permissions: number): roles.Role|null { + permissions &= ~Permissions.PUBLIC; // tslint:disable-line:no-bitwise + const group = this.defaultBasicGroups.find(grp => + (permissions & grp.permissions) === grp.permissions); // tslint:disable-line:no-bitwise + return group ? group.name : null; + } + + // Returns the maxInheritedRole group name set on a resource. + // The resource's aclRules, groups, and memberGroups must be populated. + public getMaxInheritedRole(res: Workspace|Document): roles.BasicRole|null { + const groups = (res.aclRules as AclRule[]).map((_aclRule: AclRule) => _aclRule.group); + let maxInheritedRole: roles.NonGuestRole|null = null; + for (const name of this.defaultBasicGroupNames) { + const group = groups.find(_grp => _grp.name === name); + if (!group) { + throw new Error(`Error in _getMaxInheritedRole: group ${name} not found in ${res.name}`); + } + if (group.memberGroups.length > 0) { + maxInheritedRole = name; + break; + } + } + return roles.getEffectiveRole(maxInheritedRole); + } +} diff --git a/app/gen-server/lib/homedb/HomeDBManager.ts b/app/gen-server/lib/homedb/HomeDBManager.ts index 721b18633f..d4593ebda4 100644 --- a/app/gen-server/lib/homedb/HomeDBManager.ts +++ b/app/gen-server/lib/homedb/HomeDBManager.ts @@ -46,6 +46,7 @@ import { AvailableUsers, DocumentAccessChanges, GetUserOptions, + GroupDescriptor, NonGuestGroup, OrgAccessChanges, PreviousAndCurrent, @@ -87,6 +88,7 @@ import { WhereExpressionBuilder } from "typeorm"; import {v4 as uuidv4} from "uuid"; +import { GroupsManager } from './GroupsManager'; // Support transactions in Sqlite in async code. This is a monkey patch, affecting // the prototypes of various TypeORM classes. @@ -155,13 +157,6 @@ interface QueryOptions { // potentially overriding markPermissions. } -interface GroupDescriptor { - readonly name: roles.Role; - readonly permissions: number; - readonly nestParent: boolean; - readonly orgOnly?: boolean; -} - // Information about a change in billable users. export interface UserChange { userId: number; // who initiated the change @@ -258,6 +253,7 @@ export type BillingOptions = Partial _grpDesc.nestParent); + return this._groupsManager.defaultBasicGroups; } - // Groups that are common to all resources. public get defaultCommonGroups(): GroupDescriptor[] { - return this._defaultGroups - .filter(_grpDesc => !_grpDesc.orgOnly); + return this._groupsManager.defaultCommonGroups; } public get defaultGroupNames(): roles.Role[] { - return this._defaultGroups.map(_grpDesc => _grpDesc.name); + return this._groupsManager.defaultGroupNames; } public get defaultBasicGroupNames(): roles.BasicRole[] { - return this.defaultBasicGroups - .map(_grpDesc => _grpDesc.name) as roles.BasicRole[]; + return this._groupsManager.defaultBasicGroupNames; } public get defaultNonGuestGroupNames(): roles.NonGuestRole[] { - return this._defaultGroups - .filter(_grpDesc => _grpDesc.name !== roles.GUEST) - .map(_grpDesc => _grpDesc.name) as roles.NonGuestRole[]; + return this._groupsManager.defaultNonGuestGroupNames; } public get defaultCommonGroupNames(): roles.NonMemberRole[] { @@ -1221,7 +1170,7 @@ export class HomeDBManager extends EventEmitter { org.owner = user; } // Create the special initial permission groups for the new org. - const groupMap = this._createGroups(); + const groupMap = this._groupsManager.createGroups(); org.aclRules = this.defaultGroups.map(_grpDesc => { // Get the special group with the name needed for this ACL Rule const group = groupMap[_grpDesc.name]; @@ -1629,7 +1578,7 @@ export class HomeDBManager extends EventEmitter { doc.workspace = workspace; doc.createdBy = scope.userId; // Create the special initial permission groups for the new workspace. - const groupMap = this._createGroups(workspace, scope.userId); + const groupMap = this._groupsManager.createGroups(workspace, scope.userId); doc.aclRules = this.defaultCommonGroups.map(_grpDesc => { // Get the special group with the name needed for this ACL Rule const group = groupMap[_grpDesc.name]; @@ -2190,7 +2139,7 @@ export class HomeDBManager extends EventEmitter { return queryResult; } const org: Organization = queryResult.data; - const userRoleMap = getMemberUserRoles(org, this.defaultGroupNames); + const userRoleMap = GroupsManager.getMemberUserRoles(org, this.defaultGroupNames); const users = UsersManager.getResourceUsers(org).filter(u => userRoleMap[u.id]).map(u => { const access = userRoleMap[u.id]; return { @@ -2239,13 +2188,13 @@ export class HomeDBManager extends EventEmitter { return queryFailure; } - const wsMap = getMemberUserRoles(workspace, this.defaultCommonGroupNames); + const wsMap = GroupsManager.getMemberUserRoles(workspace, this.defaultCommonGroupNames); // Also fetch the organization ACLs so we can determine inherited rights. // The orgMap gives the org access inherited by each user. - const orgMap = getMemberUserRoles(org, this.defaultBasicGroupNames); - const orgMapWithMembership = getMemberUserRoles(org, this.defaultGroupNames); + const orgMap = GroupsManager.getMemberUserRoles(org, this.defaultBasicGroupNames); + const orgMapWithMembership = GroupsManager.getMemberUserRoles(org, this.defaultGroupNames); // Iterate through the org since all users will be in the org. const users: UserAccessData[] = UsersManager.getResourceUsers([workspace, org]).map(u => { @@ -2258,7 +2207,7 @@ export class HomeDBManager extends EventEmitter { isMember: orgAccess && orgAccess !== 'guests', }; }); - const maxInheritedRole = this._getMaxInheritedRole(workspace); + const maxInheritedRole = this._groupsManager.getMaxInheritedRole(workspace); const personal = this._filterAccessData(scope, users, maxInheritedRole); return { status: 200, @@ -2297,16 +2246,16 @@ export class HomeDBManager extends EventEmitter { const {trunkId, forkId, forkUserId, snapshotId} = parseUrlId(scope.urlId); const doc = await this._loadDocAccess({...scope, urlId: trunkId}, Permissions.VIEW); - const docMap = getMemberUserRoles(doc, this.defaultCommonGroupNames); + const docMap = GroupsManager.getMemberUserRoles(doc, this.defaultCommonGroupNames); // The wsMap gives the ws access inherited by each user. - const wsMap = getMemberUserRoles(doc.workspace, this.defaultBasicGroupNames); + const wsMap = GroupsManager.getMemberUserRoles(doc.workspace, this.defaultBasicGroupNames); // The orgMap gives the org access inherited by each user. - const orgMap = getMemberUserRoles(doc.workspace.org, this.defaultBasicGroupNames); + const orgMap = GroupsManager.getMemberUserRoles(doc.workspace.org, this.defaultBasicGroupNames); // The orgMapWithMembership gives the full access to the org for each user, including // the "members" level, which grants no default inheritable access but allows the user // to be added freely to workspaces and documents. - const orgMapWithMembership = getMemberUserRoles(doc.workspace.org, this.defaultGroupNames); - const wsMaxInheritedRole = this._getMaxInheritedRole(doc.workspace); + const orgMapWithMembership = GroupsManager.getMemberUserRoles(doc.workspace.org, this.defaultGroupNames); + const wsMaxInheritedRole = this._groupsManager.getMaxInheritedRole(doc.workspace); // Iterate through the org since all users will be in the org. let users: UserAccessData[] = UsersManager.getResourceUsers([doc, doc.workspace, doc.workspace.org]).map(u => { // Merge the strongest roles from the resource and parent resources. Note that the parent @@ -2324,7 +2273,7 @@ export class HomeDBManager extends EventEmitter { isSupport: u.id === this._usersManager.getSupportUserId() ? true : undefined, }; }); - let maxInheritedRole = this._getMaxInheritedRole(doc); + let maxInheritedRole = this._groupsManager.getMaxInheritedRole(doc); if (options?.excludeUsersWithoutAccess) { users = users.filter(user => { @@ -2447,7 +2396,7 @@ export class HomeDBManager extends EventEmitter { // Update the doc groups to inherit the groups in the new workspace/org. // Any previously custom added members remain in the doc groups. doc.aclRules.forEach(aclRule => { - this._setInheritance(aclRule.group, workspace); + this._groupsManager.setInheritance(aclRule.group, workspace); }); // If the org is changing, remove all urlIds for this doc, since there could be // conflicts in the new org. @@ -3360,7 +3309,7 @@ export class HomeDBManager extends EventEmitter { .leftJoinAndSelect('doc_groups.memberUsers', 'doc_users') .andWhere('doc_users.id is not null'); const wsWithDocs = await wsWithDocsQuery.getOne(); - await this._setGroupUsers(manager, wsGuestGroup.id, wsGuestGroup.memberUsers, + await this._groupsManager.setGroupUsers(manager, wsGuestGroup.id, wsGuestGroup.memberUsers, this._usersManager.filterEveryone( UsersManager.getResourceUsers(wsWithDocs?.docs || []) ) @@ -3394,41 +3343,11 @@ export class HomeDBManager extends EventEmitter { throw new Error(`_repairOrgGuests error: found ${orgGroups.length} ${roles.GUEST} ACL group(s)`); } const orgGuestGroup = orgGroups[0]!; - await this._setGroupUsers(manager, orgGuestGroup.id, orgGuestGroup.memberUsers, + await this._groupsManager.setGroupUsers(manager, orgGuestGroup.id, orgGuestGroup.memberUsers, this._usersManager.filterEveryone(UsersManager.getResourceUsers(org.workspaces))); }); } - /** - * Update the set of users in a group. TypeORM's .save() method appears to be - * unreliable for a ManyToMany relation with a table with a multi-column primary - * key, so we make the update using explicit deletes and inserts. - */ - private async _setGroupUsers(manager: EntityManager, groupId: number, usersBefore: User[], - usersAfter: User[]) { - const userIdsBefore = new Set(usersBefore.map(u => u.id)); - const userIdsAfter = new Set(usersAfter.map(u => u.id)); - const toDelete = [...userIdsBefore].filter(id => !userIdsAfter.has(id)); - const toAdd = [...userIdsAfter].filter(id => !userIdsBefore.has(id)); - if (toDelete.length > 0) { - await manager.createQueryBuilder() - .delete() - .from('group_users') - .whereInIds(toDelete.map(id => ({user_id: id, group_id: groupId}))) - .execute(); - } - if (toAdd.length > 0) { - await manager.createQueryBuilder() - .insert() - // Since we are adding new records in group_users, we may get a duplicate key error if two documents - // are added at the same time (even in transaction, since we are not blocking the whole table). - .orIgnore() - .into('group_users') - .values(toAdd.map(id => ({user_id: id, group_id: groupId}))) - .execute(); - } - } - /** * Creates, initializes and saves a workspace in the given org with the given properties. * Product limits on number of workspaces allowed in org are not checked. @@ -3446,7 +3365,7 @@ export class HomeDBManager extends EventEmitter { workspace.org = org; // Create the special initial permission groups for the new workspace. // Optionally add the owner to the workspace. - const groupMap = this._createGroups(org, ownerId); + const groupMap = this._groupsManager.createGroups(org, ownerId); workspace.aclRules = this.defaultCommonGroups.map(_grpDesc => { // Get the special group with the name needed for this ACL Rule const group = groupMap[_grpDesc.name]; @@ -3985,50 +3904,6 @@ export class HomeDBManager extends EventEmitter { }); } - /** - * Returns a name to group mapping for the standard groups. Useful when adding a new child - * entity. Finds and includes the correct parent groups as member groups. - */ - private _createGroups(inherit?: Organization|Workspace, ownerId?: number): {[name: string]: Group} { - const groupMap: {[name: string]: Group} = {}; - this.defaultGroups.forEach(groupProps => { - if (!groupProps.orgOnly || !inherit) { - // Skip this group if it's an org only group and the resource inherits from a parent. - const group = new Group(); - group.name = groupProps.name; - if (inherit) { - this._setInheritance(group, inherit); - } - groupMap[groupProps.name] = group; - } - }); - // Add the owner explicitly to the owner group. - if (ownerId) { - const ownerGroup = groupMap[roles.OWNER]; - const user = new User(); - user.id = ownerId; - ownerGroup.memberUsers = [user]; - } - return groupMap; - } - - // Sets the given group to inherit the groups in the given parent resource. - private _setInheritance(group: Group, parent: Organization|Workspace) { - // Add the parent groups to the group - const groupProps = this.defaultGroups.find(special => special.name === group.name); - if (!groupProps) { - throw new Error(`Non-standard group passed to _addInheritance: ${group.name}`); - } - if (groupProps.nestParent) { - const parentGroups = (parent.aclRules as AclRule[]).map((_aclRule: AclRule) => _aclRule.group); - const inheritGroup = parentGroups.find((_parentGroup: Group) => _parentGroup.name === group.name); - if (!inheritGroup) { - throw new Error(`Special group ${group.name} not found in ${parent.name} for inheritance`); - } - group.memberGroups = [inheritGroup]; - } - } - // Return a QueryResult reflecting the output of a query builder. // If a rawQueryBuilder is supplied, it is used to make the query, // but then the original queryBuilder is used to interpret the results @@ -4183,7 +4058,7 @@ export class HomeDBManager extends EventEmitter { } if (typeof subValue === 'number' || !subValue) { // Find the first special group for which the user has all permissions. - value.access = this._getRoleFromPermissions(subValue || 0); + value.access = this._groupsManager.getRoleFromPermissions(subValue || 0); if (subValue & Permissions.PUBLIC) { // tslint:disable-line:no-bitwise value.public = true; } @@ -4191,7 +4066,7 @@ export class HomeDBManager extends EventEmitter { // Resource may be accessed by multiple users, encoded in JSON. const accessOptions: AccessOption[] = readJson(this._dbType, subValue); value.accessOptions = accessOptions.map(option => ({ - access: this._getRoleFromPermissions(option.perms), ...option + access: this._groupsManager.getRoleFromPermissions(option.perms), ...option })); } delete value.permissions; // permissions is not specified in the api, so we drop it. @@ -4217,33 +4092,6 @@ export class HomeDBManager extends EventEmitter { return entity.accessOptions.length === 0; } - // Returns the most permissive default role that does not have more permissions than the passed - // in argument. - private _getRoleFromPermissions(permissions: number): roles.Role|null { - permissions &= ~Permissions.PUBLIC; // tslint:disable-line:no-bitwise - const group = this.defaultBasicGroups.find(grp => - (permissions & grp.permissions) === grp.permissions); // tslint:disable-line:no-bitwise - return group ? group.name : null; - } - - // Returns the maxInheritedRole group name set on a resource. - // The resource's aclRules, groups, and memberGroups must be populated. - private _getMaxInheritedRole(res: Workspace|Document): roles.BasicRole|null { - const groups = (res.aclRules as AclRule[]).map((_aclRule: AclRule) => _aclRule.group); - let maxInheritedRole: roles.NonGuestRole|null = null; - for (const name of this.defaultBasicGroupNames) { - const group = groups.find(_grp => _grp.name === name); - if (!group) { - throw new Error(`Error in _getMaxInheritedRole: group ${name} not found in ${res.name}`); - } - if (group.memberGroups.length > 0) { - maxInheritedRole = name; - break; - } - } - return roles.getEffectiveRole(maxInheritedRole); - } - /** * Return a query builder to check if we have access to the given resource. * Tests the given permission-level access, defaulting to view permission. @@ -4803,27 +4651,6 @@ async function verifyEntity( }; } -// Returns a map of userIds to the user's strongest default role on the given resource. -// The resource's aclRules, groups, and memberUsers must be populated. -function getMemberUserRoles(res: Resource, allowRoles: T[]): {[userId: string]: T} { - // Add the users to a map to ensure uniqueness. (A user may be present in - // more than one group) - const userMap: {[userId: string]: T} = {}; - (res.aclRules as AclRule[]).forEach((aclRule: AclRule) => { - const role = aclRule.group.name as T; - if (allowRoles.includes(role)) { - // Map the users to remove sensitive information from the result and - // to add the group names. - aclRule.group.memberUsers.forEach((u: User) => { - // If the user is already present in another group, use the more - // powerful role name. - userMap[u.id] = userMap[u.id] ? roles.getStrongestRole(userMap[u.id], role) : role; - }); - } - }); - return userMap; -} - // Extract a human-readable name for the type of entity being selected. function getFrom(queryBuilder: SelectQueryBuilder): string { const alias = queryBuilder.expressionMap.mainAlias; diff --git a/app/gen-server/lib/homedb/Interfaces.ts b/app/gen-server/lib/homedb/Interfaces.ts index 2bb65a57fe..bb125f9b4b 100644 --- a/app/gen-server/lib/homedb/Interfaces.ts +++ b/app/gen-server/lib/homedb/Interfaces.ts @@ -61,6 +61,13 @@ export interface OrgAccessChanges { accessChanges: Omit; } +export interface GroupDescriptor { + readonly name: roles.Role; + readonly permissions: number; + readonly nestParent: boolean; + readonly orgOnly?: boolean; +} + interface AccessChanges { publicAccess: roles.NonGuestRole | null; maxInheritedAccess: roles.BasicRole | null; @@ -70,3 +77,4 @@ interface AccessChanges { } >; } +