mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 06:34:05 -08:00
fix: PermissionChecker integration tests (no-changelog) (#8776)
This commit is contained in:
parent
0818824a72
commit
0e037add71
|
@ -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');
|
||||||
|
|
Loading…
Reference in a new issue