fix: PermissionChecker integration tests (no-changelog) (#8776)

This commit is contained in:
Val 2024-03-05 18:18:34 +00:00 committed by GitHub
parent 0818824a72
commit 0e037add71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -27,6 +27,8 @@ import type { SaveCredentialFunction } from '../integration/shared/types';
import { mockNodeTypesData } from '../unit/Helpers'; import { mockNodeTypesData } from '../unit/Helpers';
import { affixRoleToSaveCredential } from '../integration/shared/db/credentials'; import { affixRoleToSaveCredential } from '../integration/shared/db/credentials';
import { createOwner, createUser } from '../integration/shared/db/users'; import { createOwner, createUser } from '../integration/shared/db/users';
import { SharedCredentialsRepository } from '@/databases/repositories/sharedCredentials.repository';
import type { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
export const toTargetCallErrorMsg = (subworkflowId: string) => export const toTargetCallErrorMsg = (subworkflowId: string) =>
`Target workflow ID ${subworkflowId} may not be called`; `Target workflow ID ${subworkflowId} may not be called`;
@ -69,8 +71,34 @@ export function createSubworkflow({
}); });
} }
const createWorkflow = async (nodes: INode[], workflowOwner?: User): Promise<WorkflowEntity> => {
const workflowDetails = {
id: uuid(),
name: 'test',
active: false,
connections: {},
nodeTypes: mockNodeTypes,
nodes,
};
const workflowEntity = await Container.get(WorkflowRepository).save(workflowDetails);
if (workflowOwner) {
await Container.get(SharedWorkflowRepository).save({
workflow: workflowEntity,
user: workflowOwner,
role: 'workflow:owner',
});
}
return workflowEntity;
};
let saveCredential: SaveCredentialFunction; let saveCredential: SaveCredentialFunction;
let owner: User;
let member: User;
const mockNodeTypes = mockInstance(NodeTypes); const mockNodeTypes = mockInstance(NodeTypes);
mockInstance(LoadNodesAndCredentials, { mockInstance(LoadNodesAndCredentials, {
loadedNodes: mockNodeTypesData(['start', 'actionNetwork']), loadedNodes: mockNodeTypesData(['start', 'actionNetwork']),
@ -78,17 +106,29 @@ mockInstance(LoadNodesAndCredentials, {
let permissionChecker: PermissionChecker; let permissionChecker: PermissionChecker;
let license: LicenseMocker;
beforeAll(async () => { beforeAll(async () => {
await testDb.init(); await testDb.init();
saveCredential = affixRoleToSaveCredential('credential:owner'); saveCredential = affixRoleToSaveCredential('credential:owner');
permissionChecker = Container.get(PermissionChecker); permissionChecker = Container.get(PermissionChecker);
[owner, member] = await Promise.all([createOwner(), createUser()]);
license = new LicenseMocker();
license.mock(Container.get(License));
license.setDefaults({
features: ['feat:sharing'],
});
});
beforeEach(() => {
license.reset();
}); });
describe('check()', () => { describe('check()', () => {
const workflowId = randomPositiveDigit().toString();
beforeEach(async () => { beforeEach(async () => {
await testDb.truncate(['Workflow', 'Credentials']); await testDb.truncate(['Workflow', 'Credentials']);
}); });
@ -98,7 +138,6 @@ describe('check()', () => {
}); });
test('should allow if workflow has no creds', async () => { test('should allow if workflow has no creds', async () => {
const userId = uuid();
const nodes: INode[] = [ const nodes: INode[] = [
{ {
id: uuid(), id: uuid(),
@ -110,7 +149,11 @@ describe('check()', () => {
}, },
]; ];
expect(async () => await permissionChecker.check(workflowId, userId, nodes)).not.toThrow(); const workflow = await createWorkflow(nodes, member);
await expect(
permissionChecker.check(workflow.id, member.id, workflow.nodes),
).resolves.not.toThrow();
}); });
test('should allow if requesting user is instance owner', async () => { test('should allow if requesting user is instance owner', async () => {
@ -132,14 +175,25 @@ describe('check()', () => {
}, },
]; ];
expect(async () => await permissionChecker.check(workflowId, owner.id, nodes)).not.toThrow(); const workflow = await createWorkflow(nodes);
await expect(
permissionChecker.check(workflow.id, owner.id, workflow.nodes),
).resolves.not.toThrow();
}); });
test('should allow if workflow creds are valid subset', async () => { test('should allow if workflow creds are valid subset (shared credential)', async () => {
const [owner, member] = await Promise.all([createOwner(), createUser()]);
const ownerCred = await saveCredential(randomCred(), { user: owner }); const ownerCred = await saveCredential(randomCred(), { user: owner });
const memberCred = await saveCredential(randomCred(), { user: member }); const memberCred = await saveCredential(randomCred(), { user: member });
await Container.get(SharedCredentialsRepository).save(
Container.get(SharedCredentialsRepository).create({
credentialsId: ownerCred.id,
userId: member.id,
role: 'credential:user',
}),
);
const nodes: INode[] = [ const nodes: INode[] = [
{ {
id: uuid(), id: uuid(),
@ -171,7 +225,111 @@ describe('check()', () => {
}, },
]; ];
expect(async () => await permissionChecker.check(workflowId, owner.id, nodes)).not.toThrow(); const workflow = await createWorkflow(nodes, member);
await expect(
permissionChecker.check(workflow.id, member.id, workflow.nodes),
).resolves.not.toThrow();
});
test('should allow if workflow creds are valid subset (shared workflow)', async () => {
const ownerCred = await saveCredential(randomCred(), { user: owner });
const memberCred = await saveCredential(randomCred(), { user: member });
const nodes: INode[] = [
{
id: uuid(),
name: 'Action Network',
type: 'n8n-nodes-base.actionNetwork',
parameters: {},
typeVersion: 1,
position: [0, 0],
credentials: {
actionNetworkApi: {
id: ownerCred.id,
name: ownerCred.name,
},
},
},
{
id: uuid(),
name: 'Action Network 2',
type: 'n8n-nodes-base.actionNetwork',
parameters: {},
typeVersion: 1,
position: [0, 0],
credentials: {
actionNetworkApi: {
id: memberCred.id,
name: memberCred.name,
},
},
},
];
const workflow = await createWorkflow(nodes, member);
await Container.get(SharedWorkflowRepository).save(
Container.get(SharedWorkflowRepository).create({
workflowId: workflow.id,
userId: owner.id,
role: 'workflow:editor',
}),
);
await expect(
permissionChecker.check(workflow.id, member.id, workflow.nodes),
).resolves.not.toThrow();
});
test('should deny if workflow creds are valid subset but sharing is disabled', async () => {
const [owner, member] = await Promise.all([createOwner(), createUser()]);
const ownerCred = await saveCredential(randomCred(), { user: owner });
const memberCred = await saveCredential(randomCred(), { user: member });
await Container.get(SharedCredentialsRepository).save(
Container.get(SharedCredentialsRepository).create({
credentialsId: ownerCred.id,
userId: member.id,
role: 'credential:user',
}),
);
const nodes: INode[] = [
{
id: uuid(),
name: 'Action Network',
type: 'n8n-nodes-base.actionNetwork',
parameters: {},
typeVersion: 1,
position: [0, 0],
credentials: {
actionNetworkApi: {
id: ownerCred.id,
name: ownerCred.name,
},
},
},
{
id: uuid(),
name: 'Action Network 2',
type: 'n8n-nodes-base.actionNetwork',
parameters: {},
typeVersion: 1,
position: [0, 0],
credentials: {
actionNetworkApi: {
id: memberCred.id,
name: memberCred.name,
},
},
},
];
const workflow = await createWorkflow(nodes, member);
license.disable('feat:sharing');
await expect(permissionChecker.check(workflow.id, member.id, nodes)).rejects.toThrow();
}); });
test('should deny if workflow creds are not valid subset', async () => { test('should deny if workflow creds are not valid subset', async () => {
@ -179,69 +337,46 @@ describe('check()', () => {
const memberCred = await saveCredential(randomCred(), { user: member }); const memberCred = await saveCredential(randomCred(), { user: member });
const workflowDetails = { const nodes: INode[] = [
id: randomPositiveDigit().toString(), {
name: 'test', id: uuid(),
active: false, name: 'Action Network',
connections: {}, type: 'n8n-nodes-base.actionNetwork',
nodeTypes: mockNodeTypes, parameters: {},
nodes: [ typeVersion: 1,
{ position: [0, 0] as [number, number],
id: uuid(), credentials: {
name: 'Action Network', actionNetworkApi: {
type: 'n8n-nodes-base.actionNetwork', id: memberCred.id,
parameters: {}, name: memberCred.name,
typeVersion: 1,
position: [0, 0] as [number, number],
credentials: {
actionNetworkApi: {
id: memberCred.id,
name: memberCred.name,
},
}, },
}, },
{ },
id: uuid(), {
name: 'Action Network 2', id: uuid(),
type: 'n8n-nodes-base.actionNetwork', name: 'Action Network 2',
parameters: {}, type: 'n8n-nodes-base.actionNetwork',
typeVersion: 1, parameters: {},
position: [0, 0] as [number, number], typeVersion: 1,
credentials: { position: [0, 0] as [number, number],
actionNetworkApi: { credentials: {
id: 'non-existing-credential-id', actionNetworkApi: {
name: 'Non-existing credential name', id: 'non-existing-credential-id',
}, name: 'Non-existing credential name',
}, },
}, },
], },
}; ];
const workflowEntity = await Container.get(WorkflowRepository).save(workflowDetails); const workflow = await createWorkflow(nodes, member);
await Container.get(SharedWorkflowRepository).save({ await expect(permissionChecker.check(workflow.id, member.id, workflow.nodes)).rejects.toThrow();
workflow: workflowEntity,
user: member,
role: 'workflow:owner',
});
await expect(
permissionChecker.check(workflowDetails.id, member.id, workflowDetails.nodes),
).rejects.toThrow();
}); });
}); });
describe('checkSubworkflowExecutePolicy()', () => { describe('checkSubworkflowExecutePolicy()', () => {
const ownershipService = mockInstance(OwnershipService); const ownershipService = mockInstance(OwnershipService);
let license: LicenseMocker;
beforeAll(() => {
license = new LicenseMocker();
license.mock(Container.get(License));
license.enable('feat:sharing');
});
describe('no caller policy', () => { describe('no caller policy', () => {
test('should fall back to N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION', async () => { test('should fall back to N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION', async () => {
config.set('workflows.callerPolicyDefaultOption', 'none'); config.set('workflows.callerPolicyDefaultOption', 'none');