implement "acting on behalf of user" for workflows and agents (#15103)
## Summary **Step 1 of 2:** Implements the "acting on behalf of user" concept for workflows and agents to prevent permission escalation and maintain proper audit trails. ## Problem Previously, workflows and agents would bypass permissions regardless of who initiated them, allowing users to escalate their privileges by triggering workflows that performed actions they couldn't do directly. ## Solution ### For Workflows Introduced `WorkflowExecutionContext` service that determines execution mode: - **Manual triggers/test button**: Uses user's roleId for permissions, user's identity for `createdBy` - **Automated triggers** (cron, database events, webhooks): Bypasses permissions, uses workflow identity ### For Agents **In Chat:** - Always act on behalf of the user - Use user's roleId for permission checks - Use user's identity for `createdBy` # Step 1 vs Step 2 ### ✅ Step 1 (This PR): Acting on Behalf Concept - Introduced `isActingOnBehalfOfUser` boolean concept - Single roleId used for permission checks (user's OR system bypass) - `createdBy` field properly attributes actions to initiator - Prevents permission escalation in user-initiated flows ### 🔜 Step 2 (Future): Multi-Role Permission Support - Support role intersection: `{ intersection: ['roleA', 'roleB'] }` - Support role union: `{ union: ['roleA', 'roleB', 'roleC'] }` - Enable user+agent collaboration scenarios - Update `WorkspaceEntityManager` and `WorkspaceDatasource` to handle multiple roleIds --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
getRecordInputSchema,
|
||||
} from 'src/engine/metadata-modules/agent/utils/agent-tool-schema.utils';
|
||||
import { isWorkflowRunObject } from 'src/engine/metadata-modules/agent/utils/is-workflow-run-object.util';
|
||||
import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
@@ -30,7 +31,11 @@ export class ToolService {
|
||||
private readonly findRecordsService: FindRecordsService,
|
||||
) {}
|
||||
|
||||
async listTools(roleId: string, workspaceId: string): Promise<ToolSet> {
|
||||
async listTools(
|
||||
roleId: string,
|
||||
workspaceId: string,
|
||||
actorContext?: ActorMetadata,
|
||||
): Promise<ToolSet> {
|
||||
const tools: ToolSet = {};
|
||||
|
||||
const { data: rolesPermissions } =
|
||||
@@ -70,6 +75,7 @@ export class ToolService {
|
||||
objectRecord: parameters.input,
|
||||
workspaceId,
|
||||
roleId,
|
||||
createdBy: actorContext,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
+3
-3
@@ -85,9 +85,9 @@ export class CreateRecordService {
|
||||
const insertResult = await repository.insert({
|
||||
...transformedObjectRecord,
|
||||
position,
|
||||
createdBy: {
|
||||
source: roleId ? FieldActorSource.AGENT : FieldActorSource.WORKFLOW,
|
||||
name: roleId ? 'Agent' : 'Workflow',
|
||||
createdBy: params.createdBy ?? {
|
||||
source: FieldActorSource.WORKFLOW,
|
||||
name: 'Workflow',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+2
@@ -1,8 +1,10 @@
|
||||
import { type ObjectRecordProperties } from 'src/engine/core-modules/record-crud/types/object-record-properties.type';
|
||||
import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
|
||||
export type CreateRecordParams = {
|
||||
objectName: string;
|
||||
objectRecord: ObjectRecordProperties;
|
||||
workspaceId: string;
|
||||
roleId?: string;
|
||||
createdBy?: ActorMetadata;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user