| | const mongoose = require('mongoose'); |
| | const { RoleBits, createModels } = require('@librechat/data-schemas'); |
| | const { MongoMemoryServer } = require('mongodb-memory-server'); |
| | const { |
| | ResourceType, |
| | AccessRoleIds, |
| | PrincipalType, |
| | PrincipalModel, |
| | } = require('librechat-data-provider'); |
| | const { |
| | bulkUpdateResourcePermissions, |
| | getEffectivePermissions, |
| | findAccessibleResources, |
| | getAvailableRoles, |
| | grantPermission, |
| | checkPermission, |
| | } = require('./PermissionService'); |
| | const { findRoleByIdentifier, getUserPrincipals, seedDefaultRoles } = require('~/models'); |
| |
|
| | |
| | jest.mock('@librechat/data-schemas', () => ({ |
| | ...jest.requireActual('@librechat/data-schemas'), |
| | getTransactionSupport: jest.fn().mockResolvedValue(false), |
| | createModels: jest.requireActual('@librechat/data-schemas').createModels, |
| | })); |
| |
|
| | |
| | jest.mock('~/server/services/GraphApiService', () => ({ |
| | getGroupMembers: jest.fn().mockResolvedValue([]), |
| | })); |
| |
|
| | |
| | jest.mock('~/config', () => ({ |
| | logger: { |
| | error: jest.fn(), |
| | }, |
| | })); |
| |
|
| | let mongoServer; |
| | let AclEntry; |
| |
|
| | beforeAll(async () => { |
| | mongoServer = await MongoMemoryServer.create(); |
| | const mongoUri = mongoServer.getUri(); |
| | await mongoose.connect(mongoUri); |
| |
|
| | |
| | createModels(mongoose); |
| |
|
| | |
| | const dbModels = require('~/db/models'); |
| | Object.assign(mongoose.models, dbModels); |
| |
|
| | AclEntry = dbModels.AclEntry; |
| |
|
| | |
| | await seedDefaultRoles(); |
| | }); |
| |
|
| | afterAll(async () => { |
| | await mongoose.disconnect(); |
| | await mongoServer.stop(); |
| | }); |
| |
|
| | beforeEach(async () => { |
| | |
| | await AclEntry.deleteMany({}); |
| | }); |
| |
|
| | |
| | jest.mock('~/models', () => ({ |
| | ...jest.requireActual('~/models'), |
| | getUserPrincipals: jest.fn(), |
| | })); |
| |
|
| | describe('PermissionService', () => { |
| | |
| | const userId = new mongoose.Types.ObjectId(); |
| | const groupId = new mongoose.Types.ObjectId(); |
| | const resourceId = new mongoose.Types.ObjectId(); |
| | const grantedById = new mongoose.Types.ObjectId(); |
| | const roleResourceId = new mongoose.Types.ObjectId(); |
| |
|
| | describe('grantPermission', () => { |
| | test('should grant permission to a user with a role', async () => { |
| | const entry = await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(entry).toBeDefined(); |
| | expect(entry.principalType).toBe(PrincipalType.USER); |
| | expect(entry.principalId.toString()).toBe(userId.toString()); |
| | expect(entry.principalModel).toBe(PrincipalModel.USER); |
| | expect(entry.resourceType).toBe(ResourceType.AGENT); |
| | expect(entry.resourceId.toString()).toBe(resourceId.toString()); |
| |
|
| | |
| | const role = await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER); |
| | expect(entry.permBits).toBe(role.permBits); |
| | expect(entry.roleId.toString()).toBe(role._id.toString()); |
| | expect(entry.grantedBy.toString()).toBe(grantedById.toString()); |
| | expect(entry.grantedAt).toBeInstanceOf(Date); |
| | }); |
| |
|
| | test('should grant permission to a group with a role', async () => { |
| | const entry = await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: groupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(entry).toBeDefined(); |
| | expect(entry.principalType).toBe(PrincipalType.GROUP); |
| | expect(entry.principalId.toString()).toBe(groupId.toString()); |
| | expect(entry.principalModel).toBe(PrincipalModel.GROUP); |
| |
|
| | |
| | const role = await findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR); |
| | expect(entry.permBits).toBe(role.permBits); |
| | expect(entry.roleId.toString()).toBe(role._id.toString()); |
| | }); |
| |
|
| | test('should grant public permission with a role', async () => { |
| | const entry = await grantPermission({ |
| | principalType: PrincipalType.PUBLIC, |
| | principalId: null, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(entry).toBeDefined(); |
| | expect(entry.principalType).toBe(PrincipalType.PUBLIC); |
| | expect(entry.principalId).toBeUndefined(); |
| | expect(entry.principalModel).toBeUndefined(); |
| |
|
| | |
| | const role = await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER); |
| | expect(entry.permBits).toBe(role.permBits); |
| | expect(entry.roleId.toString()).toBe(role._id.toString()); |
| | }); |
| |
|
| | test('should throw error for invalid principal type', async () => { |
| | await expect( |
| | grantPermission({ |
| | principalType: 'invalid', |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }), |
| | ).rejects.toThrow('Invalid principal type: invalid'); |
| | }); |
| |
|
| | test('should throw error for missing principalId with user type', async () => { |
| | await expect( |
| | grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: null, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }), |
| | ).rejects.toThrow('Principal ID is required for user, group, and role principals'); |
| | }); |
| |
|
| | test('should throw error for non-existent role', async () => { |
| | await expect( |
| | grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: 'non_existent_role', |
| | grantedBy: grantedById, |
| | }), |
| | ).rejects.toThrow('Role non_existent_role not found'); |
| | }); |
| |
|
| | test('should throw error for role-resource type mismatch', async () => { |
| | await expect( |
| | grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, |
| | grantedBy: grantedById, |
| | }), |
| | ).rejects.toThrow('Role promptGroup_viewer is for promptGroup resources, not agent'); |
| | }); |
| |
|
| | test('should update existing permission when granting to same principal and resource', async () => { |
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | const updated = await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | const editorRole = await findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR); |
| | expect(updated.permBits).toBe(editorRole.permBits); |
| | expect(updated.roleId.toString()).toBe(editorRole._id.toString()); |
| |
|
| | |
| | const entries = await AclEntry.find({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }); |
| | expect(entries).toHaveLength(1); |
| | }); |
| | }); |
| |
|
| | describe('checkPermission', () => { |
| | let otherResourceId; |
| |
|
| | beforeEach(async () => { |
| | |
| | getUserPrincipals.mockReset(); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | otherResourceId = new mongoose.Types.ObjectId(); |
| | await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: groupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: otherResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| | }); |
| |
|
| | test('should check permission for user principal', async () => { |
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | ]); |
| |
|
| | const hasViewPermission = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasViewPermission).toBe(true); |
| |
|
| | |
| | const hasEditPermission = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | requiredPermission: 3, |
| | }); |
| |
|
| | expect(hasEditPermission).toBe(false); |
| | }); |
| |
|
| | test('should check permission for user and group principals', async () => { |
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | { principalType: PrincipalType.GROUP, principalId: groupId }, |
| | ]); |
| |
|
| | |
| | const hasViewOnOriginal = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasViewOnOriginal).toBe(true); |
| |
|
| | |
| | const hasViewOnOther = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: otherResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | |
| | expect(hasViewOnOther).toBe(true); |
| | }); |
| |
|
| | test('should check permission for public access', async () => { |
| | const publicResourceId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.PUBLIC, |
| | principalId: null, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: publicResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | { principalType: PrincipalType.GROUP, principalId: groupId }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const hasPublicAccess = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: publicResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasPublicAccess).toBe(true); |
| | }); |
| |
|
| | test('should return false for invalid permission bits', async () => { |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | ]); |
| |
|
| | await expect( |
| | checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | requiredPermission: 'invalid', |
| | }), |
| | ).rejects.toThrow('requiredPermission must be a positive number'); |
| |
|
| | const nonExistentResource = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: new mongoose.Types.ObjectId(), |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(nonExistentResource).toBe(false); |
| | }); |
| |
|
| | test('should return false if user has no principals', async () => { |
| | getUserPrincipals.mockResolvedValue([]); |
| |
|
| | const hasPermission = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasPermission).toBe(false); |
| | }); |
| | }); |
| |
|
| | describe('getEffectivePermissions', () => { |
| | beforeEach(async () => { |
| | |
| | getUserPrincipals.mockReset(); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: groupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | const publicResourceId = new mongoose.Types.ObjectId(); |
| | await grantPermission({ |
| | principalType: PrincipalType.PUBLIC, |
| | principalId: null, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: publicResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | const parentResourceId = new mongoose.Types.ObjectId(); |
| | const childResourceId = new mongoose.Types.ObjectId(); |
| |
|
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.PROMPTGROUP, |
| | resourceId: parentResourceId, |
| | accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | await AclEntry.create({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | principalModel: PrincipalModel.USER, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: childResourceId, |
| | permBits: RoleBits.VIEWER, |
| | roleId: (await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER))._id, |
| | grantedBy: grantedById, |
| | grantedAt: new Date(), |
| | inheritedFrom: parentResourceId, |
| | }); |
| | }); |
| |
|
| | test('should get effective permissions from multiple sources', async () => { |
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | { principalType: PrincipalType.GROUP, principalId: groupId }, |
| | ]); |
| |
|
| | const effective = await getEffectivePermissions({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }); |
| |
|
| | |
| | |
| | expect(effective).toBe(RoleBits.EDITOR); |
| | }); |
| |
|
| | test('should get effective permissions from inherited permissions', async () => { |
| | |
| | const inheritedEntry = await AclEntry.findOne({ inheritedFrom: { $exists: true } }); |
| | const childResourceId = inheritedEntry.resourceId; |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | ]); |
| |
|
| | const effective = await getEffectivePermissions({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: childResourceId, |
| | }); |
| |
|
| | |
| | expect(effective).toBe(RoleBits.VIEWER); |
| | }); |
| |
|
| | test('should return 0 for non-existent permissions', async () => { |
| | getUserPrincipals.mockResolvedValue([{ principalType: 'user', principalId: userId }]); |
| |
|
| | const nonExistentResource = new mongoose.Types.ObjectId(); |
| | const effective = await getEffectivePermissions({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: nonExistentResource, |
| | }); |
| |
|
| | |
| | expect(effective).toBe(0); |
| | }); |
| |
|
| | test('should return 0 if user has no principals', async () => { |
| | getUserPrincipals.mockResolvedValue([]); |
| |
|
| | const effective = await getEffectivePermissions({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }); |
| |
|
| | |
| | expect(effective).toBe(0); |
| | }); |
| | }); |
| |
|
| | describe('findAccessibleResources', () => { |
| | beforeEach(async () => { |
| | |
| | getUserPrincipals.mockReset(); |
| |
|
| | |
| | const resource1 = new mongoose.Types.ObjectId(); |
| | const resource2 = new mongoose.Types.ObjectId(); |
| | const resource3 = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: resource1, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: resource2, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: groupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: resource3, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| | }); |
| |
|
| | test('should find resources user can view', async () => { |
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | ]); |
| |
|
| | const viewableResources = await findAccessibleResources({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | requiredPermissions: 1, |
| | }); |
| |
|
| | |
| | expect(viewableResources).toHaveLength(2); |
| | }); |
| |
|
| | test('should find resources user can edit', async () => { |
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | ]); |
| |
|
| | const editableResources = await findAccessibleResources({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | requiredPermissions: 3, |
| | }); |
| |
|
| | |
| | expect(editableResources).toHaveLength(1); |
| | }); |
| |
|
| | test('should find resources accessible via group membership', async () => { |
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | { principalType: PrincipalType.GROUP, principalId: groupId }, |
| | ]); |
| |
|
| | const viewableResources = await findAccessibleResources({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | requiredPermissions: 1, |
| | }); |
| |
|
| | |
| | expect(viewableResources).toHaveLength(3); |
| | }); |
| |
|
| | test('should return empty array for invalid permissions', async () => { |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | ]); |
| |
|
| | await expect( |
| | findAccessibleResources({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | requiredPermissions: 'invalid', |
| | }), |
| | ).rejects.toThrow('requiredPermissions must be a positive number'); |
| |
|
| | const nonExistentType = await findAccessibleResources({ |
| | userId, |
| | resourceType: 'non_existent_type', |
| | requiredPermissions: 1, |
| | }); |
| |
|
| | expect(nonExistentType).toEqual([]); |
| | }); |
| |
|
| | test('should return empty array if user has no principals', async () => { |
| | getUserPrincipals.mockResolvedValue([]); |
| |
|
| | const resources = await findAccessibleResources({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | requiredPermissions: 1, |
| | }); |
| |
|
| | expect(resources).toEqual([]); |
| | }); |
| | }); |
| |
|
| | describe('getAvailableRoles', () => { |
| | test('should get all roles for a resource type', async () => { |
| | const roles = await getAvailableRoles({ |
| | resourceType: ResourceType.AGENT, |
| | }); |
| |
|
| | expect(roles).toHaveLength(3); |
| | expect(roles.map((r) => r.accessRoleId).sort()).toEqual( |
| | [AccessRoleIds.AGENT_EDITOR, AccessRoleIds.AGENT_OWNER, AccessRoleIds.AGENT_VIEWER].sort(), |
| | ); |
| | }); |
| |
|
| | test('should throw error for non-existent resource type', async () => { |
| | await expect( |
| | getAvailableRoles({ |
| | resourceType: 'non_existent_type', |
| | }), |
| | ).rejects.toThrow('Invalid resourceType: non_existent_type. Valid types: agent, promptGroup'); |
| | }); |
| | }); |
| |
|
| | describe('bulkUpdateResourcePermissions', () => { |
| | const otherUserId = new mongoose.Types.ObjectId(); |
| |
|
| | beforeEach(async () => { |
| | |
| | await seedDefaultRoles(); |
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: groupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | await grantPermission({ |
| | principalType: PrincipalType.PUBLIC, |
| | principalId: null, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| | }); |
| |
|
| | test('should grant new permissions in bulk', async () => { |
| | const newResourceId = new mongoose.Types.ObjectId(); |
| | const updatedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: userId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | }, |
| | { |
| | type: PrincipalType.USER, |
| | id: otherUserId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | }, |
| | { |
| | type: PrincipalType.GROUP, |
| | id: groupId, |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId: newResourceId, |
| | updatedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(3); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(0); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const aclEntries = await AclEntry.find({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId: newResourceId, |
| | }); |
| | expect(aclEntries).toHaveLength(3); |
| | }); |
| |
|
| | test('should update existing permissions in bulk', async () => { |
| | const updatedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: userId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | }, |
| | { |
| | type: PrincipalType.GROUP, |
| | id: groupId, |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | }, |
| | { |
| | type: PrincipalType.PUBLIC, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | updatedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | expect(results.granted).toHaveLength(3); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(0); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const userEntry = await AclEntry.findOne({ |
| | principalType: PrincipalType.USER, |
| | principalId: userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }).populate('roleId', 'accessRoleId'); |
| | expect(userEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR); |
| |
|
| | const groupEntry = await AclEntry.findOne({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: groupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }).populate('roleId', 'accessRoleId'); |
| | expect(groupEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_OWNER); |
| | }); |
| |
|
| | test('should revoke specified permissions', async () => { |
| | const revokedPrincipals = [ |
| | { |
| | type: PrincipalType.GROUP, |
| | id: groupId, |
| | }, |
| | { |
| | type: PrincipalType.PUBLIC, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | revokedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(0); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(2); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const remainingEntries = await AclEntry.find({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }); |
| | expect(remainingEntries).toHaveLength(1); |
| | expect(remainingEntries[0].principalType).toBe(PrincipalType.USER); |
| | expect(remainingEntries[0].principalId.toString()).toBe(userId.toString()); |
| | }); |
| |
|
| | test('should handle mixed operations (grant, update, revoke)', async () => { |
| | const updatedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: userId, |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | }, |
| | { |
| | type: PrincipalType.USER, |
| | id: otherUserId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | }, |
| | ]; |
| |
|
| | const revokedPrincipals = [ |
| | { |
| | type: PrincipalType.GROUP, |
| | id: groupId, |
| | }, |
| | { |
| | type: PrincipalType.PUBLIC, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | updatedPrincipals, |
| | revokedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(2); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(2); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const finalEntries = await AclEntry.find({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }).populate('roleId', 'accessRoleId'); |
| |
|
| | expect(finalEntries).toHaveLength(2); |
| |
|
| | const userEntry = finalEntries.find((e) => e.principalId.toString() === userId.toString()); |
| | expect(userEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_OWNER); |
| |
|
| | const otherUserEntry = finalEntries.find( |
| | (e) => e.principalId.toString() === otherUserId.toString(), |
| | ); |
| | expect(otherUserEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER); |
| | }); |
| |
|
| | test('should handle errors for invalid roles gracefully', async () => { |
| | const updatedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: userId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | }, |
| | { |
| | type: PrincipalType.USER, |
| | id: otherUserId, |
| | accessRoleId: 'non_existent_role', |
| | }, |
| | { |
| | type: PrincipalType.GROUP, |
| | id: groupId, |
| | accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | updatedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(1); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(0); |
| | expect(results.errors).toHaveLength(2); |
| |
|
| | |
| | expect(results.errors[0].error).toContain('Role non_existent_role not found'); |
| | expect(results.errors[1].error).toContain('Role promptGroup_viewer not found'); |
| | }); |
| |
|
| | test('should handle empty arrays (no operations)', async () => { |
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | updatedPrincipals: [], |
| | revokedPrincipals: [], |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(0); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(0); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const remainingEntries = await AclEntry.find({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }); |
| | expect(remainingEntries).toHaveLength(3); |
| | }); |
| |
|
| | test('should throw error for invalid updatedPrincipals array', async () => { |
| | await expect( |
| | bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | updatedPrincipals: 'not an array', |
| | grantedBy: grantedById, |
| | }), |
| | ).rejects.toThrow('updatedPrincipals must be an array'); |
| | }); |
| |
|
| | test('should throw error for invalid resource ID', async () => { |
| | await expect( |
| | bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId: 'invalid-id', |
| | permissions: [], |
| | grantedBy: grantedById, |
| | }), |
| | ).rejects.toThrow('Invalid resource ID: invalid-id'); |
| | }); |
| |
|
| | test('should handle public permissions correctly', async () => { |
| | const updatedPrincipals = [ |
| | { |
| | type: PrincipalType.PUBLIC, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | }, |
| | { |
| | type: PrincipalType.USER, |
| | id: otherUserId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | }, |
| | ]; |
| |
|
| | const revokedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: userId, |
| | }, |
| | { |
| | type: PrincipalType.GROUP, |
| | id: groupId, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | updatedPrincipals, |
| | revokedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(2); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(2); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const publicEntry = await AclEntry.findOne({ |
| | principalType: PrincipalType.PUBLIC, |
| | resourceType: ResourceType.AGENT, |
| | resourceId, |
| | }).populate('roleId', 'accessRoleId'); |
| |
|
| | expect(publicEntry).toBeDefined(); |
| | expect(publicEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR); |
| | }); |
| |
|
| | test('should grant permission to a role', async () => { |
| | const entry = await grantPermission({ |
| | principalType: PrincipalType.ROLE, |
| | principalId: 'admin', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: roleResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(entry).toBeDefined(); |
| | expect(entry.principalType).toBe(PrincipalType.ROLE); |
| | expect(entry.principalId).toBe('admin'); |
| | expect(entry.principalModel).toBe(PrincipalModel.ROLE); |
| | expect(entry.resourceType).toBe(ResourceType.AGENT); |
| | expect(entry.resourceId.toString()).toBe(roleResourceId.toString()); |
| |
|
| | |
| | const role = await findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR); |
| | expect(entry.permBits).toBe(role.permBits); |
| | expect(entry.roleId.toString()).toBe(role._id.toString()); |
| | }); |
| |
|
| | test('should check permissions for user with role', async () => { |
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.ROLE, |
| | principalId: 'admin', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: roleResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | { principalType: PrincipalType.ROLE, principalId: 'admin' }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const hasPermission = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: roleResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasPermission).toBe(true); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: userId }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const hasNoPermission = await checkPermission({ |
| | userId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: roleResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasNoPermission).toBe(false); |
| | }); |
| |
|
| | test('should optimize permission checks when role is provided', async () => { |
| | const testUserId = new mongoose.Types.ObjectId(); |
| | const testResourceId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | const User = mongoose.models.User; |
| | await User.create({ |
| | _id: testUserId, |
| | email: 'editor@test.com', |
| | emailVerified: true, |
| | provider: 'local', |
| | role: 'EDITOR', |
| | }); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.ROLE, |
| | principalId: 'EDITOR', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: testUserId }, |
| | { principalType: PrincipalType.ROLE, principalId: 'EDITOR' }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | |
| | const hasPermissionWithRole = await checkPermission({ |
| | userId: testUserId, |
| | role: 'EDITOR', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasPermissionWithRole).toBe(true); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: testUserId, role: 'EDITOR' }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: testUserId }, |
| | { principalType: PrincipalType.ROLE, principalId: 'EDITOR' }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const hasPermissionWithoutRole = await checkPermission({ |
| | userId: testUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasPermissionWithoutRole).toBe(true); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: testUserId, role: undefined }); |
| |
|
| | |
| | getUserPrincipals.mockClear(); |
| |
|
| | const effectiveWithRole = await getEffectivePermissions({ |
| | userId: testUserId, |
| | role: 'EDITOR', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | }); |
| |
|
| | expect(effectiveWithRole).toBe(3); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: testUserId, role: 'EDITOR' }); |
| |
|
| | |
| | getUserPrincipals.mockClear(); |
| |
|
| | const accessibleWithRole = await findAccessibleResources({ |
| | userId: testUserId, |
| | role: 'EDITOR', |
| | resourceType: ResourceType.AGENT, |
| | requiredPermissions: 1, |
| | }); |
| |
|
| | expect(accessibleWithRole.map((id) => id.toString())).toContain(testResourceId.toString()); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: testUserId, role: 'EDITOR' }); |
| | }); |
| |
|
| | test('should handle role changes dynamically', async () => { |
| | const testUserId = new mongoose.Types.ObjectId(); |
| | const testResourceId = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.ROLE, |
| | principalId: 'ADMIN', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: testUserId }, |
| | { principalType: PrincipalType.ROLE, principalId: 'ADMIN' }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const hasAdminAccess = await checkPermission({ |
| | userId: testUserId, |
| | role: 'ADMIN', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | requiredPermission: 7, |
| | }); |
| |
|
| | expect(hasAdminAccess).toBe(true); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: testUserId, role: 'ADMIN' }); |
| |
|
| | |
| | getUserPrincipals.mockClear(); |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: testUserId }, |
| | { principalType: PrincipalType.ROLE, principalId: 'USER' }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const hasUserAccess = await checkPermission({ |
| | userId: testUserId, |
| | role: 'USER', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasUserAccess).toBe(false); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: testUserId, role: 'USER' }); |
| |
|
| | |
| | getUserPrincipals.mockClear(); |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: testUserId }, |
| | { principalType: PrincipalType.ROLE, principalId: 'EDITOR' }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const hasEditorAccess = await checkPermission({ |
| | userId: testUserId, |
| | role: 'EDITOR', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasEditorAccess).toBe(false); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: testUserId, role: 'EDITOR' }); |
| | }); |
| |
|
| | test('should work with different resource types', async () => { |
| | |
| | const promptGroupResourceId = new mongoose.Types.ObjectId(); |
| | const updatedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: userId, |
| | accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, |
| | }, |
| | { |
| | type: PrincipalType.GROUP, |
| | id: groupId, |
| | accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.PROMPTGROUP, |
| | resourceId: promptGroupResourceId, |
| | updatedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(2); |
| | expect(results.updated).toHaveLength(0); |
| | expect(results.revoked).toHaveLength(0); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const promptGroupEntries = await AclEntry.find({ |
| | resourceType: ResourceType.PROMPTGROUP, |
| | resourceId: promptGroupResourceId, |
| | }); |
| | expect(promptGroupEntries).toHaveLength(2); |
| | expect(promptGroupEntries.every((e) => e.resourceType === ResourceType.PROMPTGROUP)).toBe( |
| | true, |
| | ); |
| | }); |
| | }); |
| |
|
| | describe('String vs ObjectId Edge Cases', () => { |
| | const stringUserId = new mongoose.Types.ObjectId().toString(); |
| | const objectIdUserId = new mongoose.Types.ObjectId(); |
| | const stringGroupId = new mongoose.Types.ObjectId().toString(); |
| | const objectIdGroupId = new mongoose.Types.ObjectId(); |
| | const testResourceId = new mongoose.Types.ObjectId(); |
| |
|
| | beforeEach(async () => { |
| | |
| | await AclEntry.deleteMany({}); |
| | getUserPrincipals.mockReset(); |
| | }); |
| |
|
| | test('should handle string userId in grantPermission', async () => { |
| | const entry = await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: stringUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(entry).toBeDefined(); |
| | expect(entry.principalType).toBe(PrincipalType.USER); |
| | |
| | expect(entry.principalId).toBeInstanceOf(mongoose.Types.ObjectId); |
| | expect(entry.principalId.toString()).toBe(stringUserId); |
| | }); |
| |
|
| | test('should handle string groupId in grantPermission', async () => { |
| | const entry = await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: stringGroupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(entry).toBeDefined(); |
| | expect(entry.principalType).toBe(PrincipalType.GROUP); |
| | |
| | expect(entry.principalId).toBeInstanceOf(mongoose.Types.ObjectId); |
| | expect(entry.principalId.toString()).toBe(stringGroupId); |
| | }); |
| |
|
| | test('should handle string roleId in grantPermission for ROLE type', async () => { |
| | const roleString = 'moderator'; |
| |
|
| | const entry = await grantPermission({ |
| | principalType: PrincipalType.ROLE, |
| | principalId: roleString, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(entry).toBeDefined(); |
| | expect(entry.principalType).toBe(PrincipalType.ROLE); |
| | |
| | expect(typeof entry.principalId).toBe('string'); |
| | expect(entry.principalId).toBe(roleString); |
| | expect(entry.principalModel).toBe(PrincipalModel.ROLE); |
| | }); |
| |
|
| | test('should check permissions correctly when permission granted with string userId', async () => { |
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: stringUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { |
| | principalType: PrincipalType.USER, |
| | principalId: new mongoose.Types.ObjectId(stringUserId), |
| | }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | |
| | const hasPermission = await checkPermission({ |
| | userId: stringUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | requiredPermission: 1, |
| | }); |
| |
|
| | expect(hasPermission).toBe(true); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: stringUserId, role: undefined }); |
| | }); |
| |
|
| | test('should check permissions correctly when permission granted with ObjectId', async () => { |
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: objectIdUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { principalType: PrincipalType.USER, principalId: objectIdUserId }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | |
| | const hasPermission = await checkPermission({ |
| | userId: objectIdUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | requiredPermission: 7, |
| | }); |
| |
|
| | expect(hasPermission).toBe(true); |
| | expect(getUserPrincipals).toHaveBeenCalledWith({ userId: objectIdUserId, role: undefined }); |
| | }); |
| |
|
| | test('should handle bulkUpdateResourcePermissions with string IDs', async () => { |
| | const updatedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: stringUserId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | }, |
| | { |
| | type: PrincipalType.GROUP, |
| | id: stringGroupId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | }, |
| | { |
| | type: PrincipalType.ROLE, |
| | id: 'admin', |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | updatedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.granted).toHaveLength(3); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const userEntry = await AclEntry.findOne({ |
| | principalType: PrincipalType.USER, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | }); |
| | expect(userEntry.principalId).toBeInstanceOf(mongoose.Types.ObjectId); |
| | expect(userEntry.principalId.toString()).toBe(stringUserId); |
| |
|
| | |
| | const groupEntry = await AclEntry.findOne({ |
| | principalType: PrincipalType.GROUP, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | }); |
| | expect(groupEntry.principalId).toBeInstanceOf(mongoose.Types.ObjectId); |
| | expect(groupEntry.principalId.toString()).toBe(stringGroupId); |
| |
|
| | |
| | const roleEntry = await AclEntry.findOne({ |
| | principalType: PrincipalType.ROLE, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | }); |
| | expect(typeof roleEntry.principalId).toBe('string'); |
| | expect(roleEntry.principalId).toBe('admin'); |
| | }); |
| |
|
| | test('should handle revoking permissions with string IDs in bulkUpdateResourcePermissions', async () => { |
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: objectIdUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: objectIdGroupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | const revokedPrincipals = [ |
| | { |
| | type: PrincipalType.USER, |
| | id: objectIdUserId.toString(), |
| | }, |
| | { |
| | type: PrincipalType.GROUP, |
| | id: objectIdGroupId.toString(), |
| | }, |
| | ]; |
| |
|
| | const results = await bulkUpdateResourcePermissions({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | revokedPrincipals, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | expect(results.revoked).toHaveLength(2); |
| | expect(results.errors).toHaveLength(0); |
| |
|
| | |
| | const remainingEntries = await AclEntry.find({ |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | }); |
| | expect(remainingEntries).toHaveLength(0); |
| | }); |
| |
|
| | test('should find accessible resources when permissions granted with mixed ID types', async () => { |
| | const resource1 = new mongoose.Types.ObjectId(); |
| | const resource2 = new mongoose.Types.ObjectId(); |
| | const resource3 = new mongoose.Types.ObjectId(); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: stringUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: resource1, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: new mongoose.Types.ObjectId(stringUserId), |
| | resourceType: ResourceType.AGENT, |
| | resourceId: resource2, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.ROLE, |
| | principalId: 'admin', |
| | resourceType: ResourceType.AGENT, |
| | resourceId: resource3, |
| | accessRoleId: AccessRoleIds.AGENT_OWNER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { |
| | principalType: PrincipalType.USER, |
| | principalId: new mongoose.Types.ObjectId(stringUserId), |
| | }, |
| | { principalType: PrincipalType.ROLE, principalId: 'admin' }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const accessibleResources = await findAccessibleResources({ |
| | userId: stringUserId, |
| | role: 'admin', |
| | resourceType: ResourceType.AGENT, |
| | requiredPermissions: 1, |
| | }); |
| |
|
| | |
| | expect(accessibleResources).toHaveLength(3); |
| | const resourceIds = accessibleResources.map((id) => id.toString()); |
| | expect(resourceIds).toContain(resource1.toString()); |
| | expect(resourceIds).toContain(resource2.toString()); |
| | expect(resourceIds).toContain(resource3.toString()); |
| | }); |
| |
|
| | test('should get effective permissions with mixed ID types', async () => { |
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.USER, |
| | principalId: stringUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_VIEWER, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | await grantPermission({ |
| | principalType: PrincipalType.GROUP, |
| | principalId: stringGroupId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | accessRoleId: AccessRoleIds.AGENT_EDITOR, |
| | grantedBy: grantedById, |
| | }); |
| |
|
| | |
| | getUserPrincipals.mockResolvedValue([ |
| | { |
| | principalType: PrincipalType.USER, |
| | principalId: new mongoose.Types.ObjectId(stringUserId), |
| | }, |
| | { |
| | principalType: PrincipalType.GROUP, |
| | principalId: new mongoose.Types.ObjectId(stringGroupId), |
| | }, |
| | { principalType: PrincipalType.PUBLIC }, |
| | ]); |
| |
|
| | const effectivePermissions = await getEffectivePermissions({ |
| | userId: stringUserId, |
| | resourceType: ResourceType.AGENT, |
| | resourceId: testResourceId, |
| | }); |
| |
|
| | |
| | expect(effectivePermissions).toBe(3); |
| | }); |
| | }); |
| | }); |
| |
|