From 2356fb0f0c247271ffa00d1cf25460e06212f1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 28 Nov 2023 14:16:47 +0100 Subject: [PATCH] feat(core): Set up endpoint for all existing roles with license flag (#7834) https://linear.app/n8n/issue/PAY-1034/create-endpoint-to-list-all-existing-roles --- packages/cli/src/License.ts | 4 + packages/cli/src/Server.ts | 2 + packages/cli/src/constants.ts | 1 + .../cli/src/controllers/e2e.controller.ts | 1 + .../cli/src/controllers/role.controller.ts | 24 ++++++ packages/cli/src/services/role.service.ts | 4 + .../cli/test/integration/role.api.test.ts | 82 +++++++++++++++++++ packages/cli/test/integration/shared/types.ts | 1 + .../integration/shared/utils/testServer.ts | 5 ++ 9 files changed, 124 insertions(+) create mode 100644 packages/cli/src/controllers/role.controller.ts create mode 100644 packages/cli/test/integration/role.api.test.ts diff --git a/packages/cli/src/License.ts b/packages/cli/src/License.ts index 4a12ad99f689f..fec5f6cd8cacb 100644 --- a/packages/cli/src/License.ts +++ b/packages/cli/src/License.ts @@ -223,6 +223,10 @@ export class License { return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS); } + isAdvancedPermissionsLicensed() { + return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_PERMISSIONS); + } + isDebugInEditorLicensed() { return this.isFeatureEnabled(LICENSE_FEATURES.DEBUG_IN_EDITOR); } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 99611163ce886..2835b7ddbe6f3 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -117,6 +117,7 @@ import { OrchestrationController } from './controllers/orchestration.controller' import { WorkflowHistoryController } from './workflows/workflowHistory/workflowHistory.controller.ee'; import { InvitationController } from './controllers/invitation.controller'; import { CollaborationService } from './collaboration/collaboration.service'; +import { RoleController } from './controllers/role.controller'; import { BadRequestError } from './errors/response-errors/bad-request.error'; import { NotFoundError } from './errors/response-errors/not-found.error'; @@ -298,6 +299,7 @@ export class Server extends AbstractServer { postHog, ), Container.get(VariablesController), + Container.get(RoleController), ]; if (isLdapEnabled()) { diff --git a/packages/cli/src/constants.ts b/packages/cli/src/constants.ts index a48ab3e94cc67..0668bf9923268 100644 --- a/packages/cli/src/constants.ts +++ b/packages/cli/src/constants.ts @@ -81,6 +81,7 @@ export const LICENSE_FEATURES = { BINARY_DATA_S3: 'feat:binaryDataS3', MULTIPLE_MAIN_INSTANCES: 'feat:multipleMainInstances', WORKER_VIEW: 'feat:workerView', + ADVANCED_PERMISSIONS: 'feat:advancedPermissions', } as const; export const LICENSE_QUOTAS = { diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index e96aaddc73199..5b1184229f392 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -71,6 +71,7 @@ export class E2EController { [LICENSE_FEATURES.BINARY_DATA_S3]: false, [LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES]: false, [LICENSE_FEATURES.WORKER_VIEW]: false, + [LICENSE_FEATURES.ADVANCED_PERMISSIONS]: false, }; constructor( diff --git a/packages/cli/src/controllers/role.controller.ts b/packages/cli/src/controllers/role.controller.ts new file mode 100644 index 0000000000000..f918e177176a9 --- /dev/null +++ b/packages/cli/src/controllers/role.controller.ts @@ -0,0 +1,24 @@ +import { License } from '@/License'; +import { Get, RestController } from '@/decorators'; +import { RoleService } from '@/services/role.service'; +import { Service } from 'typedi'; + +@Service() +@RestController('/roles') +export class RoleController { + constructor( + private readonly roleService: RoleService, + private readonly license: License, + ) {} + + @Get('/') + async listRoles() { + return this.roleService.listRoles().map((role) => { + if (role.scope === 'global' && role.name === 'admin') { + return { ...role, isAvailable: this.license.isAdvancedPermissionsLicensed() }; + } + + return { ...role, isAvailable: true }; + }); + } +} diff --git a/packages/cli/src/services/role.service.ts b/packages/cli/src/services/role.service.ts index e10523223593e..5b81b8b99ffc3 100644 --- a/packages/cli/src/services/role.service.ts +++ b/packages/cli/src/services/role.service.ts @@ -56,6 +56,10 @@ export class RoleService { { scope: 'workflow', name: 'editor' }, ]; + listRoles() { + return this.roles; + } + private isValid(scope: RoleScopes, name: RoleNames) { return this.roles.some((r) => r.scope === scope && r.name === name); } diff --git a/packages/cli/test/integration/role.api.test.ts b/packages/cli/test/integration/role.api.test.ts new file mode 100644 index 0000000000000..d1aa25782e789 --- /dev/null +++ b/packages/cli/test/integration/role.api.test.ts @@ -0,0 +1,82 @@ +import { License } from '@/License'; + +import * as utils from './shared/utils/'; +import * as testDb from './shared/testDb'; +import { mockInstance } from '../shared/mocking'; +import { createAdmin, createMember, createOwner } from './shared/db/users'; + +import type { SuperAgentTest } from 'supertest'; +import type { User } from '@db/entities/User'; + +const testServer = utils.setupTestServer({ endpointGroups: ['role'] }); + +const license = mockInstance(License, { + isAdvancedPermissionsLicensed: jest.fn().mockReturnValue(true), + isWithinUsersLimit: jest.fn().mockReturnValue(true), +}); + +describe('GET /roles', () => { + let owner: User; + let admin: User; + let member: User; + + let ownerAgent: SuperAgentTest; + let adminAgent: SuperAgentTest; + let memberAgent: SuperAgentTest; + + let toAgent: Record = {}; + + beforeAll(async () => { + await testDb.truncate(['User']); + + owner = await createOwner(); + admin = await createAdmin(); + member = await createMember(); + + ownerAgent = testServer.authAgentFor(owner); + adminAgent = testServer.authAgentFor(admin); + memberAgent = testServer.authAgentFor(member); + + toAgent = { + owner: ownerAgent, + admin: adminAgent, + member: memberAgent, + }; + }); + + describe('with advanced permissions licensed', () => { + test.each(['owner', 'admin', 'member'])('should return all roles to %s', async (user) => { + license.isAdvancedPermissionsLicensed.mockReturnValue(true); + + const response = await toAgent[user].get('/roles').expect(200); + + expect(response.body.data).toEqual([ + { scope: 'global', name: 'owner', isAvailable: true }, + { scope: 'global', name: 'member', isAvailable: true }, + { scope: 'global', name: 'admin', isAvailable: true }, + { scope: 'workflow', name: 'owner', isAvailable: true }, + { scope: 'credential', name: 'owner', isAvailable: true }, + { scope: 'credential', name: 'user', isAvailable: true }, + { scope: 'workflow', name: 'editor', isAvailable: true }, + ]); + }); + }); + + describe('with advanced permissions not licensed', () => { + test.each(['owner', 'admin', 'member'])('should return all roles to %s', async (user) => { + license.isAdvancedPermissionsLicensed.mockReturnValue(false); + + const response = await toAgent[user].get('/roles').expect(200); + + expect(response.body.data).toEqual([ + { scope: 'global', name: 'owner', isAvailable: true }, + { scope: 'global', name: 'member', isAvailable: true }, + { scope: 'global', name: 'admin', isAvailable: false }, + { scope: 'workflow', name: 'owner', isAvailable: true }, + { scope: 'credential', name: 'owner', isAvailable: true }, + { scope: 'credential', name: 'user', isAvailable: true }, + { scope: 'workflow', name: 'editor', isAvailable: true }, + ]); + }); + }); +}); diff --git a/packages/cli/test/integration/shared/types.ts b/packages/cli/test/integration/shared/types.ts index ffc47b78f75ad..b0ea1e0215b4d 100644 --- a/packages/cli/test/integration/shared/types.ts +++ b/packages/cli/test/integration/shared/types.ts @@ -30,6 +30,7 @@ type EndpointGroup = | 'executions' | 'workflowHistory' | 'binaryData' + | 'role' | 'invitations'; export interface SetupProps { diff --git a/packages/cli/test/integration/shared/utils/testServer.ts b/packages/cli/test/integration/shared/utils/testServer.ts index ec57f70732274..fbd99aef13717 100644 --- a/packages/cli/test/integration/shared/utils/testServer.ts +++ b/packages/cli/test/integration/shared/utils/testServer.ts @@ -295,6 +295,11 @@ export const setupTestServer = ({ const { BinaryDataController } = await import('@/controllers/binaryData.controller'); registerController(app, config, Container.get(BinaryDataController)); break; + + case 'role': + const { RoleController } = await import('@/controllers/role.controller'); + registerController(app, config, Container.get(RoleController)); + break; } } }