From e95905f4b9fd3ba1845c97d7126f84932a1bc264 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 28 Nov 2023 15:08:59 +0200 Subject: [PATCH 01/10] feat: update middleware to no longer use roles --- packages/@n8n/permissions/src/types.ts | 43 ++++++++++++++-------- packages/cli/src/permissions/roles.ts | 50 ++++++++++++++------------ packages/editor-ui/src/router.ts | 47 ++++++++++++++---------- 3 files changed, 86 insertions(+), 54 deletions(-) diff --git a/packages/@n8n/permissions/src/types.ts b/packages/@n8n/permissions/src/types.ts index 0a14440922d80..a017a0396f698 100644 --- a/packages/@n8n/permissions/src/types.ts +++ b/packages/@n8n/permissions/src/types.ts @@ -1,12 +1,17 @@ export type DefaultOperations = 'create' | 'read' | 'update' | 'delete' | 'list'; export type Resource = - | 'workflow' + | 'auditLogs' + | 'communityNodes' + | 'credential' + | 'externalSecretsStore' + | 'ldap' + | 'logStreaming' + | 'sourceControl' + | 'sso' | 'tag' | 'user' - | 'credential' | 'variable' - | 'sourceControl' - | 'externalSecretsStore'; + | 'workflow'; export type ResourceScope< R extends Resource, @@ -15,25 +20,35 @@ export type ResourceScope< export type WildcardScope = `${Resource}:*` | '*'; -export type WorkflowScope = ResourceScope<'workflow', DefaultOperations | 'share'>; -export type TagScope = ResourceScope<'tag'>; -export type UserScope = ResourceScope<'user'>; +export type AuditLogsScope = ResourceScope<'auditLogs', 'manage'>; +export type CommunityNodesScope = ResourceScope<'communityNodes', 'manage'>; export type CredentialScope = ResourceScope<'credential', DefaultOperations | 'share'>; -export type VariableScope = ResourceScope<'variable'>; -export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>; export type ExternalSecretStoreScope = ResourceScope< 'externalSecretsStore', - DefaultOperations | 'refresh' + DefaultOperations | 'manage' | 'refresh' >; +export type LdapScope = ResourceScope<'ldap', 'manage'>; +export type LogStreamingScope = ResourceScope<'logStreaming', 'manage'>; +export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>; +export type SsoScope = ResourceScope<'sso', 'manage'>; +export type TagScope = ResourceScope<'tag'>; +export type UserScope = ResourceScope<'user'>; +export type VariableScope = ResourceScope<'variable'>; +export type WorkflowScope = ResourceScope<'workflow', DefaultOperations | 'share'>; export type Scope = - | WorkflowScope + | AuditLogsScope + | CommunityNodesScope + | CredentialScope + | ExternalSecretStoreScope + | LdapScope + | LogStreamingScope + | SourceControlScope + | SsoScope | TagScope | UserScope - | CredentialScope | VariableScope - | SourceControlScope - | ExternalSecretStoreScope; + | WorkflowScope; export type ScopeLevel = 'global' | 'project' | 'resource'; export type GetScopeLevel = Record; diff --git a/packages/cli/src/permissions/roles.ts b/packages/cli/src/permissions/roles.ts index 131406aee854d..bd278b400dd49 100644 --- a/packages/cli/src/permissions/roles.ts +++ b/packages/cli/src/permissions/roles.ts @@ -1,50 +1,56 @@ import type { Scope } from '@n8n/permissions'; export const ownerPermissions: Scope[] = [ - 'workflow:create', - 'workflow:read', - 'workflow:update', - 'workflow:delete', - 'workflow:list', - 'workflow:share', - 'user:create', - 'user:read', - 'user:update', - 'user:delete', - 'user:list', + 'auditLogs:manage', + 'communityNodes:manage', 'credential:create', 'credential:read', 'credential:update', 'credential:delete', 'credential:list', 'credential:share', - 'variable:create', - 'variable:read', - 'variable:update', - 'variable:delete', - 'variable:list', - 'sourceControl:pull', - 'sourceControl:push', - 'sourceControl:manage', 'externalSecretsStore:create', 'externalSecretsStore:read', 'externalSecretsStore:update', 'externalSecretsStore:delete', 'externalSecretsStore:list', 'externalSecretsStore:refresh', + 'externalSecretsStore:manage', + 'ldap:manage', + 'logStreaming:manage', + 'sourceControl:pull', + 'sourceControl:push', + 'sourceControl:manage', + 'sso:manage', 'tag:create', 'tag:read', 'tag:update', 'tag:delete', 'tag:list', + 'user:create', + 'user:read', + 'user:update', + 'user:delete', + 'user:list', + 'variable:create', + 'variable:read', + 'variable:update', + 'variable:delete', + 'variable:list', + 'workflow:create', + 'workflow:read', + 'workflow:update', + 'workflow:delete', + 'workflow:list', + 'workflow:share', ]; export const adminPermissions: Scope[] = ownerPermissions.concat(); export const memberPermissions: Scope[] = [ - 'user:list', - 'variable:list', - 'variable:read', 'tag:create', 'tag:read', 'tag:update', 'tag:list', + 'user:list', + 'variable:list', + 'variable:read', ]; diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts index 034a064afdca8..22c7b79dcaeef 100644 --- a/packages/editor-ui/src/router.ts +++ b/packages/editor-ui/src/router.ts @@ -480,10 +480,7 @@ export const routes = [ settingsView: SettingsPersonalView, }, meta: { - middleware: ['authenticated', 'role'], - middlewareOptions: { - role: [ROLE.Owner, ROLE.Member], - }, + middleware: ['authenticated'], telemetry: { pageCategory: 'settings', getProperties(route: RouteLocation) { @@ -548,9 +545,11 @@ export const routes = [ settingsView: SettingsSourceControl, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'sourceControl:manage', + }, }, telemetry: { pageCategory: 'settings', @@ -569,9 +568,11 @@ export const routes = [ settingsView: SettingsExternalSecrets, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'externalSecretsStore:manage', + }, }, telemetry: { pageCategory: 'settings', @@ -590,13 +591,15 @@ export const routes = [ settingsView: SettingsSso, }, meta: { - middleware: ['authenticated', 'role', 'custom'], + middleware: ['authenticated', 'rbac', 'custom'], middlewareOptions: { custom: () => { const settingsStore = useSettingsStore(); return !settingsStore.isDesktopDeployment; }, - role: [ROLE.Owner], + rbac: { + scope: 'sso:manage', + }, }, telemetry: { pageCategory: 'settings', @@ -615,9 +618,11 @@ export const routes = [ settingsView: SettingsLogStreamingView, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'logStreaming:manage', + }, }, telemetry: { pageCategory: 'settings', @@ -641,9 +646,11 @@ export const routes = [ settingsView: SettingsCommunityNodesView, }, meta: { - middleware: ['authenticated', 'role', 'custom'], + middleware: ['authenticated', 'rbac', 'custom'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'communityNodes:manage', + }, custom: () => { const settingsStore = useSettingsStore(); return settingsStore.isCommunityNodesFeatureEnabled; @@ -679,9 +686,11 @@ export const routes = [ settingsView: SettingsLdapView, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'ldap:manage', + }, }, }, }, @@ -692,12 +701,14 @@ export const routes = [ settingsView: SettingsAuditLogs, }, meta: { - middleware: ['authenticated', 'role', 'custom'], + middleware: ['authenticated', 'rbac', 'custom'], middlewareOptions: { custom: () => { return !!useStorage('audit-logs').value; }, - role: [ROLE.Owner], + rbac: { + scope: 'auditLogs:manage', + }, }, telemetry: { pageCategory: 'settings', From a4bed4b4b535d20d30c5ee2ba484dfd03e94b9c4 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 28 Nov 2023 15:28:15 +0200 Subject: [PATCH 02/10] fix: update various user permission checks --- .../editor-ui/src/components/MainSidebar.vue | 4 +- .../rbac/checks/__tests__/hasScope.test.ts | 4 +- .../checks/__tests__/isDefaultUser.test.ts | 27 +++++++++ packages/editor-ui/src/rbac/checks/index.ts | 1 + .../src/rbac/checks/isDefaultUser.ts | 12 ++++ packages/editor-ui/src/rbac/middleware.ts | 2 + .../middleware/__tests__/defaultUser.test.ts | 55 +++++++++++++++++++ .../src/rbac/middleware/defaultUser.ts | 15 +++++ packages/editor-ui/src/router.ts | 6 +- packages/editor-ui/src/stores/rbac.store.ts | 4 +- packages/editor-ui/src/types/rbac.ts | 15 ++++- packages/editor-ui/src/types/router.ts | 2 + .../editor-ui/src/views/SettingsUsersView.vue | 2 +- 13 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 packages/editor-ui/src/rbac/checks/__tests__/isDefaultUser.test.ts create mode 100644 packages/editor-ui/src/rbac/checks/isDefaultUser.ts create mode 100644 packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts create mode 100644 packages/editor-ui/src/rbac/middleware/defaultUser.ts diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index 9da524a48e2d8..3f7352505c5de 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -177,9 +177,7 @@ export default defineComponent({ return accessibleRoute !== null; }, showUserArea(): boolean { - return hasPermission(['role'], { - role: [ROLE.Member, ROLE.Owner], - }); + return hasPermission(['authenticated']); }, workflowExecution(): IExecutionResponse | null { return this.workflowsStore.getWorkflowExecution; diff --git a/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts b/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts index 80feaaaf96657..bf33c75e5609d 100644 --- a/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts +++ b/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts @@ -1,6 +1,6 @@ import { useRBACStore } from '@/stores/rbac.store'; import { hasScope } from '@/rbac/checks/hasScope'; -import type { HasScopeOptions } from '@n8n/permissions'; +import type { ScopeOptions } from '@n8n/permissions'; vi.mock('@/stores/rbac.store', () => ({ useRBACStore: vi.fn(), @@ -19,7 +19,7 @@ describe('Checks', () => { } as unknown as ReturnType); const scope = 'workflow:read'; - const options: HasScopeOptions = { mode: 'allOf' }; + const options: ScopeOptions = { mode: 'allOf' }; const projectId = 'proj123'; const resourceType = 'workflow'; const resourceId = 'res123'; diff --git a/packages/editor-ui/src/rbac/checks/__tests__/isDefaultUser.test.ts b/packages/editor-ui/src/rbac/checks/__tests__/isDefaultUser.test.ts new file mode 100644 index 0000000000000..815c0217683ca --- /dev/null +++ b/packages/editor-ui/src/rbac/checks/__tests__/isDefaultUser.test.ts @@ -0,0 +1,27 @@ +import { useUsersStore } from '@/stores/users.store'; +import { isDefaultUser } from '@/rbac/checks/isDefaultUser'; + +vi.mock('@/stores/users.store', () => ({ + useUsersStore: vi.fn(), +})); + +describe('Checks', () => { + describe('isDefaultUser()', () => { + it('should return false if user not logged in', () => { + vi.mocked(useUsersStore).mockReturnValue({ currentUser: null } as ReturnType< + typeof useUsersStore + >); + + expect(isDefaultUser()).toBe(false); + }); + + it('should return true if user is default user', () => { + const mockUser = { id: 'user123', name: 'Test User', isDefaultUser: true }; + vi.mocked(useUsersStore).mockReturnValue({ currentUser: mockUser } as unknown as ReturnType< + typeof useUsersStore + >); + + expect(isDefaultUser()).toBe(mockUser.isDefaultUser); + }); + }); +}); diff --git a/packages/editor-ui/src/rbac/checks/index.ts b/packages/editor-ui/src/rbac/checks/index.ts index cb8acc3533fec..17088ed8b1f57 100644 --- a/packages/editor-ui/src/rbac/checks/index.ts +++ b/packages/editor-ui/src/rbac/checks/index.ts @@ -1,6 +1,7 @@ export * from './hasRole'; export * from './hasScope'; export * from './isAuthenticated'; +export * from './isDefaultUser'; export * from './isEnterpriseFeatureEnabled'; export * from './isGuest'; export * from './isValid'; diff --git a/packages/editor-ui/src/rbac/checks/isDefaultUser.ts b/packages/editor-ui/src/rbac/checks/isDefaultUser.ts new file mode 100644 index 0000000000000..f0f812df69c86 --- /dev/null +++ b/packages/editor-ui/src/rbac/checks/isDefaultUser.ts @@ -0,0 +1,12 @@ +import { useUsersStore } from '@/stores/users.store'; +import type { DefaultUserMiddlewareOptions, RBACPermissionCheck } from '@/types/rbac'; + +export const isDefaultUser: RBACPermissionCheck = () => { + const usersStore = useUsersStore(); + const currentUser = usersStore.currentUser; + + if (currentUser) { + return currentUser.isDefaultUser; + } + return false; +}; diff --git a/packages/editor-ui/src/rbac/middleware.ts b/packages/editor-ui/src/rbac/middleware.ts index e8c0ed5ae3561..e6805b381338d 100644 --- a/packages/editor-ui/src/rbac/middleware.ts +++ b/packages/editor-ui/src/rbac/middleware.ts @@ -5,6 +5,7 @@ import { guestMiddleware } from '@/rbac/middleware/guest'; import { rbacMiddleware } from '@/rbac/middleware/rbac'; import { roleMiddleware } from '@/rbac/middleware/role'; import { customMiddleware } from '@/rbac/middleware/custom'; +import { defaultUserMiddleware } from '@/rbac/middleware/defaultUser'; type Middleware = { [key in RouterMiddlewareType]: RouterMiddleware; @@ -13,6 +14,7 @@ type Middleware = { export const middleware: Middleware = { authenticated: authenticatedMiddleware, custom: customMiddleware, + defaultUser: defaultUserMiddleware, enterprise: enterpriseMiddleware, guest: guestMiddleware, rbac: rbacMiddleware, diff --git a/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts b/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts new file mode 100644 index 0000000000000..d469da503c90e --- /dev/null +++ b/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts @@ -0,0 +1,55 @@ +import { useUsersStore } from '@/stores/users.store'; +import { VIEWS } from '@/constants'; +import { defaultUserMiddleware } from '@/rbac/middleware/defaultUser'; +import type { RouteLocationNormalized } from 'vue-router'; +import { authenticatedMiddleware } from '@/rbac/middleware/authenticated'; + +vi.mock('@/stores/users.store', () => ({ + useUsersStore: vi.fn(), +})); + +describe('Middleware', () => { + describe('defaultUser', () => { + it('should redirect to homepage if user not logged in', async () => { + vi.mocked(useUsersStore).mockReturnValue({ + currentUser: null, + } as ReturnType); + + const nextMock = vi.fn(); + const toMock = { query: {} } as RouteLocationNormalized; + const fromMock = {} as RouteLocationNormalized; + + await defaultUserMiddleware(toMock, fromMock, nextMock, {}); + + expect(nextMock).toHaveBeenCalledWith({ name: VIEWS.HOMEPAGE }); + }); + + it('should redirect to homepage if user is not default user', async () => { + vi.mocked(useUsersStore).mockReturnValue({ + currentUser: { id: '123', isDefaultUser: false }, + } as ReturnType); + + const nextMock = vi.fn(); + const toMock = { query: {} } as RouteLocationNormalized; + const fromMock = {} as RouteLocationNormalized; + + await defaultUserMiddleware(toMock, fromMock, nextMock, {}); + + expect(nextMock).toHaveBeenCalledWith({ name: VIEWS.HOMEPAGE }); + }); + + it('should allow navigation if a current user is present', async () => { + vi.mocked(useUsersStore).mockReturnValue({ + currentUser: { id: '123', isDefaultUser: true }, + } as ReturnType); + + const nextMock = vi.fn(); + const toMock = { query: {} } as RouteLocationNormalized; + const fromMock = {} as RouteLocationNormalized; + + await defaultUserMiddleware(toMock, fromMock, nextMock, {}); + + expect(nextMock).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/editor-ui/src/rbac/middleware/defaultUser.ts b/packages/editor-ui/src/rbac/middleware/defaultUser.ts new file mode 100644 index 0000000000000..bab12869f9928 --- /dev/null +++ b/packages/editor-ui/src/rbac/middleware/defaultUser.ts @@ -0,0 +1,15 @@ +import type { RouterMiddleware } from '@/types/router'; +import { VIEWS } from '@/constants'; +import type { DefaultUserMiddlewareOptions } from '@/types/rbac'; +import { isDefaultUser } from '@/rbac/checks'; + +export const defaultUserMiddleware: RouterMiddleware = async ( + to, + from, + next, +) => { + const valid = isDefaultUser(); + if (!valid) { + return next({ name: VIEWS.HOMEPAGE }); + } +}; diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts index 22c7b79dcaeef..bdefe91865223 100644 --- a/packages/editor-ui/src/router.ts +++ b/packages/editor-ui/src/router.ts @@ -9,7 +9,6 @@ import type { } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router'; import { runExternalHook } from '@/utils/externalHooks'; -import { ROLE } from '@/utils/userUtils'; import { useSettingsStore } from '@/stores/settings.store'; import { useTemplatesStore } from '@/stores/templates.store'; import { useUIStore } from '@/stores/ui.store'; @@ -406,10 +405,7 @@ export const routes = [ default: SetupView, }, meta: { - middleware: ['role'], - middlewareOptions: { - role: [ROLE.Default], - }, + middleware: ['defaultUser'], telemetry: { pageCategory: 'auth', }, diff --git a/packages/editor-ui/src/stores/rbac.store.ts b/packages/editor-ui/src/stores/rbac.store.ts index 3667b519ac1ac..fbcb6b80b6fa2 100644 --- a/packages/editor-ui/src/stores/rbac.store.ts +++ b/packages/editor-ui/src/stores/rbac.store.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia'; import { hasScope as genericHasScope } from '@n8n/permissions'; -import type { HasScopeOptions, Scope, Resource } from '@n8n/permissions'; +import type { ScopeOptions, Scope, Resource } from '@n8n/permissions'; import { ref } from 'vue'; import { STORES } from '@/constants'; import type { IRole } from '@/Interface'; @@ -80,7 +80,7 @@ export const useRBACStore = defineStore(STORES.RBAC, () => { resourceId?: string; projectId?: string; }, - options?: HasScopeOptions, + options?: ScopeOptions, ): boolean { return genericHasScope( scope, diff --git a/packages/editor-ui/src/types/rbac.ts b/packages/editor-ui/src/types/rbac.ts index b6603b2e1bdbe..169a7a0a738f9 100644 --- a/packages/editor-ui/src/types/rbac.ts +++ b/packages/editor-ui/src/types/rbac.ts @@ -1,9 +1,10 @@ import type { EnterpriseEditionFeature } from '@/constants'; -import type { Resource, HasScopeOptions, Scope } from '@n8n/permissions'; +import type { Resource, ScopeOptions, Scope } from '@n8n/permissions'; import type { IRole } from '@/Interface'; export type AuthenticatedPermissionOptions = {}; export type CustomPermissionOptions = RBACPermissionCheck; +export type DefaultUserMiddlewareOptions = {}; export type EnterprisePermissionOptions = { feature?: EnterpriseEditionFeature | EnterpriseEditionFeature[]; mode?: 'oneOf' | 'allOf'; @@ -14,14 +15,22 @@ export type RBACPermissionOptions = { projectId?: string; resourceType?: Resource; resourceId?: string; - options?: HasScopeOptions; + options?: ScopeOptions; }; export type RolePermissionOptions = IRole[]; -export type PermissionType = 'authenticated' | 'custom' | 'enterprise' | 'guest' | 'rbac' | 'role'; +export type PermissionType = + | 'authenticated' + | 'custom' + | 'defaultUser' + | 'enterprise' + | 'guest' + | 'rbac' + | 'role'; export type PermissionTypeOptions = { authenticated: AuthenticatedPermissionOptions; custom: CustomPermissionOptions; + defaultUser: DefaultUserMiddlewareOptions; enterprise: EnterprisePermissionOptions; guest: GuestPermissionOptions; rbac: RBACPermissionOptions; diff --git a/packages/editor-ui/src/types/router.ts b/packages/editor-ui/src/types/router.ts index 6ddf027e55fcf..43268767e8b54 100644 --- a/packages/editor-ui/src/types/router.ts +++ b/packages/editor-ui/src/types/router.ts @@ -13,6 +13,7 @@ import type { RBACPermissionOptions, RolePermissionOptions, PermissionType, + DefaultUserMiddlewareOptions, } from '@/types/rbac'; export type RouterMiddlewareType = PermissionType; @@ -24,6 +25,7 @@ export type CustomMiddlewareOptions = CustomPermissionOptions<{ export type MiddlewareOptions = { authenticated: AuthenticatedPermissionOptions; custom: CustomMiddlewareOptions; + defaultUser: DefaultUserMiddlewareOptions; enterprise: EnterprisePermissionOptions; guest: GuestPermissionOptions; rbac: RBACPermissionOptions; diff --git a/packages/editor-ui/src/views/SettingsUsersView.vue b/packages/editor-ui/src/views/SettingsUsersView.vue index 9bff8d74dd487..952b8e788cc6f 100644 --- a/packages/editor-ui/src/views/SettingsUsersView.vue +++ b/packages/editor-ui/src/views/SettingsUsersView.vue @@ -108,7 +108,7 @@ export default defineComponent({ return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing); }, showUMSetupWarning() { - return hasPermission(['role'], { role: [ROLE.Default] }); + return hasPermission(['defaultUser']); }, usersListActions(): IUserListAction[] { return [ From 380231c8255b0609a9cf5b89bae5bdc0a7072728 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 28 Nov 2023 15:51:20 +0200 Subject: [PATCH 03/10] fix: remove duplicate scope --- packages/@n8n/permissions/src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@n8n/permissions/src/types.ts b/packages/@n8n/permissions/src/types.ts index a0775c8ff5b0d..b040403416e15 100644 --- a/packages/@n8n/permissions/src/types.ts +++ b/packages/@n8n/permissions/src/types.ts @@ -2,7 +2,6 @@ export type DefaultOperations = 'create' | 'read' | 'update' | 'delete' | 'list' export type Resource = | 'auditLogs' | 'communityPackage' - | 'communityNodes' | 'credential' | 'externalSecretsStore' | 'externalSecretsProvider' From d0c0b177c3cc8cbd277dab1748871da64c4138c8 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 28 Nov 2023 15:53:32 +0200 Subject: [PATCH 04/10] fix: whitespace --- packages/@n8n/permissions/src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@n8n/permissions/src/types.ts b/packages/@n8n/permissions/src/types.ts index b040403416e15..5d33a1de09849 100644 --- a/packages/@n8n/permissions/src/types.ts +++ b/packages/@n8n/permissions/src/types.ts @@ -41,7 +41,6 @@ export type EventBusDestinationScope = ResourceScope< DefaultOperations | 'test' >; export type EventBusEventScope = ResourceScope<'eventBusEvent', DefaultOperations | 'query'>; - export type LdapScope = ResourceScope<'ldap', 'manage' | 'sync'>; export type LogStreamingScope = ResourceScope<'logStreaming', 'manage'>; export type OrchestrationScope = ResourceScope<'orchestration', 'read' | 'list'>; From 4cb3b857c18cb5594209ae6bab824ce63493df81 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 28 Nov 2023 15:54:47 +0200 Subject: [PATCH 05/10] fix: remove merge conflict --- packages/cli/src/permissions/roles.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/cli/src/permissions/roles.ts b/packages/cli/src/permissions/roles.ts index 3b39b242bef96..d43c3dd60b74a 100644 --- a/packages/cli/src/permissions/roles.ts +++ b/packages/cli/src/permissions/roles.ts @@ -1,7 +1,7 @@ import type { Scope } from '@n8n/permissions'; export const ownerPermissions: Scope[] = [ - 'auditLogs:manage', + 'auditLogs:manage', 'credential:create', 'credential:read', 'credential:update', @@ -70,14 +70,8 @@ export const memberPermissions: Scope[] = [ 'tag:read', 'tag:update', 'tag:list', -<<<<<<< HEAD - 'user:list', - 'variable:list', - 'variable:read', -======= 'eventBusEvent:list', 'eventBusEvent:read', 'eventBusDestination:list', 'eventBusDestination:test', ->>>>>>> origin/master ]; From 09d96bce53904798b5c6dcb0529e443c67737c0c Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 28 Nov 2023 15:58:37 +0200 Subject: [PATCH 06/10] fix: fix some scopes --- packages/editor-ui/src/router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts index 805cd73fd06ab..781217b9a90a6 100644 --- a/packages/editor-ui/src/router.ts +++ b/packages/editor-ui/src/router.ts @@ -567,7 +567,7 @@ export const routes = [ middleware: ['authenticated', 'rbac'], middlewareOptions: { rbac: { - scope: ['externalSecretsProvider:list', 'externalSecretsProvider:read'], + scope: ['externalSecretsProvider:list', 'externalSecretsProvider:update'], }, }, telemetry: { @@ -645,7 +645,7 @@ export const routes = [ middleware: ['authenticated', 'rbac', 'custom'], middlewareOptions: { rbac: { - scope: 'communityPackage:manage', + scope: ['communityPackage:list', 'communityPackage:update'], }, custom: () => { const settingsStore = useSettingsStore(); From 190aef674a776efaaf6fc7b294dee18e15b0d91f Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 28 Nov 2023 16:11:08 +0200 Subject: [PATCH 07/10] fix: fix role changes --- packages/@n8n/permissions/src/types.ts | 1 - packages/cli/src/permissions/roles.ts | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/@n8n/permissions/src/types.ts b/packages/@n8n/permissions/src/types.ts index 5d33a1de09849..ebd5015cb49dd 100644 --- a/packages/@n8n/permissions/src/types.ts +++ b/packages/@n8n/permissions/src/types.ts @@ -3,7 +3,6 @@ export type Resource = | 'auditLogs' | 'communityPackage' | 'credential' - | 'externalSecretsStore' | 'externalSecretsProvider' | 'externalSecret' | 'eventBusEvent' diff --git a/packages/cli/src/permissions/roles.ts b/packages/cli/src/permissions/roles.ts index d43c3dd60b74a..b8f19349cb15d 100644 --- a/packages/cli/src/permissions/roles.ts +++ b/packages/cli/src/permissions/roles.ts @@ -66,12 +66,15 @@ export const ownerPermissions: Scope[] = [ ]; export const adminPermissions: Scope[] = ownerPermissions.concat(); export const memberPermissions: Scope[] = [ - 'tag:create', - 'tag:read', - 'tag:update', - 'tag:list', 'eventBusEvent:list', 'eventBusEvent:read', 'eventBusDestination:list', 'eventBusDestination:test', + 'tag:create', + 'tag:read', + 'tag:update', + 'tag:list', + 'user:list', + 'variable:list', + 'variable:read', ]; From 90db64079c025a122ce0d8d22ecc9fc1abdbbde3 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 29 Nov 2023 06:45:18 +0100 Subject: [PATCH 08/10] fix(editor): Lint fix --- packages/editor-ui/src/components/MainSidebar.vue | 1 - .../editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index 3f7352505c5de..1ba1996a8cdd5 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -120,7 +120,6 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import { isNavigationFailure } from 'vue-router'; import ExecutionsUsage from '@/components/ExecutionsUsage.vue'; import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue'; -import { ROLE } from '@/utils/userUtils'; import { hasPermission } from '@/rbac/permissions'; export default defineComponent({ diff --git a/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts b/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts index d469da503c90e..d550dd9fe0b94 100644 --- a/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts +++ b/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts @@ -2,7 +2,6 @@ import { useUsersStore } from '@/stores/users.store'; import { VIEWS } from '@/constants'; import { defaultUserMiddleware } from '@/rbac/middleware/defaultUser'; import type { RouteLocationNormalized } from 'vue-router'; -import { authenticatedMiddleware } from '@/rbac/middleware/authenticated'; vi.mock('@/stores/users.store', () => ({ useUsersStore: vi.fn(), From c9d741bcfea0532eb056f6f5f430b2fc9c8e265a Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Wed, 29 Nov 2023 09:28:55 +0200 Subject: [PATCH 09/10] fix: fix registered permission checks --- cypress/e2e/18-user-management.cy.ts | 14 ++++++++++++-- packages/editor-ui/src/rbac/permissions.ts | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/18-user-management.cy.ts b/cypress/e2e/18-user-management.cy.ts index dcf32fd678b7c..8c4e89f22ca57 100644 --- a/cypress/e2e/18-user-management.cy.ts +++ b/cypress/e2e/18-user-management.cy.ts @@ -63,13 +63,23 @@ describe('User Management', { disableAutoLogin: true }, () => { personalSettingsPage.actions.changeTheme('Dark'); cy.get('body').should('have.attr', 'data-theme', 'dark'); settingsSidebar.actions.back(); - mainSidebar.getters.logo().should('have.attr', 'src', '/n8n-dev-logo-dark-mode.svg'); + mainSidebar.getters + .logo() + .should('have.attr', 'src') + .then((src) => { + expect(src).to.include('/n8n-dev-logo-dark-mode.svg'); + }); cy.visit(personalSettingsPage.url); personalSettingsPage.actions.changeTheme('Light'); cy.get('body').should('have.attr', 'data-theme', 'light'); settingsSidebar.actions.back(); - mainSidebar.getters.logo().should('have.attr', 'src', '/n8n-dev-logo.svg'); + mainSidebar.getters + .logo() + .should('have.attr', 'src') + .then((src) => { + expect(src).to.include('/n8n-dev-logo.svg'); + }); }); it('should delete user and their data', () => { diff --git a/packages/editor-ui/src/rbac/permissions.ts b/packages/editor-ui/src/rbac/permissions.ts index 13e9c2be008a4..f132ce8049dff 100644 --- a/packages/editor-ui/src/rbac/permissions.ts +++ b/packages/editor-ui/src/rbac/permissions.ts @@ -2,6 +2,7 @@ import { hasRole, hasScope, isAuthenticated, + isDefaultUser, isEnterpriseFeatureEnabled, isGuest, isValid, @@ -15,6 +16,7 @@ type Permissions = { export const permissions: Permissions = { authenticated: isAuthenticated, custom: isValid, + defaultUser: isDefaultUser, enterprise: isEnterpriseFeatureEnabled, guest: isGuest, rbac: hasScope, From 28c1e982e7f0f62d9c41b894faee56e4388f0d96 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 29 Nov 2023 09:07:29 +0100 Subject: [PATCH 10/10] fix: Fix unit test --- .../src/rbac/__tests__/permissions.test.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/rbac/__tests__/permissions.test.ts b/packages/editor-ui/src/rbac/__tests__/permissions.test.ts index 7a7ea3fa12c6c..204ace46fab95 100644 --- a/packages/editor-ui/src/rbac/__tests__/permissions.test.ts +++ b/packages/editor-ui/src/rbac/__tests__/permissions.test.ts @@ -5,6 +5,7 @@ vi.mock('@/rbac/checks', () => ({ hasRole: vi.fn(), hasScope: vi.fn(), isGuest: vi.fn(), + isDefaultUser: vi.fn(), isAuthenticated: vi.fn(), isEnterpriseFeatureEnabled: vi.fn(), isValid: vi.fn(), @@ -15,13 +16,22 @@ describe('hasPermission()', () => { vi.mocked(checks.hasRole).mockReturnValue(true); vi.mocked(checks.hasScope).mockReturnValue(true); vi.mocked(checks.isGuest).mockReturnValue(true); + vi.mocked(checks.isDefaultUser).mockReturnValue(true); vi.mocked(checks.isAuthenticated).mockReturnValue(true); vi.mocked(checks.isEnterpriseFeatureEnabled).mockReturnValue(true); vi.mocked(checks.isValid).mockReturnValue(true); - expect(hasPermission(['authenticated', 'custom', 'enterprise', 'guest', 'rbac', 'role'])).toBe( - true, - ); + expect( + hasPermission([ + 'authenticated', + 'custom', + 'enterprise', + 'guest', + 'rbac', + 'role', + 'defaultUser', + ]), + ).toBe(true); }); it('should return false if any permission is invalid', () => {