From 7067f6ef88762ec1aba131e36a434aa2298ca23a Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:51:27 +0530 Subject: [PATCH] fix: honor agent rolePermissionConfig in record CRUD (#23248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Agent tools were built with the agent’s `rolePermissionConfig`, but record CRUD ignored it and re-resolved permissions from `authContext` (app `defaultRoleId`) - CRUD services now pass `rolePermissionConfig` through `CommonApiContextBuilder` and the common query runner, so repository access matches the agent role - Workflow/chat paths already use the same role for auth and `rolePermissionConfig`, so their behavior should be unchanged Review in cubic --- .../common-base-query-runner.service.ts | 12 +-- .../common-base-query-runner-context.type.ts | 2 + .../common-api-context-builder.service.ts | 38 +++++++-- .../services/create-many-records.service.ts | 4 +- .../services/create-record.service.ts | 4 +- .../services/delete-many-records.service.ts | 3 +- .../services/delete-record.service.ts | 9 +- .../services/find-records.service.ts | 2 + .../services/group-by-records.service.ts | 2 + .../services/update-many-records.service.ts | 4 +- .../services/update-record.service.ts | 2 + .../services/upsert-many-records.service.ts | 4 +- .../services/upsert-record.service.ts | 4 +- .../providers/database-tool.provider.ts | 41 ++------- .../agent-async-executor.service.spec.ts | 4 +- .../services/agent-async-executor.service.ts | 2 +- ...s-from-role-permission-config.util.spec.ts | 83 +++++++++++++++++++ ...ssions-from-role-permission-config.util.ts | 33 ++++++++ 18 files changed, 193 insertions(+), 60 deletions(-) create mode 100644 packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-objects-permissions-from-role-permission-config.util.spec.ts create mode 100644 packages/twenty-server/src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util.ts diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts index c902b80942..0189c6c80f 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts @@ -317,11 +317,13 @@ export abstract class CommonBaseQueryRunnerService< ): Promise> { const context = getWorkspaceContext(); - const rolePermissionConfig = resolveRolePermissionConfig({ - authContext: context.authContext, - userWorkspaceRoleMap: context.userWorkspaceRoleMap, - apiKeyRoleMap: context.apiKeyRoleMap, - }); + const rolePermissionConfig = + queryRunnerContext.rolePermissionConfig ?? + resolveRolePermissionConfig({ + authContext: context.authContext, + userWorkspaceRoleMap: context.userWorkspaceRoleMap, + apiKeyRoleMap: context.apiKeyRoleMap, + }); if (!rolePermissionConfig) { throw new CommonQueryRunnerException( diff --git a/packages/twenty-server/src/engine/api/common/types/common-base-query-runner-context.type.ts b/packages/twenty-server/src/engine/api/common/types/common-base-query-runner-context.type.ts index 938ffac8f6..aff74ac614 100644 --- a/packages/twenty-server/src/engine/api/common/types/common-base-query-runner-context.type.ts +++ b/packages/twenty-server/src/engine/api/common/types/common-base-query-runner-context.type.ts @@ -3,6 +3,7 @@ import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/typ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; export type CommonBaseQueryRunnerContext = { authContext: WorkspaceAuthContext; @@ -11,4 +12,5 @@ export type CommonBaseQueryRunnerContext = { flatFieldMetadataMaps: FlatEntityMaps; flatIndexMaps?: FlatEntityMaps; objectIdByNameSingular: Record; + rolePermissionConfig?: RolePermissionConfig; }; diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts index 7b4c623df4..48b72e21f2 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts @@ -22,6 +22,8 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; +import { getObjectsPermissionsFromRolePermissionConfig } from 'src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; export type CommonApiContext = { @@ -45,9 +47,11 @@ export class CommonApiContextBuilderService { async build({ authContext, objectName, + rolePermissionConfig, }: { authContext: WorkspaceAuthContext; objectName: string; + rolePermissionConfig?: RolePermissionConfig; }): Promise { const workspaceId = authContext.workspace.id; @@ -94,7 +98,10 @@ export class CommonApiContextBuilderService { ); } - const objectsPermissions = await this.getObjectsPermissions(authContext); + const objectsPermissions = await this.getObjectsPermissions({ + authContext, + rolePermissionConfig, + }); const restrictedFields = objectsPermissions[flatObjectMetadata.id]?.restrictedFields ?? {}; @@ -113,6 +120,7 @@ export class CommonApiContextBuilderService { flatFieldMetadataMaps, flatIndexMaps, objectIdByNameSingular: idByNameSingular, + rolePermissionConfig, }, selectedFields, flatObjectMetadata, @@ -122,10 +130,27 @@ export class CommonApiContextBuilderService { }; } - private async getObjectsPermissions( - authContext: WorkspaceAuthContext, - ): Promise { + private async getObjectsPermissions({ + authContext, + rolePermissionConfig, + }: { + authContext: WorkspaceAuthContext; + rolePermissionConfig?: RolePermissionConfig; + }): Promise { const workspaceId = authContext.workspace.id; + + const { rolesPermissions } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'rolesPermissions', + ]); + + if (isDefined(rolePermissionConfig)) { + return getObjectsPermissionsFromRolePermissionConfig({ + rolesPermissions, + rolePermissionConfig, + }); + } + let roleId: string; if (isApiKeyAuthContext(authContext)) { @@ -160,11 +185,6 @@ export class CommonApiContextBuilderService { ); } - const { rolesPermissions } = - await this.workspaceCacheService.getOrRecompute(workspaceId, [ - 'rolesPermissions', - ]); - return rolesPermissions[roleId] ?? {}; } } diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts index 0ec3ba5e18..9c4bba5f85 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts @@ -24,7 +24,8 @@ export class CreateManyRecordsService { ) {} async execute(params: CreateManyRecordsParams): Promise { - const { objectName, objectRecords, authContext } = params; + const { objectName, objectRecords, authContext, rolePermissionConfig } = + params; try { const { @@ -35,6 +36,7 @@ export class CreateManyRecordsService { } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts index cd36ddb67e..aefb3f23e9 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts @@ -24,7 +24,8 @@ export class CreateRecordService { ) {} async execute(params: CreateRecordParams): Promise { - const { objectName, objectRecord, authContext } = params; + const { objectName, objectRecord, authContext, rolePermissionConfig } = + params; try { const { @@ -35,6 +36,7 @@ export class CreateRecordService { } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-many-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-many-records.service.ts index 473db7fb07..3966a0a90d 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-many-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-many-records.service.ts @@ -21,7 +21,7 @@ export class DeleteManyRecordsService { ) {} async execute(params: DeleteManyRecordsParams): Promise { - const { objectName, filter, authContext } = params; + const { objectName, filter, authContext, rolePermissionConfig } = params; if (!isDefined(filter) || isEmptyObject(filter)) { return { @@ -37,6 +37,7 @@ export class DeleteManyRecordsService { await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts index b6eea807b6..b8a3d7ecee 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts @@ -24,7 +24,13 @@ export class DeleteRecordService { ) {} async execute(params: DeleteRecordParams): Promise { - const { objectName, objectRecordId, authContext, soft = true } = params; + const { + objectName, + objectRecordId, + authContext, + rolePermissionConfig, + soft = true, + } = params; if (!isDefined(objectRecordId) || !isValidUuid(objectRecordId)) { return { @@ -39,6 +45,7 @@ export class DeleteRecordService { await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/find-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/find-records.service.ts index 929f5f986e..2374a85a64 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/find-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/find-records.service.ts @@ -34,6 +34,7 @@ export class FindRecordsService { limit, offset = 0, authContext, + rolePermissionConfig, select, shouldBuildEffectiveSelectFields, } = params; @@ -55,6 +56,7 @@ export class FindRecordsService { } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); const { effectiveSelectedFields, warnings } = diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/group-by-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/group-by-records.service.ts index ed452d360b..610f1b4d6d 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/group-by-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/group-by-records.service.ts @@ -41,6 +41,7 @@ export class GroupByRecordsService { orderBy = 'DESC', filter, authContext, + rolePermissionConfig, } = params; try { @@ -52,6 +53,7 @@ export class GroupByRecordsService { } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); const availableAggregations = diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts index dbe83ad114..61ccab7c16 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts @@ -23,7 +23,8 @@ export class UpdateManyRecordsService { ) {} async execute(params: UpdateManyRecordsParams): Promise { - const { objectName, filter, data, authContext } = params; + const { objectName, filter, data, authContext, rolePermissionConfig } = + params; try { const { @@ -34,6 +35,7 @@ export class UpdateManyRecordsService { } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts index ce07e5bd01..e8ac275add 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts @@ -30,6 +30,7 @@ export class UpdateRecordService { objectRecord, fieldsToUpdate, authContext, + rolePermissionConfig, } = params; if (!isDefined(objectRecordId) || !isValidUuid(objectRecordId)) { @@ -49,6 +50,7 @@ export class UpdateRecordService { } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-many-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-many-records.service.ts index 3f7b01ff23..3a7c1dae16 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-many-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-many-records.service.ts @@ -25,7 +25,8 @@ export class UpsertManyRecordsService { ) {} async execute(params: UpsertManyRecordsParams): Promise { - const { objectName, objectRecords, authContext } = params; + const { objectName, objectRecords, authContext, rolePermissionConfig } = + params; try { const { @@ -36,6 +37,7 @@ export class UpsertManyRecordsService { } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts index 58ee63f859..cd2ca17308 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts @@ -22,13 +22,15 @@ export class UpsertRecordService { ) {} async execute(params: UpsertRecordParams): Promise { - const { objectName, objectRecord, authContext } = params; + const { objectName, objectRecord, authContext, rolePermissionConfig } = + params; try { const { queryRunnerContext, selectedFields, flatObjectMetadata } = await this.commonApiContextBuilder.build({ authContext, objectName, + rolePermissionConfig, }); if ( diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts index 6422372623..1c85dc8937 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts @@ -1,9 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - type ObjectsPermissions, - type ObjectsPermissionsByRoleId, -} from 'twenty-shared/types'; import { camelToSnakeCase, isDefined } from 'twenty-shared/utils'; import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; @@ -32,7 +28,7 @@ import { type ToolIndexEntry } from 'src/engine/core-modules/tool-provider/types import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; import { getDatabaseCrudToolFlatObjects } from 'src/engine/metadata-modules/ai/ai-agent/utils/get-database-crud-tool-flat-objects.util'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { computePermissionIntersection } from 'src/engine/twenty-orm/utils/compute-permission-intersection.util'; +import { getObjectsPermissionsFromRolePermissionConfig } from 'src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { ToolCategory } from 'twenty-shared/ai'; @@ -77,12 +73,12 @@ export class DatabaseToolProvider implements ToolProvider { 'rolesPermissions', ]); - const objectPermissions = this.getObjectPermissions( + const objectPermissions = getObjectsPermissionsFromRolePermissionConfig({ rolesPermissions, - context.rolePermissionConfig, - ); + rolePermissionConfig: context.rolePermissionConfig, + }); - if (!objectPermissions) { + if (Object.keys(objectPermissions).length === 0) { return descriptors; } @@ -427,31 +423,4 @@ export class DatabaseToolProvider implements ToolProvider { toolNames.has(`upsert_many_${snakePlural}`) ); } - - private getObjectPermissions( - rolesPermissions: ObjectsPermissionsByRoleId, - rolePermissionConfig: ToolProviderContext['rolePermissionConfig'], - ): ObjectsPermissions | null { - if ('intersectionOf' in rolePermissionConfig) { - const allRolePermissions = rolePermissionConfig.intersectionOf.map( - (roleId: string) => rolesPermissions[roleId], - ); - - return allRolePermissions.length === 1 - ? allRolePermissions[0] - : computePermissionIntersection(allRolePermissions); - } - - if ('unionOf' in rolePermissionConfig) { - if (rolePermissionConfig.unionOf.length === 1) { - return rolesPermissions[rolePermissionConfig.unionOf[0]]; - } - - throw new Error( - 'Union permission logic for multiple roles not yet implemented', - ); - } - - return null; - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/__tests__/agent-async-executor.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/__tests__/agent-async-executor.service.spec.ts index 6bb9dd1b1b..d647d8d971 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/__tests__/agent-async-executor.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/__tests__/agent-async-executor.service.spec.ts @@ -132,7 +132,7 @@ describe('AgentAsyncExecutorService — workflow agent role-scoped tool resoluti service = module.get(AgentAsyncExecutorService); }); - it('passes unionOf: [agentRoleId] when the agent has a role assigned', async () => { + it('passes intersectionOf: [agentRoleId] when the agent has a role assigned', async () => { roleTargetRepository.findOne.mockResolvedValueOnce({ roleId: agentRoleId }); await service.executeAgent({ @@ -145,7 +145,7 @@ describe('AgentAsyncExecutorService — workflow agent role-scoped tool resoluti expect(toolRegistry.getToolsByCategories).toHaveBeenCalledWith( expect.objectContaining({ roleId: agentRoleId, - rolePermissionConfig: { unionOf: [agentRoleId] }, + rolePermissionConfig: { intersectionOf: [agentRoleId] }, workspaceId, }), expect.objectContaining({ wrapWithErrorContext: false }), diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts index 32e07bca0d..944b5f6715 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts @@ -176,7 +176,7 @@ export class AgentAsyncExecutorService { // permission-tab role. No role means no registry tools. if (isDefined(agentRoleId)) { const agentRolePermissionConfig: RolePermissionConfig = { - unionOf: [agentRoleId], + intersectionOf: [agentRoleId], }; const toolProviderContext: ToolProviderContext = { diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-objects-permissions-from-role-permission-config.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-objects-permissions-from-role-permission-config.util.spec.ts new file mode 100644 index 0000000000..1f7d601a8c --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-objects-permissions-from-role-permission-config.util.spec.ts @@ -0,0 +1,83 @@ +import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types'; + +import { getObjectsPermissionsFromRolePermissionConfig } from 'src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util'; + +const OBJECT_ID = 'object-1'; + +const agentRolePermissions = { + [OBJECT_ID]: { + canReadObjectRecords: true, + canUpdateObjectRecords: false, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + restrictedFields: {}, + rowLevelPermissionPredicates: [], + rowLevelPermissionPredicateGroups: [], + }, +}; + +const defaultRolePermissions = { + [OBJECT_ID]: { + canReadObjectRecords: false, + canUpdateObjectRecords: false, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + restrictedFields: {}, + rowLevelPermissionPredicates: [], + rowLevelPermissionPredicateGroups: [], + }, +}; + +const rolesPermissions: ObjectsPermissionsByRoleId = { + 'agent-role-id': agentRolePermissions, + 'default-role-id': defaultRolePermissions, +}; + +describe('getObjectsPermissionsFromRolePermissionConfig', () => { + it('should resolve a single union role', () => { + expect( + getObjectsPermissionsFromRolePermissionConfig({ + rolesPermissions, + rolePermissionConfig: { unionOf: ['agent-role-id'] }, + }), + ).toEqual(agentRolePermissions); + }); + + it('should resolve a single intersection role', () => { + expect( + getObjectsPermissionsFromRolePermissionConfig({ + rolesPermissions, + rolePermissionConfig: { intersectionOf: ['default-role-id'] }, + }), + ).toEqual(defaultRolePermissions); + }); + + it('should use the first role when multiple are provided', () => { + expect( + getObjectsPermissionsFromRolePermissionConfig({ + rolesPermissions, + rolePermissionConfig: { + intersectionOf: ['agent-role-id', 'default-role-id'], + }, + }), + ).toEqual(agentRolePermissions); + }); + + it('should return empty permissions when bypassing checks', () => { + expect( + getObjectsPermissionsFromRolePermissionConfig({ + rolesPermissions, + rolePermissionConfig: { shouldBypassPermissionChecks: true }, + }), + ).toEqual({}); + }); + + it('should return empty permissions when the role is missing from the cache', () => { + expect( + getObjectsPermissionsFromRolePermissionConfig({ + rolesPermissions, + rolePermissionConfig: { unionOf: ['missing-role-id'] }, + }), + ).toEqual({}); + }); +}); diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util.ts new file mode 100644 index 0000000000..f2fb20537d --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util.ts @@ -0,0 +1,33 @@ +import { + type ObjectsPermissions, + type ObjectsPermissionsByRoleId, +} from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; + +// Multi-role union/intersection is not ready — use the first assigned role only. +export const getObjectsPermissionsFromRolePermissionConfig = ({ + rolesPermissions, + rolePermissionConfig, +}: { + rolesPermissions: ObjectsPermissionsByRoleId; + rolePermissionConfig: RolePermissionConfig; +}): ObjectsPermissions => { + if ('shouldBypassPermissionChecks' in rolePermissionConfig) { + return {}; + } + + const roleId = + 'intersectionOf' in rolePermissionConfig + ? rolePermissionConfig.intersectionOf[0] + : 'unionOf' in rolePermissionConfig + ? rolePermissionConfig.unionOf[0] + : undefined; + + if (!isDefined(roleId)) { + return {}; + } + + return rolesPermissions[roleId] ?? {}; +};