2024-01-16 05:15:29 -08:00
|
|
|
import { Service } from 'typedi';
|
2024-07-24 03:51:01 -07:00
|
|
|
import type { INode } from 'n8n-workflow';
|
|
|
|
import { CredentialAccessError, NodeOperationError } from 'n8n-workflow';
|
2024-01-16 05:15:29 -08:00
|
|
|
|
2023-07-31 02:37:09 -07:00
|
|
|
import { OwnershipService } from '@/services/ownership.service';
|
2023-11-10 06:04:26 -08:00
|
|
|
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
2024-05-17 01:53:15 -07:00
|
|
|
import { ProjectService } from '@/services/project.service';
|
2022-11-11 02:14:45 -08:00
|
|
|
|
2024-01-16 05:15:29 -08:00
|
|
|
@Service()
|
2022-11-11 02:14:45 -08:00
|
|
|
export class PermissionChecker {
|
2024-01-16 05:15:29 -08:00
|
|
|
constructor(
|
|
|
|
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
|
|
|
private readonly ownershipService: OwnershipService,
|
2024-05-17 01:53:15 -07:00
|
|
|
private readonly projectService: ProjectService,
|
2024-01-16 05:15:29 -08:00
|
|
|
) {}
|
|
|
|
|
2022-11-11 02:14:45 -08:00
|
|
|
/**
|
2024-05-17 01:53:15 -07:00
|
|
|
* Check if a workflow has the ability to execute based on the projects it's apart of.
|
2022-11-11 02:14:45 -08:00
|
|
|
*/
|
2024-05-17 01:53:15 -07:00
|
|
|
async check(workflowId: string, nodes: INode[]) {
|
|
|
|
const homeProject = await this.ownershipService.getWorkflowProjectCached(workflowId);
|
|
|
|
const homeProjectOwner = await this.ownershipService.getProjectOwnerCached(homeProject.id);
|
|
|
|
if (homeProject.type === 'personal' && homeProjectOwner?.hasGlobalScope('credential:list')) {
|
|
|
|
// Workflow belongs to a project by a user with privileges
|
|
|
|
// so all credentials are usable. Skip credential checks.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const projectIds = await this.projectService.findProjectsWorkflowIsIn(workflowId);
|
2024-02-21 05:47:02 -08:00
|
|
|
const credIdsToNodes = this.mapCredIdsToNodes(nodes);
|
2022-11-11 02:14:45 -08:00
|
|
|
|
|
|
|
const workflowCredIds = Object.keys(credIdsToNodes);
|
|
|
|
|
|
|
|
if (workflowCredIds.length === 0) return;
|
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
const accessible = await this.sharedCredentialsRepository.getFilteredAccessibleCredentials(
|
|
|
|
projectIds,
|
|
|
|
workflowCredIds,
|
|
|
|
);
|
2022-11-21 23:37:52 -08:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
for (const credentialsId of workflowCredIds) {
|
|
|
|
if (!accessible.includes(credentialsId)) {
|
|
|
|
const nodeToFlag = credIdsToNodes[credentialsId][0];
|
|
|
|
throw new CredentialAccessError(nodeToFlag, credentialsId, workflowId);
|
|
|
|
}
|
2022-11-21 23:37:52 -08:00
|
|
|
}
|
2022-11-11 02:14:45 -08:00
|
|
|
}
|
|
|
|
|
2024-02-21 05:47:02 -08:00
|
|
|
private mapCredIdsToNodes(nodes: INode[]) {
|
|
|
|
return nodes.reduce<{ [credentialId: string]: INode[] }>((map, node) => {
|
|
|
|
if (node.disabled || !node.credentials) return map;
|
|
|
|
|
|
|
|
Object.values(node.credentials).forEach((cred) => {
|
|
|
|
if (!cred.id) {
|
|
|
|
throw new NodeOperationError(node, 'Node uses invalid credential', {
|
|
|
|
description: 'Please recreate the credential.',
|
|
|
|
level: 'warning',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
map[cred.id] = map[cred.id] ? [...map[cred.id], node] : [node];
|
|
|
|
});
|
|
|
|
|
|
|
|
return map;
|
|
|
|
}, {});
|
2022-11-11 02:14:45 -08:00
|
|
|
}
|
|
|
|
}
|