feat(core): Add credential runtime checks and prevent tampering in manual run (#4481)
* ✨ Create `PermissionChecker` * ⚡ Adjust helper * 🔥 Remove superseded helpers * ⚡ Use `PermissionChecker` * 🧪 Add test for dynamic router switching * ⚡ Simplify checks * ⚡ Export utils * ⚡ Add missing `init` method * 🧪 Write tests for `PermissionChecker` * 📘 Update types * 🧪 Fix tests * ✨ Set up `runManually()` * ⚡ Refactor to reuse methods * 🧪 Clear shared tables first * 🔀 Adjust merge * ⚡ Adjust imports
This commit is contained in:
@@ -145,97 +145,6 @@ export async function getUserById(userId: string): Promise<User> {
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function checkPermissionsForExecution(
|
||||
workflow: Workflow,
|
||||
userId: string,
|
||||
): Promise<boolean> {
|
||||
const credentialIds = new Set();
|
||||
const nodeNames = Object.keys(workflow.nodes);
|
||||
const credentialUsedBy = new Map();
|
||||
// Iterate over all nodes
|
||||
nodeNames.forEach((nodeName) => {
|
||||
const node = workflow.nodes[nodeName];
|
||||
if (node.disabled === true) {
|
||||
// If a node is disabled there is no need to check its credentials
|
||||
return;
|
||||
}
|
||||
// And check if any of the nodes uses credentials.
|
||||
if (node.credentials) {
|
||||
const credentialNames = Object.keys(node.credentials);
|
||||
// For every credential this node uses
|
||||
credentialNames.forEach((credentialName) => {
|
||||
const credentialDetail = node.credentials![credentialName];
|
||||
// If it does not contain an id, it means it is a very old
|
||||
// workflow. Nowadays it should not happen anymore.
|
||||
// Migrations should handle the case where a credential does
|
||||
// not have an id.
|
||||
if (credentialDetail.id === null) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`The credential on node '${node.name}' is not valid. Please open the workflow and set it to a valid value.`,
|
||||
);
|
||||
}
|
||||
if (!credentialDetail.id) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Error initializing workflow: credential ID not present. Please open the workflow and save it to fix this error. [Node: '${node.name}']`,
|
||||
);
|
||||
}
|
||||
credentialIds.add(credentialDetail.id.toString());
|
||||
if (!credentialUsedBy.has(credentialDetail.id)) {
|
||||
credentialUsedBy.set(credentialDetail.id, node);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Now that we obtained all credential IDs used by this workflow, we can
|
||||
// now check if the owner of this workflow has access to all of them.
|
||||
|
||||
const ids = Array.from(credentialIds);
|
||||
|
||||
if (ids.length === 0) {
|
||||
// If the workflow does not use any credentials, then we're fine
|
||||
return true;
|
||||
}
|
||||
// If this check happens on top, we may get
|
||||
// uninitialized db errors.
|
||||
// Db is certainly initialized if workflow uses credentials.
|
||||
const user = await getUserById(userId);
|
||||
if (user.globalRole.name === 'owner') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for the user's permission to all used credentials
|
||||
const credentialsWithAccess = await Db.collections.SharedCredentials.find({
|
||||
where: {
|
||||
user: { id: userId },
|
||||
credentials: In(ids),
|
||||
},
|
||||
});
|
||||
|
||||
// Considering the user needs to have access to all credentials
|
||||
// then both arrays (allowed credentials vs used credentials)
|
||||
// must be the same length
|
||||
if (ids.length !== credentialsWithAccess.length) {
|
||||
credentialsWithAccess.forEach((credential) => {
|
||||
credentialUsedBy.delete(credential.credentialId.toString());
|
||||
});
|
||||
|
||||
// Find the first missing node from the Set - this is arbitrarily fetched
|
||||
const firstMissingCredentialNode = credentialUsedBy.values().next().value as INode;
|
||||
throw new NodeOperationError(
|
||||
firstMissingCredentialNode,
|
||||
'This node does not have access to the required credential',
|
||||
{
|
||||
description:
|
||||
'Maybe the credential was removed or you have lost access to it. Try contacting the owner if this credential does not belong to you',
|
||||
},
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL contains an auth-excluded endpoint.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user