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:
Abdul Rahman
2025-10-19 13:00:05 +05:30
committed by GitHub
parent f6d133f285
commit 3bec43696f
75 changed files with 909 additions and 489 deletions
@@ -8,6 +8,7 @@ import type { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/wor
import type { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto';
import type { UpdateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-step-input.dto';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
@@ -43,7 +44,10 @@ export class WorkflowToolWorkspaceService {
private readonly recordPositionService: RecordPositionService,
) {}
generateWorkflowTools(workspaceId: string, roleId: string): ToolSet {
generateWorkflowTools(
workspaceId: string,
rolePermissionConfig: RolePermissionConfig,
): ToolSet {
const tools: ToolSet = {};
tools.create_complete_workflow = {
@@ -88,7 +92,7 @@ This is the most efficient way for AI to create workflows as it handles all the
const workflowId = await this.createWorkflow({
workspaceId,
name: parameters.name,
roleId,
rolePermissionConfig,
});
const workflowVersionId = await this.createWorkflowVersion({
@@ -96,7 +100,7 @@ This is the most efficient way for AI to create workflows as it handles all the
workflowId,
trigger: parameters.trigger,
steps: parameters.steps,
roleId,
rolePermissionConfig,
});
if (parameters.stepPositions && parameters.stepPositions.length > 0) {
@@ -132,7 +136,7 @@ This is the most efficient way for AI to create workflows as it handles all the
workspaceId,
workflowId,
workflowVersionId,
roleId,
rolePermissionConfig,
});
}
@@ -400,17 +404,17 @@ This is the most efficient way for AI to create workflows as it handles all the
private async createWorkflow({
workspaceId,
name,
roleId,
rolePermissionConfig,
}: {
workspaceId: string;
name: string;
roleId: string;
rolePermissionConfig: RolePermissionConfig;
}): Promise<string> {
const workflowRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
'workflow',
{ roleId },
rolePermissionConfig,
);
const workflowPosition =
@@ -440,19 +444,19 @@ This is the most efficient way for AI to create workflows as it handles all the
workflowId,
trigger,
steps,
roleId,
rolePermissionConfig,
}: {
workspaceId: string;
workflowId: string;
trigger: WorkflowTrigger;
steps: WorkflowAction[];
roleId: string;
rolePermissionConfig: RolePermissionConfig;
}): Promise<string> {
const workflowVersionRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
'workflowVersion',
{ roleId },
rolePermissionConfig,
);
const versionPosition =
@@ -484,18 +488,18 @@ This is the most efficient way for AI to create workflows as it handles all the
workspaceId,
workflowId,
workflowVersionId,
roleId,
rolePermissionConfig,
}: {
workspaceId: string;
workflowId: string;
workflowVersionId: string;
roleId: string;
rolePermissionConfig: RolePermissionConfig;
}) {
const workflowRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
'workflow',
{ roleId },
rolePermissionConfig,
);
await workflowRepository.update(workflowId, {