mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 12:44:07 -08:00
refactor: Forbid access to workflows when enterprise features is unavailable (#4635) (no-changelog)
* refactor: Forbid access to workflows when enterprise features is unavailable
This commit is contained in:
parent
bb5ebdf6c9
commit
e1a491edce
|
@ -52,7 +52,6 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'
|
|||
|
||||
import config from '@/config';
|
||||
import { User } from '@db/entities/User';
|
||||
import { whereClause } from '@/WorkflowHelpers';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import * as ActiveExecutions from '@/ActiveExecutions';
|
||||
import { createErrorExecution } from '@/GenericHelpers';
|
||||
|
@ -60,6 +59,7 @@ import { WORKFLOW_REACTIVATE_INITIAL_TIMEOUT, WORKFLOW_REACTIVATE_MAX_TIMEOUT }
|
|||
import { NodeTypes } from '@/NodeTypes';
|
||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { whereClause } from './UserManagement/UserManagementHelper';
|
||||
|
||||
const WEBHOOK_PROD_UNREGISTERED_HINT = `The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)`;
|
||||
|
||||
|
|
|
@ -43,13 +43,14 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import { ICredentialsDb, WhereClause } from '@/Interfaces';
|
||||
import { ICredentialsDb } from '@/Interfaces';
|
||||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||
import { User } from '@db/entities/User';
|
||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
||||
import { CredentialTypes } from '@/CredentialTypes';
|
||||
import { whereClause } from './UserManagement/UserManagementHelper';
|
||||
|
||||
const mockNodeTypes: INodeTypes = {
|
||||
nodeTypes: {} as INodeTypeData,
|
||||
|
@ -738,28 +739,6 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `where` clause for a `find()` or `findOne()` operation
|
||||
* in the `shared_workflow` or `shared_credentials` tables.
|
||||
*/
|
||||
export function whereClause({
|
||||
user,
|
||||
entityType,
|
||||
entityId = '',
|
||||
}: {
|
||||
user: User;
|
||||
entityType: 'workflow' | 'credentials';
|
||||
entityId?: string;
|
||||
}): WhereClause {
|
||||
const where: WhereClause = entityId ? { [entityType]: { id: entityId } } : {};
|
||||
|
||||
if (user.globalRole.name !== 'owner') {
|
||||
where.user = { id: user.id };
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a credential if it has been shared with a user.
|
||||
*/
|
||||
|
|
|
@ -24,7 +24,7 @@ import { WorkflowExecute } from 'n8n-core';
|
|||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import PCancelable from 'p-cancelable';
|
||||
import { Repository } from 'typeorm';
|
||||
import type { FindOperator, Repository } from 'typeorm';
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { Url } from 'url';
|
||||
|
@ -710,7 +710,7 @@ export interface IWorkflowExecuteProcess {
|
|||
workflowExecute: WorkflowExecute;
|
||||
}
|
||||
|
||||
export type WhereClause = Record<string, { id: string }>;
|
||||
export type WhereClause = Record<string, { [key: string]: string | FindOperator<unknown> }>;
|
||||
|
||||
// ----------------------------------
|
||||
// community nodes
|
||||
|
|
|
@ -89,7 +89,7 @@ import * as Queue from '@/Queue';
|
|||
import { InternalHooksManager } from '@/InternalHooksManager';
|
||||
import { getCredentialTranslationPath } from '@/TranslationHelpers';
|
||||
import { WEBHOOK_METHODS } from '@/WebhookHelpers';
|
||||
import { getSharedWorkflowIds, whereClause } from '@/WorkflowHelpers';
|
||||
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
||||
|
||||
import { nodesController } from '@/api/nodes.api';
|
||||
import { workflowsController } from '@/workflows/workflows.controller';
|
||||
|
@ -122,6 +122,7 @@ import {
|
|||
isEmailSetUp,
|
||||
isSharingEnabled,
|
||||
isUserManagementEnabled,
|
||||
whereClause,
|
||||
} from '@/UserManagement/UserManagementHelper';
|
||||
import * as Db from '@/Db';
|
||||
import {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Role } from '@db/entities/Role';
|
|||
import { AuthenticatedRequest } from '@/requests';
|
||||
import config from '@/config';
|
||||
import { getWebhookBaseUrl } from '../WebhookHelpers';
|
||||
import { WhereClause } from '@/Interfaces';
|
||||
|
||||
export async function getWorkflowOwner(workflowId: string | number): Promise<User> {
|
||||
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
|
||||
|
@ -210,3 +211,31 @@ export function rightDiff<T1, T2>(
|
|||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `where` clause for a TypeORM entity search,
|
||||
* checking for member access if the user is not an owner.
|
||||
*/
|
||||
export function whereClause({
|
||||
user,
|
||||
entityType,
|
||||
entityId = '',
|
||||
roles = [],
|
||||
}: {
|
||||
user: User;
|
||||
entityType: 'workflow' | 'credentials';
|
||||
entityId?: string;
|
||||
roles?: string[];
|
||||
}): WhereClause {
|
||||
const where: WhereClause = entityId ? { [entityType]: { id: entityId } } : {};
|
||||
|
||||
// TODO: Decide if owner access should be restricted
|
||||
if (user.globalRole.name !== 'owner') {
|
||||
where.user = { id: user.id };
|
||||
if (roles?.length) {
|
||||
where.role = { name: In(roles) };
|
||||
}
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecuteWorkflowInfo,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
IRun,
|
||||
|
@ -62,7 +61,7 @@ import * as Push from '@/Push';
|
|||
import * as ResponseHelper from '@/ResponseHelper';
|
||||
import * as WebhookHelpers from '@/WebhookHelpers';
|
||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||
import { getUserById, getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
|
||||
import { getUserById, getWorkflowOwner, whereClause } from '@/UserManagement/UserManagementHelper';
|
||||
import { findSubworkflowStart } from '@/utils';
|
||||
import { PermissionChecker } from './UserManagement/PermissionChecker';
|
||||
|
||||
|
@ -852,7 +851,7 @@ export async function getWorkflowData(
|
|||
|
||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||
relations,
|
||||
where: WorkflowHelpers.whereClause({
|
||||
where: whereClause({
|
||||
user,
|
||||
entityType: 'workflow',
|
||||
entityId: workflowInfo.id,
|
||||
|
|
|
@ -33,7 +33,6 @@ import {
|
|||
ITransferNodeTypes,
|
||||
IWorkflowErrorData,
|
||||
IWorkflowExecutionDataProcess,
|
||||
WhereClause,
|
||||
} from '@/Interfaces';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||
|
@ -41,7 +40,7 @@ import { WorkflowRunner } from '@/WorkflowRunner';
|
|||
import config from '@/config';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { User } from '@db/entities/User';
|
||||
import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
|
||||
import { getWorkflowOwner, whereClause } from '@/UserManagement/UserManagementHelper';
|
||||
|
||||
const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType');
|
||||
|
||||
|
@ -573,40 +572,14 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
|
|||
return workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `where` clause for a TypeORM entity search,
|
||||
* checking for member access if the user is not an owner.
|
||||
*/
|
||||
export function whereClause({
|
||||
user,
|
||||
entityType,
|
||||
entityId = '',
|
||||
}: {
|
||||
user: User;
|
||||
entityType: 'workflow' | 'credentials';
|
||||
entityId?: string;
|
||||
}): WhereClause {
|
||||
const where: WhereClause = entityId ? { [entityType]: { id: entityId } } : {};
|
||||
|
||||
// TODO: Decide if owner access should be restricted
|
||||
if (user.globalRole.name !== 'owner') {
|
||||
where.user = { id: user.id };
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IDs of the workflows that have been shared with the user.
|
||||
* Returns all IDs if user is global owner (see `whereClause`)
|
||||
*/
|
||||
export async function getSharedWorkflowIds(user: User): Promise<number[]> {
|
||||
export async function getSharedWorkflowIds(user: User, roles?: string[]): Promise<number[]> {
|
||||
const sharedWorkflows = await Db.collections.SharedWorkflow.find({
|
||||
relations: ['workflow'],
|
||||
where: whereClause({
|
||||
user,
|
||||
entityType: 'workflow',
|
||||
}),
|
||||
relations: ['workflow', 'role'],
|
||||
where: whereClause({ user, entityType: 'workflow', roles }),
|
||||
});
|
||||
|
||||
return sharedWorkflows.map(({ workflow }) => workflow.id);
|
||||
|
|
|
@ -14,7 +14,6 @@ import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
|||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import * as TagHelpers from '@/TagHelpers';
|
||||
import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee';
|
||||
import { WorkflowsService } from './workflows.services';
|
||||
import { IExecutionPushResponse } from '@/Interfaces';
|
||||
import * as GenericHelpers from '@/GenericHelpers';
|
||||
|
||||
|
@ -197,7 +196,7 @@ EEWorkflowController.post(
|
|||
EEWorkflowController.get(
|
||||
'/',
|
||||
ResponseHelper.send(async (req: WorkflowRequest.GetAll) => {
|
||||
const workflows = (await WorkflowsService.getMany(
|
||||
const workflows = (await EEWorkflows.getMany(
|
||||
req.user,
|
||||
req.query.filter,
|
||||
)) as unknown as WorkflowEntity[];
|
||||
|
@ -222,7 +221,7 @@ EEWorkflowController.patch(
|
|||
|
||||
const safeWorkflow = await EEWorkflows.preventTampering(updateData, workflowId, req.user);
|
||||
|
||||
const updatedWorkflow = await WorkflowsService.update(
|
||||
const updatedWorkflow = await EEWorkflows.update(
|
||||
req.user,
|
||||
safeWorkflow,
|
||||
workflowId,
|
||||
|
@ -256,6 +255,6 @@ EEWorkflowController.post(
|
|||
|
||||
req.body.workflowData.nodes = safeWorkflow.nodes;
|
||||
|
||||
return WorkflowsService.runManually(req.body, req.user, GenericHelpers.getSessionId(req));
|
||||
return EEWorkflows.runManually(req.body, req.user, GenericHelpers.getSessionId(req));
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -9,7 +9,6 @@ import * as Db from '@/Db';
|
|||
import * as GenericHelpers from '@/GenericHelpers';
|
||||
import * as ResponseHelper from '@/ResponseHelper';
|
||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||
import { whereClause } from '@/CredentialsHelper';
|
||||
import { IWorkflowResponse, IExecutionPushResponse } from '@/Interfaces';
|
||||
import config from '@/config';
|
||||
import * as TagHelpers from '@/TagHelpers';
|
||||
|
@ -23,6 +22,7 @@ import type { WorkflowRequest } from '@/requests';
|
|||
import { isBelowOnboardingThreshold } from '@/WorkflowHelpers';
|
||||
import { EEWorkflowController } from './workflows.controller.ee';
|
||||
import { WorkflowsService } from './workflows.services';
|
||||
import { whereClause } from '@/UserManagement/UserManagementHelper';
|
||||
|
||||
export const workflowsController = express.Router();
|
||||
|
||||
|
@ -201,7 +201,7 @@ workflowsController.get(
|
|||
ResponseHelper.send(async (req: WorkflowRequest.Get) => {
|
||||
const { id: workflowId } = req.params;
|
||||
|
||||
let relations = ['workflow', 'workflow.tags'];
|
||||
let relations = ['workflow', 'workflow.tags', 'role'];
|
||||
|
||||
if (config.getEnv('workflowTagsDisabled')) {
|
||||
relations = relations.filter((relation) => relation !== 'workflow.tags');
|
||||
|
@ -213,6 +213,7 @@ workflowsController.get(
|
|||
user: req.user,
|
||||
entityType: 'workflow',
|
||||
entityId: workflowId,
|
||||
roles: ['owner'],
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -252,7 +253,14 @@ workflowsController.patch(
|
|||
const { tags, ...rest } = req.body;
|
||||
Object.assign(updateData, rest);
|
||||
|
||||
const updatedWorkflow = await WorkflowsService.update(req.user, updateData, workflowId, tags);
|
||||
const updatedWorkflow = await WorkflowsService.update(
|
||||
req.user,
|
||||
updateData,
|
||||
workflowId,
|
||||
tags,
|
||||
false,
|
||||
['owner'],
|
||||
);
|
||||
|
||||
const { id, ...remainder } = updatedWorkflow;
|
||||
|
||||
|
@ -275,11 +283,12 @@ workflowsController.delete(
|
|||
await externalHooks.run('workflow.delete', [workflowId]);
|
||||
|
||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||
relations: ['workflow'],
|
||||
relations: ['workflow', 'role'],
|
||||
where: whereClause({
|
||||
user: req.user,
|
||||
entityType: 'workflow',
|
||||
entityId: workflowId,
|
||||
roles: ['owner'],
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -11,8 +11,14 @@ import { UserService } from '@/user/user.service';
|
|||
import { WorkflowsService } from './workflows.services';
|
||||
import type { WorkflowWithSharingsAndCredentials } from './workflows.types';
|
||||
import { EECredentialsService as EECredentials } from '@/credentials/credentials.service.ee';
|
||||
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
||||
|
||||
export class EEWorkflowsService extends WorkflowsService {
|
||||
static async getWorkflowIdsForUser(user: User) {
|
||||
// Get all workflows regardless of role
|
||||
return getSharedWorkflowIds(user);
|
||||
}
|
||||
|
||||
static async isOwned(
|
||||
user: User,
|
||||
workflowId: string,
|
||||
|
|
|
@ -6,7 +6,6 @@ import * as Db from '@/Db';
|
|||
import { InternalHooksManager } from '@/InternalHooksManager';
|
||||
import * as ResponseHelper from '@/ResponseHelper';
|
||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||
import { whereClause } from '@/CredentialsHelper';
|
||||
import config from '@/config';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { User } from '@db/entities/User';
|
||||
|
@ -21,6 +20,7 @@ import { WorkflowRunner } from '@/WorkflowRunner';
|
|||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||
import * as TestWebhooks from '@/TestWebhooks';
|
||||
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
||||
import { whereClause } from '@/UserManagement/UserManagementHelper';
|
||||
|
||||
export interface IGetWorkflowsQueryFilter {
|
||||
id?: number | string;
|
||||
|
@ -93,8 +93,13 @@ export class WorkflowsService {
|
|||
return Db.collections.Workflow.findOne(workflow, options);
|
||||
}
|
||||
|
||||
// Warning: this function is overriden by EE to disregard role list.
|
||||
static async getWorkflowIdsForUser(user: User, roles?: string[]) {
|
||||
return getSharedWorkflowIds(user, roles);
|
||||
}
|
||||
|
||||
static async getMany(user: User, rawFilter: string) {
|
||||
const sharedWorkflowIds = await getSharedWorkflowIds(user);
|
||||
const sharedWorkflowIds = await this.getWorkflowIdsForUser(user, ['owner']);
|
||||
if (sharedWorkflowIds.length === 0) {
|
||||
// return early since without shared workflows there can be no hits
|
||||
// (note: getSharedWorkflowIds() returns _all_ workflow ids for global owners)
|
||||
|
@ -172,15 +177,16 @@ export class WorkflowsService {
|
|||
workflow: WorkflowEntity,
|
||||
workflowId: string,
|
||||
tags?: string[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
forceSave?: boolean,
|
||||
roles?: string[],
|
||||
): Promise<WorkflowEntity> {
|
||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||
relations: ['workflow'],
|
||||
relations: ['workflow', 'role'],
|
||||
where: whereClause({
|
||||
user,
|
||||
entityType: 'workflow',
|
||||
entityId: workflowId,
|
||||
roles,
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue