diff --git a/api/server/services/PermissionService.js b/api/server/services/PermissionService.js index 4295cd375f..4b475ab300 100644 --- a/api/server/services/PermissionService.js +++ b/api/server/services/PermissionService.js @@ -13,6 +13,10 @@ const { createUser, updateUser, findUser, + grantPermission: grantPermissionACL, + findAccessibleResources: findAccessibleResourcesACL, + hasPermission, + getEffectivePermissions: getEffectivePermissionsACL, } = require('~/models'); const { AclEntry, AccessRole, Group } = require('~/db/models'); @@ -72,34 +76,15 @@ const grantPermission = async ({ `Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}`, ); } - - const query = { + return await grantPermissionACL( principalType, + principalId, resourceType, resourceId, - }; - - if (principalType !== 'public') { - query.principalId = principalId; - query.principalModel = principalType === 'user' ? 'User' : 'Group'; - } - - const update = { - $set: { - permBits: role.permBits, - roleId: role._id, - grantedBy, - grantedAt: new Date(), - }, - }; - - const options = { - upsert: true, - new: true, - ...(session ? { session } : {}), - }; - - return await AclEntry.findOneAndUpdate(query, update, options); + role.permBits, + grantedBy, + session, + ); } catch (error) { logger.error(`[PermissionService.grantPermission] Error: ${error.message}`); throw error; @@ -128,18 +113,7 @@ const checkPermission = async ({ userId, resourceType, resourceId, requiredPermi return false; } - // Find any ACL entry matching the principals, resource, and check if it has all required permission bits - const entry = await AclEntry.findOne({ - $or: principals.map((p) => ({ - principalType: p.principalType, - ...(p.principalType !== 'public' && { principalId: p.principalId }), - })), - resourceType, - resourceId, - permBits: { $bitsAllSet: requiredPermission }, - }).lean(); - - return !!entry; + return await hasPermission(principals, resourceType, resourceId, requiredPermission); } catch (error) { logger.error(`[PermissionService.checkPermission] Error: ${error.message}`); // Re-throw validation errors @@ -166,27 +140,7 @@ const getEffectivePermissions = async ({ userId, resourceType, resourceId }) => if (principals.length === 0) { return 0; } - - // Find all matching ACL entries - const aclEntries = await AclEntry.find({ - $or: principals.map((p) => ({ - principalType: p.principalType, - ...(p.principalType !== 'public' && { principalId: p.principalId }), - })), - resourceType, - resourceId, - }).lean(); - - if (aclEntries.length === 0) { - return 0; - } - - let effectiveBits = 0; - for (const entry of aclEntries) { - effectiveBits |= entry.permBits; - } - - return effectiveBits; + return await getEffectivePermissionsACL(principals, resourceType, resourceId); } catch (error) { logger.error(`[PermissionService.getEffectivePermissions] Error: ${error.message}`); return 0; @@ -208,23 +162,12 @@ const findAccessibleResources = async ({ userId, resourceType, requiredPermissio } // Get all principals for the user (user + groups + public) - const principals = await getUserPrincipals(userId); + const principalsList = await getUserPrincipals(userId); - if (principals.length === 0) { + if (principalsList.length === 0) { return []; } - - // Find all matching ACL entries where user has at least the required permission bits - const entries = await AclEntry.find({ - $or: principals.map((p) => ({ - principalType: p.principalType, - ...(p.principalType !== 'public' && { principalId: p.principalId }), - })), - resourceType, - permBits: { $bitsAllSet: requiredPermissions }, - }).distinct('resourceId'); - - return entries; + return await findAccessibleResourcesACL(principalsList, resourceType, requiredPermissions); } catch (error) { logger.error(`[PermissionService.findAccessibleResources] Error: ${error.message}`); // Re-throw validation errors diff --git a/packages/data-schemas/src/methods/aclEntry.ts b/packages/data-schemas/src/methods/aclEntry.ts index 1531997205..9d93d0fefc 100644 --- a/packages/data-schemas/src/methods/aclEntry.ts +++ b/packages/data-schemas/src/methods/aclEntry.ts @@ -85,7 +85,7 @@ export function createAclEntryMethods(mongoose: typeof import('mongoose')) { $or: principalsQuery, resourceType, resourceId, - permBits: { $bitsAnySet: permissionBit }, + permBits: { $bitsAllSet: permissionBit }, }).lean(); return !!entry; @@ -96,22 +96,13 @@ export function createAclEntryMethods(mongoose: typeof import('mongoose')) { * @param principalsList - List of principals, each containing { principalType, principalId } * @param resourceType - The type of resource * @param resourceId - The ID of the resource - * @returns Object with effectiveBits (combined permissions) and sources (individual entries) + * @returns {Promise} Effective permission bitmask */ async function getEffectivePermissions( principalsList: Array<{ principalType: string; principalId?: string | Types.ObjectId }>, resourceType: string, resourceId: string | Types.ObjectId, - ): Promise<{ - effectiveBits: number; - sources: Array<{ - from: string; - principalId?: Types.ObjectId; - permBits: number; - direct: boolean; - inheritedFrom?: Types.ObjectId; - }>; - }> { + ): Promise { const aclEntries = await findEntriesByPrincipalsAndResource( principalsList, resourceType, @@ -119,18 +110,10 @@ export function createAclEntryMethods(mongoose: typeof import('mongoose')) { ); let effectiveBits = 0; - const sources = aclEntries.map((entry) => { + for (const entry of aclEntries) { effectiveBits |= entry.permBits; - return { - from: entry.principalType, - principalId: entry.principalId, - permBits: entry.permBits, - direct: !entry.inheritedFrom, - inheritedFrom: entry.inheritedFrom, - }; - }); - - return { effectiveBits, sources }; + } + return effectiveBits; } /** @@ -286,7 +269,7 @@ export function createAclEntryMethods(mongoose: typeof import('mongoose')) { const entries = await AclEntry.find({ $or: principalsQuery, resourceType, - permBits: { $bitsAnySet: requiredPermBit }, + permBits: { $bitsAllSet: requiredPermBit }, }).distinct('resourceId'); return entries;