feat: multi role permission intersection (#15150)
Implements permission intersection (AND logic) to prevent permission escalation when agents act on behalf of users. ### Changes: - **Permission Intersection**: Operations requiring both user AND agent permissions - **RoleContext Type**: Unified type supporting single `roleId` or multiple `roleIds` for intersection - **CRUD Services**: Updated to accept `roleContext` for granular permission control - **Agent Integration**: Chat agents now use user + agent role intersection for all operations - **ORM Layer**: Enhanced `getRepository` to support multi-role permission checks ### Related: - Part 2 of ["Acting on behalf of user" concept PR](https://github.com/twentyhq/twenty/pull/15103) [Closes #1661](https://github.com/twentyhq/core-team-issues/issues/1661) --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
+10
-16
@@ -1,5 +1,4 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import {
|
||||
convertToModelMessages,
|
||||
@@ -13,15 +12,15 @@ import {
|
||||
} from 'ai';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath } from 'twenty-shared/utils';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { getAllSelectableFields } from 'src/engine/api/utils/get-all-selectable-fields.utils';
|
||||
import { AIBillingService } from 'src/engine/core-modules/ai/services/ai-billing.service';
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentHandoffToolService } from 'src/engine/metadata-modules/agent/agent-handoff-tool.service';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/agent/constants/agent-config.const';
|
||||
import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/agent/constants/agent-system-prompts.const';
|
||||
import { AgentActorContextService } from 'src/engine/metadata-modules/agent/services/agent-actor-context.service';
|
||||
@@ -48,7 +47,6 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
|
||||
constructor(
|
||||
private readonly agentHandoffToolService: AgentHandoffToolService,
|
||||
private readonly fileService: FileService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
@@ -57,8 +55,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
private readonly agentModelConfigService: AgentModelConfigService,
|
||||
private readonly aiBillingService: AIBillingService,
|
||||
private readonly agentActorContextService: AgentActorContextService,
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
private readonly agentService: AgentService,
|
||||
) {}
|
||||
|
||||
async prepareAIRequestConfig({
|
||||
@@ -66,14 +63,14 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
system,
|
||||
agent,
|
||||
actorContext,
|
||||
roleIdOverride,
|
||||
roleIds,
|
||||
excludeHandoffTools = false,
|
||||
}: {
|
||||
system: string;
|
||||
agent: AgentEntity | null;
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
actorContext?: ActorMetadata;
|
||||
roleIdOverride?: string;
|
||||
roleIds?: string[];
|
||||
excludeHandoffTools?: boolean;
|
||||
}) {
|
||||
try {
|
||||
@@ -95,7 +92,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
actorContext,
|
||||
roleIdOverride,
|
||||
roleIds,
|
||||
);
|
||||
|
||||
let handoffTools = {};
|
||||
@@ -193,8 +190,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
|
||||
const repository = workspaceDataSource.getRepository(
|
||||
recordsWithObjectMetadataNameSingular.objectMetadataNameSingular,
|
||||
false,
|
||||
roleId,
|
||||
{ unionOf: [roleId] },
|
||||
);
|
||||
|
||||
const restrictedFields =
|
||||
@@ -254,9 +250,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType;
|
||||
}) {
|
||||
try {
|
||||
const agent = await this.agentRepository.findOneOrFail({
|
||||
where: { id: agentId },
|
||||
});
|
||||
const agent = await this.agentService.findOneAgent(agentId, workspace.id);
|
||||
|
||||
let contextString = '';
|
||||
|
||||
@@ -271,7 +265,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
}
|
||||
|
||||
const { actorContext, roleId } =
|
||||
await this.agentActorContextService.buildUserActorContext(
|
||||
await this.agentActorContextService.buildUserAndAgentActorContext(
|
||||
userWorkspaceId,
|
||||
workspace.id,
|
||||
);
|
||||
@@ -281,7 +275,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
agent,
|
||||
messages,
|
||||
actorContext,
|
||||
roleIdOverride: roleId,
|
||||
roleIds: [roleId, ...(agent?.roleId ? [agent?.roleId] : [])],
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
|
||||
+8
-24
@@ -6,7 +6,6 @@ import { Repository } from 'typeorm';
|
||||
|
||||
import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service';
|
||||
import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
@@ -24,58 +23,43 @@ export class AgentToolGeneratorService {
|
||||
private readonly toolService: ToolService,
|
||||
private readonly workflowToolService: WorkflowToolService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
private readonly agentService: AgentService,
|
||||
) {}
|
||||
|
||||
async generateToolsForAgent(
|
||||
agentId: string,
|
||||
workspaceId: string,
|
||||
actorContext?: ActorMetadata,
|
||||
roleIdOverride?: string,
|
||||
roleIds?: string[],
|
||||
): Promise<ToolSet> {
|
||||
let tools: ToolSet = {};
|
||||
|
||||
try {
|
||||
const agent = await this.agentService.findOneAgent(agentId, workspaceId);
|
||||
const actionTools = await this.toolAdapterService.getTools();
|
||||
|
||||
tools = { ...actionTools };
|
||||
|
||||
const effectiveRoleId = roleIdOverride || agent.roleId;
|
||||
|
||||
if (!effectiveRoleId) {
|
||||
return tools;
|
||||
}
|
||||
|
||||
const role = await this.roleRepository.findOne({
|
||||
where: {
|
||||
id: effectiveRoleId,
|
||||
workspaceId,
|
||||
},
|
||||
relations: ['permissionFlags'],
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
if (!roleIds) {
|
||||
return tools;
|
||||
}
|
||||
|
||||
const hasWorkflowPermission =
|
||||
this.permissionsService.checkRolePermissions(
|
||||
role,
|
||||
await this.permissionsService.checkRolesPermissions(
|
||||
{ intersectionOf: roleIds },
|
||||
workspaceId,
|
||||
PermissionFlagType.WORKFLOWS,
|
||||
);
|
||||
|
||||
if (hasWorkflowPermission) {
|
||||
const workflowTools = this.workflowToolService.generateWorkflowTools(
|
||||
workspaceId,
|
||||
effectiveRoleId,
|
||||
{ intersectionOf: roleIds },
|
||||
);
|
||||
|
||||
tools = { ...tools, ...workflowTools };
|
||||
}
|
||||
|
||||
const databaseTools = await this.toolService.listTools(
|
||||
effectiveRoleId,
|
||||
{ intersectionOf: roleIds },
|
||||
workspaceId,
|
||||
actorContext,
|
||||
);
|
||||
@@ -83,7 +67,7 @@ export class AgentToolGeneratorService {
|
||||
tools = { ...tools, ...databaseTools };
|
||||
|
||||
const roleActionTools = await this.toolAdapterService.getTools(
|
||||
effectiveRoleId,
|
||||
{ intersectionOf: roleIds },
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
|
||||
+13
-3
@@ -12,7 +12,7 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.
|
||||
|
||||
export type AgentActorContext = {
|
||||
actorContext: ActorMetadata;
|
||||
roleId: string | undefined;
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -23,7 +23,7 @@ export class AgentActorContextService {
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async buildUserActorContext(
|
||||
async buildUserAndAgentActorContext(
|
||||
userWorkspaceId: string,
|
||||
workspaceId: string,
|
||||
): Promise<AgentActorContext> {
|
||||
@@ -61,11 +61,21 @@ export class AgentActorContextService {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!roleId) {
|
||||
throw new AgentException(
|
||||
'User role not found',
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
const actorContext = buildCreatedByFromFullNameMetadata({
|
||||
fullNameMetadata: workspaceMember.name,
|
||||
workspaceMemberId: workspaceMember.id,
|
||||
});
|
||||
|
||||
return { actorContext, roleId };
|
||||
return {
|
||||
actorContext,
|
||||
roleId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+2
-10
@@ -1,7 +1,7 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { type ObjectsPermissionsByRoleIdDeprecated } from 'twenty-shared/types';
|
||||
import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types';
|
||||
import { In, type Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
@@ -53,13 +53,9 @@ describe('FieldPermissionService', () => {
|
||||
isEditable: true,
|
||||
} as RoleEntity;
|
||||
|
||||
const mockRolesPermissions: ObjectsPermissionsByRoleIdDeprecated = {
|
||||
const mockRolesPermissions: ObjectsPermissionsByRoleId = {
|
||||
[testRoleId]: {
|
||||
[testObjectMetadataId]: {
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
canSoftDelete: false,
|
||||
canDestroy: false,
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
@@ -67,10 +63,6 @@ describe('FieldPermissionService', () => {
|
||||
restrictedFields: {},
|
||||
},
|
||||
[fieldRelationMock.objectMetadataId]: {
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
canSoftDelete: false,
|
||||
canDestroy: false,
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { type ObjectsPermissionsByRoleIdDeprecated } from 'twenty-shared/types';
|
||||
import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
@@ -183,7 +183,7 @@ export class FieldPermissionService {
|
||||
allFieldPermissions: UpsertFieldPermissionsInput['fieldPermissions'];
|
||||
fieldPermission: UpsertFieldPermissionsInput['fieldPermissions'][0];
|
||||
objectMetadataMapsById: ObjectMetadataMaps['byId'];
|
||||
rolesPermissions: ObjectsPermissionsByRoleIdDeprecated;
|
||||
rolesPermissions: ObjectsPermissionsByRoleId;
|
||||
role: RoleEntity;
|
||||
}) {
|
||||
const duplicateFieldPermissions = allFieldPermissions.filter(
|
||||
|
||||
+87
-15
@@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/api-key-role.service';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
@@ -17,6 +17,7 @@ import { type UserWorkspacePermissions } from 'src/engine/metadata-modules/permi
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
|
||||
@Injectable()
|
||||
export class PermissionsService {
|
||||
@@ -188,30 +189,101 @@ export class PermissionsService {
|
||||
);
|
||||
}
|
||||
|
||||
private async getRolesFromPermissionConfig(
|
||||
rolePermissionConfig: RolePermissionConfig,
|
||||
workspaceId: string,
|
||||
relations: string[] = [],
|
||||
): Promise<{ roles: RoleEntity[]; useIntersection: boolean } | null> {
|
||||
if ('shouldBypassPermissionChecks' in rolePermissionConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let roleIds: string[] = [];
|
||||
let useIntersection = false;
|
||||
|
||||
if ('intersectionOf' in rolePermissionConfig) {
|
||||
roleIds = rolePermissionConfig.intersectionOf;
|
||||
useIntersection = true;
|
||||
} else if ('unionOf' in rolePermissionConfig) {
|
||||
roleIds = rolePermissionConfig.unionOf;
|
||||
useIntersection = false;
|
||||
}
|
||||
|
||||
if (roleIds.length === 0) {
|
||||
throw new Error('No role IDs provided');
|
||||
}
|
||||
|
||||
const roles = await this.roleRepository.find({
|
||||
where: { id: In(roleIds), workspaceId },
|
||||
relations,
|
||||
});
|
||||
|
||||
if (roles.length !== roleIds.length) {
|
||||
throw new Error('Some roles not found');
|
||||
}
|
||||
|
||||
return { roles, useIntersection };
|
||||
}
|
||||
|
||||
public async checkRolesPermissions(
|
||||
rolePermissionConfig: RolePermissionConfig,
|
||||
workspaceId: string,
|
||||
setting: PermissionFlagType,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.getRolesFromPermissionConfig(
|
||||
rolePermissionConfig,
|
||||
workspaceId,
|
||||
['permissionFlags'],
|
||||
);
|
||||
|
||||
if (result === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { roles, useIntersection } = result;
|
||||
|
||||
return useIntersection
|
||||
? roles.every((role) => this.checkRolePermissions(role, setting))
|
||||
: roles.some((role) => this.checkRolePermissions(role, setting));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async hasToolPermission(
|
||||
roleId: string,
|
||||
rolePermissionConfig: RolePermissionConfig,
|
||||
workspaceId: string,
|
||||
flag: PermissionFlagType,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const role = await this.roleRepository.findOne({
|
||||
where: { id: roleId, workspaceId },
|
||||
relations: ['permissionFlags'],
|
||||
});
|
||||
const result = await this.getRolesFromPermissionConfig(
|
||||
rolePermissionConfig,
|
||||
workspaceId,
|
||||
['permissionFlags'],
|
||||
);
|
||||
|
||||
if (!role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role.canAccessAllTools === true) {
|
||||
if (result === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const permissionFlags = role.permissionFlags ?? [];
|
||||
const { roles, useIntersection } = result;
|
||||
|
||||
return permissionFlags.some(
|
||||
(permissionFlag) => permissionFlag.flag === flag,
|
||||
);
|
||||
const checkRoleHasPermission = (role: RoleEntity) => {
|
||||
if (role.canAccessAllTools === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const permissionFlags = role.permissionFlags ?? [];
|
||||
|
||||
return permissionFlags.some(
|
||||
(permissionFlag) => permissionFlag.flag === flag,
|
||||
);
|
||||
};
|
||||
|
||||
return useIntersection
|
||||
? roles.every(checkRoleHasPermission)
|
||||
: roles.some(checkRoleHasPermission);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type ObjectsPermissionsByRoleIdDeprecated } from 'twenty-shared/types';
|
||||
import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
@@ -34,12 +34,12 @@ export class WorkspacePermissionsCacheStorageService {
|
||||
|
||||
async setRolesPermissions(
|
||||
workspaceId: string,
|
||||
permissions: ObjectsPermissionsByRoleIdDeprecated,
|
||||
permissions: ObjectsPermissionsByRoleId,
|
||||
): Promise<{
|
||||
newRolesPermissionsVersion: string;
|
||||
}> {
|
||||
const [, newRolesPermissionsVersion] = await Promise.all([
|
||||
this.cacheStorageService.set<ObjectsPermissionsByRoleIdDeprecated>(
|
||||
this.cacheStorageService.set<ObjectsPermissionsByRoleId>(
|
||||
`${WORKSPACE_CACHE_KEYS.MetadataPermissionsRolesPermissions}:${workspaceId}`,
|
||||
permissions,
|
||||
TTL_INFINITE,
|
||||
@@ -52,8 +52,8 @@ export class WorkspacePermissionsCacheStorageService {
|
||||
|
||||
getRolesPermissions(
|
||||
workspaceId: string,
|
||||
): Promise<ObjectsPermissionsByRoleIdDeprecated | undefined> {
|
||||
return this.cacheStorageService.get<ObjectsPermissionsByRoleIdDeprecated>(
|
||||
): Promise<ObjectsPermissionsByRoleId | undefined> {
|
||||
return this.cacheStorageService.get<ObjectsPermissionsByRoleId>(
|
||||
`${WORKSPACE_CACHE_KEYS.MetadataPermissionsRolesPermissions}:${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
+8
-16
@@ -3,9 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Record } from 'cloudflare/core';
|
||||
import {
|
||||
ObjectsPermissions,
|
||||
type ObjectsPermissionsByRoleIdDeprecated,
|
||||
type ObjectsPermissionsDeprecated,
|
||||
type ObjectsPermissions,
|
||||
type ObjectsPermissionsByRoleId,
|
||||
type RestrictedFieldsPermissions,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -48,7 +47,7 @@ export class WorkspacePermissionsCacheService {
|
||||
private readonly workspacePermissionsCacheStorageService: WorkspacePermissionsCacheStorageService,
|
||||
private readonly getRolesPermissionsFromCacheWithRecomputeService: GetDataFromCacheWithRecomputeService<
|
||||
string,
|
||||
ObjectsPermissionsByRoleIdDeprecated
|
||||
ObjectsPermissionsByRoleId
|
||||
>,
|
||||
private readonly getUserWorkspaceRoleMapFromCacheWithRecomputeService: GetDataFromCacheWithRecomputeService<
|
||||
string,
|
||||
@@ -67,9 +66,7 @@ export class WorkspacePermissionsCacheService {
|
||||
workspaceId: string;
|
||||
roleIds?: string[];
|
||||
}): Promise<void> {
|
||||
let currentRolesPermissions:
|
||||
| ObjectsPermissionsByRoleIdDeprecated
|
||||
| undefined;
|
||||
let currentRolesPermissions: ObjectsPermissionsByRoleId | undefined;
|
||||
|
||||
if (roleIds) {
|
||||
currentRolesPermissions =
|
||||
@@ -124,7 +121,7 @@ export class WorkspacePermissionsCacheService {
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}): Promise<CacheResult<string, ObjectsPermissionsByRoleIdDeprecated>> {
|
||||
}): Promise<CacheResult<string, ObjectsPermissionsByRoleId>> {
|
||||
return this.getRolesPermissionsFromCacheWithRecomputeService.getFromCacheWithRecompute(
|
||||
{
|
||||
workspaceId,
|
||||
@@ -194,7 +191,7 @@ export class WorkspacePermissionsCacheService {
|
||||
}: {
|
||||
workspaceId: string;
|
||||
roleIds?: string[];
|
||||
}): Promise<ObjectsPermissionsByRoleIdDeprecated> {
|
||||
}): Promise<ObjectsPermissionsByRoleId> {
|
||||
let roles: RoleEntity[] = [];
|
||||
|
||||
roles = await this.roleRepository.find({
|
||||
@@ -208,11 +205,10 @@ export class WorkspacePermissionsCacheService {
|
||||
const workspaceObjectMetadataCollection =
|
||||
await this.getWorkspaceObjectMetadataCollection(workspaceId);
|
||||
|
||||
const permissionsByRoleId: ObjectsPermissionsByRoleIdDeprecated = {};
|
||||
const permissionsByRoleId: ObjectsPermissionsByRoleId = {};
|
||||
|
||||
for (const role of roles) {
|
||||
const objectRecordsPermissions: ObjectsPermissionsDeprecated &
|
||||
ObjectsPermissions = {};
|
||||
const objectRecordsPermissions: ObjectsPermissions = {};
|
||||
|
||||
for (const objectMetadata of workspaceObjectMetadataCollection) {
|
||||
const { id: objectMetadataId, isSystem, standardId } = objectMetadata;
|
||||
@@ -288,10 +284,6 @@ export class WorkspacePermissionsCacheService {
|
||||
}
|
||||
|
||||
objectRecordsPermissions[objectMetadataId] = {
|
||||
canRead,
|
||||
canUpdate,
|
||||
canSoftDelete,
|
||||
canDestroy,
|
||||
canReadObjectRecords: canRead,
|
||||
canUpdateObjectRecords: canUpdate,
|
||||
canSoftDeleteObjectRecords: canSoftDelete,
|
||||
|
||||
Reference in New Issue
Block a user