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:
+4
-5
@@ -79,19 +79,18 @@ export abstract class CommonBaseQueryRunnerService {
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
const rolePermissionConfig = { unionOf: [roleId] };
|
||||
|
||||
const repository = workspaceDataSource.getRepository(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
false,
|
||||
roleId,
|
||||
rolePermissionConfig,
|
||||
authContext,
|
||||
);
|
||||
|
||||
return {
|
||||
workspaceDataSource,
|
||||
repository,
|
||||
isExecutedByApiKey: isDefined(authContext.apiKey),
|
||||
roleId,
|
||||
shouldBypassPermissionChecks: false,
|
||||
rolePermissionConfig,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+10
-17
@@ -31,6 +31,7 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
|
||||
@Injectable()
|
||||
export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerService {
|
||||
@@ -62,15 +63,11 @@ export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
workspaceDataSource,
|
||||
repository,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
} = await this.prepareQueryRunnerContext({
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
const { workspaceDataSource, repository, rolePermissionConfig } =
|
||||
await this.prepareQueryRunnerContext({
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const processedArgs = await this.processQueryArgs({
|
||||
authContext,
|
||||
@@ -98,10 +95,9 @@ export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
records: upsertedRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
roleId,
|
||||
rolePermissionConfig,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
shouldBypassPermissionChecks,
|
||||
});
|
||||
|
||||
return upsertedRecords;
|
||||
@@ -371,19 +367,17 @@ export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
records,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
roleId,
|
||||
rolePermissionConfig,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
shouldBypassPermissionChecks,
|
||||
}: {
|
||||
args: CreateManyQueryArgs;
|
||||
records: ObjectRecord[];
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
roleId?: string;
|
||||
rolePermissionConfig?: RolePermissionConfig;
|
||||
authContext: AuthContext;
|
||||
workspaceDataSource: WorkspaceDataSource;
|
||||
shouldBypassPermissionChecks: boolean;
|
||||
}): Promise<void> {
|
||||
if (!args.selectedFieldsResult.relations) {
|
||||
return;
|
||||
@@ -401,8 +395,7 @@ export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
rolePermissionConfig,
|
||||
selectedFields: args.selectedFieldsResult.select,
|
||||
});
|
||||
}
|
||||
|
||||
+6
-11
@@ -60,15 +60,11 @@ export class CommonFindManyQueryRunnerService extends CommonBaseQueryRunnerServi
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
workspaceDataSource,
|
||||
repository,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
} = await this.prepareQueryRunnerContext({
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
const { workspaceDataSource, repository, rolePermissionConfig } =
|
||||
await this.prepareQueryRunnerContext({
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const processedArgs = await this.processQueryArgs({
|
||||
authContext,
|
||||
@@ -195,8 +191,7 @@ export class CommonFindManyQueryRunnerService extends CommonBaseQueryRunnerServi
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
rolePermissionConfig,
|
||||
selectedFields: processedArgs.selectedFieldsResult.select,
|
||||
});
|
||||
}
|
||||
|
||||
+6
-11
@@ -46,15 +46,11 @@ export class CommonFindOneQueryRunnerService extends CommonBaseQueryRunnerServic
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
workspaceDataSource,
|
||||
repository,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
} = await this.prepareQueryRunnerContext({
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
const { workspaceDataSource, repository, rolePermissionConfig } =
|
||||
await this.prepareQueryRunnerContext({
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const processedArgs = await this.processQueryArgs({
|
||||
authContext,
|
||||
@@ -128,8 +124,7 @@ export class CommonFindOneQueryRunnerService extends CommonBaseQueryRunnerServic
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
rolePermissionConfig,
|
||||
selectedFields: args.selectedFieldsResult.select,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user