n8n/packages/cli/src/workflows/workflows.services.ee.ts

213 lines
6.4 KiB
TypeScript
Raw Normal View History

import type { DeleteResult, EntityManager } from 'typeorm';
import { In, Not } from 'typeorm';
import * as Db from '@/Db';
import * as ResponseHelper from '@/ResponseHelper';
import * as WorkflowHelpers from '@/WorkflowHelpers';
import type { ICredentialsDb } from '@/Interfaces';
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import type { Role } from '@db/entities/Role';
import type { User } from '@db/entities/User';
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { RoleService } from '@/role/role.service';
import { UserService } from '@/user/user.service';
import { WorkflowsService } from './workflows.services';
import type {
CredentialUsedByWorkflow,
WorkflowWithSharingsAndCredentials,
WorkflowForList,
} from './workflows.types';
import { EECredentialsService as EECredentials } from '@/credentials/credentials.service.ee';
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
refactor: Workflow sharing bug bash fixes (#4888) * fix: Prevent workflows with only manual trigger from being activated * fix: Fix workflow id when sharing from workflows list * fix: Update sharing modal translations * fix: Allow sharees to disable workflows and fix issue with unique key when removing a user * refactor: Improve error messages and change logging level to be less verbose * fix: Broken user removal transfer issue * feat: Implement workflow sharing BE telemetry * chore: temporarily add sharing env vars * feat: Implement BE telemetry for workflow sharing * fix: Prevent issues with possibly missing workflow id * feat: Replace WorkflowSharing flag references (no-changelog) (#4918) * ci: Block all external network calls in tests (no-changelog) (#4930) * setup nock to prevent tests from making any external requests * mock all calls to posthog sdk * feat: Replace WorkflowSharing flag references (no-changelog) Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <netroy@users.noreply.github.com> * refactor: Remove temporary feature flag for workflow sharing * refactor: add sharing_role to both manual and node executions * refactor: Allow changing name, position and disabled of read only nodes * feat: Overhaul dynamic translations for local and cloud (#4943) * feat: Overhaul dynamic translations for local and cloud * fix: remove type casting * chore: remove unused translations * fix: fix workflow sharing translation * test: Fix broken test * refactor: remove unnecessary import * refactor: Minor code improvements * refactor: rename dynamicTranslations to contextBasedTranslationKeys * fix: fix type imports * refactor: Consolidate sharing feature check * feat: update cred sharing unavailable translations * feat: update upgrade message when user management not available * fix: rename plan names to Pro and Power * feat: update translations to no longer contain plan names * wip: subworkflow permissions * feat: add workflowsFromSameOwner caller policy * feat: Fix subworkflow permissions * shared entites should check for role when deleting users * refactor: remove circular dependency * role filter shouldn't be an array * fixed role issue * fix: Corrected behavior when removing users * feat: show instance owner credential sharing message only if isnt sharee * feat: update workflow caller policy caller ids labels * feat: update upgrade plan links to contain instance ids * fix: show check errors below creds message only to owner * fix(editor): Hide usage page on cloud * fix: update credential validation error message for sharee * fix(core): Remove duplicate import * fix(editor): Extending deployment types * feat: Overhaul contextual translations (#4992) feat: update how contextual translations work * refactor: improve messageing for subworkflow permissions * test: Fix issue with user deletion and transfer * fix: Explicitly throw error message so it can be displayed in UI Co-authored-by: Alex Grozav <alex@grozav.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <netroy@users.noreply.github.com> Co-authored-by: freyamade <freya@n8n.io> Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
2022-12-21 07:42:07 -08:00
import { NodeOperationError } from 'n8n-workflow';
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,
): Promise<{ ownsWorkflow: boolean; workflow?: WorkflowEntity }> {
const sharing = await this.getSharing(user, workflowId, ['workflow', 'role'], {
allowGlobalOwner: false,
});
if (!sharing || sharing.role.name !== 'owner') return { ownsWorkflow: false };
const { workflow } = sharing;
return { ownsWorkflow: true, workflow };
}
static async getSharings(
transaction: EntityManager,
workflowId: string,
): Promise<SharedWorkflow[]> {
const workflow = await transaction.findOne(WorkflowEntity, {
where: { id: workflowId },
relations: ['shared'],
});
return workflow?.shared ?? [];
}
static async pruneSharings(
transaction: EntityManager,
workflowId: string,
userIds: string[],
): Promise<DeleteResult> {
return transaction.delete(SharedWorkflow, {
workflowId,
userId: Not(In(userIds)),
});
}
static async share(
transaction: EntityManager,
workflow: WorkflowEntity,
shareWithIds: string[],
): Promise<SharedWorkflow[]> {
const [users, role] = await Promise.all([
UserService.getByIds(transaction, shareWithIds),
RoleService.trxGet(transaction, { scope: 'workflow', name: 'editor' }),
]);
const newSharedWorkflows = users.reduce<SharedWorkflow[]>((acc, user) => {
if (user.isPending) {
return acc;
}
const entity: Partial<SharedWorkflow> = {
workflowId: workflow.id,
userId: user.id,
roleId: role?.id,
};
acc.push(Db.collections.SharedWorkflow.create(entity));
return acc;
}, []);
return transaction.save(newSharedWorkflows);
}
static addOwnerId(workflow: WorkflowForList, workflowOwnerRole: Role): void {
const ownerId = workflow.shared?.find(
({ roleId }) => String(roleId) === workflowOwnerRole.id,
)?.userId;
workflow.ownedBy = ownerId ? { id: ownerId } : null;
delete workflow.shared;
}
static addOwnerAndSharings(workflow: WorkflowWithSharingsAndCredentials): void {
workflow.ownedBy = null;
workflow.sharedWith = [];
if (!workflow.usedCredentials) {
workflow.usedCredentials = [];
}
workflow.shared?.forEach(({ user, role }) => {
const { id, email, firstName, lastName } = user;
if (role.name === 'owner') {
workflow.ownedBy = { id, email, firstName, lastName };
return;
}
workflow.sharedWith?.push({ id, email, firstName, lastName });
});
delete workflow.shared;
}
static async addCredentialsToWorkflow(
workflow: WorkflowWithSharingsAndCredentials,
currentUser: User,
): Promise<void> {
workflow.usedCredentials = [];
const userCredentials = await EECredentials.getAll(currentUser, { disableGlobalRole: true });
const credentialIdsUsedByWorkflow = new Set<string>();
workflow.nodes.forEach((node) => {
if (!node.credentials) {
return;
}
Object.keys(node.credentials).forEach((credentialType) => {
const credential = node.credentials?.[credentialType];
if (!credential?.id) {
return;
}
credentialIdsUsedByWorkflow.add(credential.id);
});
});
const workflowCredentials = await EECredentials.getMany({
where: {
id: In(Array.from(credentialIdsUsedByWorkflow)),
},
relations: ['shared', 'shared.user', 'shared.role'],
});
const userCredentialIds = userCredentials.map((credential) => credential.id);
workflowCredentials.forEach((credential) => {
const credentialId = credential.id;
const workflowCredential: CredentialUsedByWorkflow = {
id: credentialId,
name: credential.name,
type: credential.type,
currentUserHasAccess: userCredentialIds.includes(credentialId),
sharedWith: [],
ownedBy: null,
};
credential.shared?.forEach(({ user, role }) => {
const { id, email, firstName, lastName } = user;
if (role.name === 'owner') {
workflowCredential.ownedBy = { id, email, firstName, lastName };
} else {
workflowCredential.sharedWith?.push({ id, email, firstName, lastName });
}
});
workflow.usedCredentials?.push(workflowCredential);
});
}
static validateCredentialPermissionsToUser(
workflow: WorkflowEntity,
allowedCredentials: ICredentialsDb[],
) {
workflow.nodes.forEach((node) => {
if (!node.credentials) {
return;
}
Object.keys(node.credentials).forEach((credentialType) => {
const credentialId = node.credentials?.[credentialType].id;
if (credentialId === undefined) return;
const matchedCredential = allowedCredentials.find(({ id }) => id === credentialId);
if (!matchedCredential) {
throw new Error('The workflow contains credentials that you do not have access to');
}
});
});
}
static async preventTampering(workflow: WorkflowEntity, workflowId: string, user: User) {
const previousVersion = await EEWorkflowsService.get({ id: workflowId });
if (!previousVersion) {
throw new ResponseHelper.NotFoundError('Workflow not found');
}
const allCredentials = await EECredentials.getAll(user);
try {
return WorkflowHelpers.validateWorkflowCredentialUsage(
workflow,
previousVersion,
allCredentials,
);
} catch (error) {
refactor: Workflow sharing bug bash fixes (#4888) * fix: Prevent workflows with only manual trigger from being activated * fix: Fix workflow id when sharing from workflows list * fix: Update sharing modal translations * fix: Allow sharees to disable workflows and fix issue with unique key when removing a user * refactor: Improve error messages and change logging level to be less verbose * fix: Broken user removal transfer issue * feat: Implement workflow sharing BE telemetry * chore: temporarily add sharing env vars * feat: Implement BE telemetry for workflow sharing * fix: Prevent issues with possibly missing workflow id * feat: Replace WorkflowSharing flag references (no-changelog) (#4918) * ci: Block all external network calls in tests (no-changelog) (#4930) * setup nock to prevent tests from making any external requests * mock all calls to posthog sdk * feat: Replace WorkflowSharing flag references (no-changelog) Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <netroy@users.noreply.github.com> * refactor: Remove temporary feature flag for workflow sharing * refactor: add sharing_role to both manual and node executions * refactor: Allow changing name, position and disabled of read only nodes * feat: Overhaul dynamic translations for local and cloud (#4943) * feat: Overhaul dynamic translations for local and cloud * fix: remove type casting * chore: remove unused translations * fix: fix workflow sharing translation * test: Fix broken test * refactor: remove unnecessary import * refactor: Minor code improvements * refactor: rename dynamicTranslations to contextBasedTranslationKeys * fix: fix type imports * refactor: Consolidate sharing feature check * feat: update cred sharing unavailable translations * feat: update upgrade message when user management not available * fix: rename plan names to Pro and Power * feat: update translations to no longer contain plan names * wip: subworkflow permissions * feat: add workflowsFromSameOwner caller policy * feat: Fix subworkflow permissions * shared entites should check for role when deleting users * refactor: remove circular dependency * role filter shouldn't be an array * fixed role issue * fix: Corrected behavior when removing users * feat: show instance owner credential sharing message only if isnt sharee * feat: update workflow caller policy caller ids labels * feat: update upgrade plan links to contain instance ids * fix: show check errors below creds message only to owner * fix(editor): Hide usage page on cloud * fix: update credential validation error message for sharee * fix(core): Remove duplicate import * fix(editor): Extending deployment types * feat: Overhaul contextual translations (#4992) feat: update how contextual translations work * refactor: improve messageing for subworkflow permissions * test: Fix issue with user deletion and transfer * fix: Explicitly throw error message so it can be displayed in UI Co-authored-by: Alex Grozav <alex@grozav.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <netroy@users.noreply.github.com> Co-authored-by: freyamade <freya@n8n.io> Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
2022-12-21 07:42:07 -08:00
if (error instanceof NodeOperationError) {
throw new ResponseHelper.BadRequestError(error.message);
}
throw new ResponseHelper.BadRequestError(
'Invalid workflow credentials - make sure you have access to all credentials and try again.',
);
}
}
}