fix(core): Fix user comparison in same-user subworkflow caller policy (#7913)

https://linear.app/n8n/issue/PAY-992


https://community.n8n.io/t/executing-workflow-using-owner-role-created-by-another-user-fails/33443

---------

Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
Iván Ovejero
2023-12-06 13:27:11 +01:00
committed by GitHub
parent f5502cc628
commit 92bab72cff
7 changed files with 133 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
import type { INode, Workflow } from 'n8n-workflow';
import { NodeOperationError, SubworkflowOperationError } from 'n8n-workflow';
import { NodeOperationError, WorkflowOperationError } from 'n8n-workflow';
import type { FindOptionsWhere } from 'typeorm';
import { In } from 'typeorm';
import config from '@/config';
@@ -78,8 +78,8 @@ export class PermissionChecker {
static async checkSubworkflowExecutePolicy(
subworkflow: Workflow,
userId: string,
parentWorkflowId?: string,
parentWorkflowId: string,
node?: INode,
) {
/**
* Important considerations: both the current workflow and the parent can have empty IDs.
@@ -101,15 +101,22 @@ export class PermissionChecker {
policy = 'workflowsFromSameOwner';
}
const parentWorkflowOwner =
await Container.get(OwnershipService).getWorkflowOwnerCached(parentWorkflowId);
const subworkflowOwner = await Container.get(OwnershipService).getWorkflowOwnerCached(
subworkflow.id,
);
const errorToThrow = new SubworkflowOperationError(
`Target workflow ID ${subworkflow.id ?? ''} may not be called`,
subworkflowOwner.id === userId
const description =
subworkflowOwner.id === parentWorkflowOwner.id
? 'Change the settings of the sub-workflow so it can be called by this one.'
: `${subworkflowOwner.firstName} (${subworkflowOwner.email}) can make this change. You may need to tell them the ID of this workflow, which is ${subworkflow.id}`,
: `${subworkflowOwner.firstName} (${subworkflowOwner.email}) can make this change. You may need to tell them the ID of the sub-workflow, which is ${subworkflow.id}`;
const errorToThrow = new WorkflowOperationError(
`Target workflow ID ${subworkflow.id} may not be called`,
node,
description,
);
if (policy === 'none') {
@@ -130,10 +137,8 @@ export class PermissionChecker {
}
}
if (policy === 'workflowsFromSameOwner') {
if (subworkflowOwner?.id !== userId) {
throw errorToThrow;
}
if (policy === 'workflowsFromSameOwner' && subworkflowOwner?.id !== parentWorkflowOwner.id) {
throw errorToThrow;
}
}

View File

@@ -93,6 +93,12 @@ export function objectToError(errorObject: unknown, workflow: Workflow): Error {
error = new Error(errorObject.message as string);
}
if ('description' in errorObject) {
// @ts-expect-error Error descriptions are surfaced by the UI but
// not all backend errors account for this property yet.
error.description = errorObject.description as string;
}
if ('stack' in errorObject) {
// If there's a 'stack' property, set it on the new Error instance.
error.stack = errorObject.stack as string;
@@ -724,6 +730,7 @@ async function executeWorkflow(
workflowInfo: IExecuteWorkflowInfo,
additionalData: IWorkflowExecuteAdditionalData,
options: {
node?: INode;
parentWorkflowId?: string;
inputData?: INodeExecutionData[];
parentExecutionId?: string;
@@ -777,8 +784,8 @@ async function executeWorkflow(
await PermissionChecker.check(workflow, additionalData.userId);
await PermissionChecker.checkSubworkflowExecutePolicy(
workflow,
additionalData.userId,
options.parentWorkflowId,
options.parentWorkflowId!,
options.node,
);
// Create new additionalData to have different workflow loaded and to call

View File

@@ -173,10 +173,13 @@ export async function executeErrorWorkflow(
});
try {
const failedNode = workflowErrorData.execution?.lastNodeExecuted
? workflowInstance.getNode(workflowErrorData.execution?.lastNodeExecuted)
: undefined;
await PermissionChecker.checkSubworkflowExecutePolicy(
workflowInstance,
runningUser.id,
workflowErrorData.workflow.id,
workflowErrorData.workflow.id!,
failedNode ?? undefined,
);
} catch (error) {
const initialNode = workflowInstance.getStartNode();