Skip to content

Commit

Permalink
fix(editor): Handle permission edge cases (empty scopes) (n8n-io#7723)
Browse files Browse the repository at this point in the history
  • Loading branch information
cstuncsik authored Nov 16, 2023
1 parent 9d6a68d commit e2ffd39
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 31 deletions.
32 changes: 9 additions & 23 deletions packages/@n8n/permissions/src/hasScope.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,28 @@
import type { Scope, ScopeLevels } from './types';

export type HasScopeMode = 'oneOf' | 'allOf';
export interface HasScopeOptions {
mode: HasScopeMode;
}
import type { Scope, ScopeLevels, GlobalScopes, ScopeOptions } from './types';

export function hasScope(
scope: Scope | Scope[],
userScopes: ScopeLevels,
options?: HasScopeOptions,
userScopes: GlobalScopes,
options?: ScopeOptions,
): boolean;
export function hasScope(
scope: Scope | Scope[],
userScopes: Pick<ScopeLevels, 'global'>,
options?: HasScopeOptions,
): boolean;
export function hasScope(
scope: Scope | Scope[],
userScopes: Omit<ScopeLevels, 'resource'>,
options?: HasScopeOptions,
userScopes: ScopeLevels,
options?: ScopeOptions,
): boolean;
export function hasScope(
scope: Scope | Scope[],
userScopes: Pick<ScopeLevels, 'global'> & Partial<ScopeLevels>,
options: HasScopeOptions = { mode: 'oneOf' },
userScopes: unknown,
options: ScopeOptions = { mode: 'oneOf' },
): boolean {
if (!Array.isArray(scope)) {
scope = [scope];
}

const userScopeSet = new Set([
...userScopes.global,
...(userScopes.project ?? []),
...(userScopes.resource ?? []),
]);
const userScopeSet = new Set(Object.values(userScopes ?? {}).flat());

if (options.mode === 'allOf') {
return scope.every((s) => userScopeSet.has(s));
return !!scope.length && scope.every((s) => userScopeSet.has(s));
}

return scope.some((s) => userScopeSet.has(s));
Expand Down
10 changes: 8 additions & 2 deletions packages/@n8n/permissions/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,11 @@ export type Scope =
| SourceControlScope
| ExternalSecretStoreScope;

export type ScopeLevel = 'global' | 'project' | 'resource';
export type ScopeLevels = Record<ScopeLevel, Scope[]>;
export type ScopeLevel<T extends 'global' | 'project' | 'resource'> = Record<T, Scope[]>;
export type GlobalScopes = ScopeLevel<'global'>;
export type ProjectScopes = ScopeLevel<'project'>;
export type ResourceScopes = ScopeLevel<'resource'>;
export type ScopeLevels = GlobalScopes & (ProjectScopes | (ProjectScopes & ResourceScopes));

export type ScopeMode = 'oneOf' | 'allOf';
export type ScopeOptions = { mode: ScopeMode };
16 changes: 16 additions & 0 deletions packages/@n8n/permissions/test/hasScope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ describe('hasScope', () => {
global: memberPermissions,
}),
).toBe(false);

expect(
hasScope([], {
global: memberPermissions,
}),
).toBe(false);
});

test('should work with allOf mode', () => {
Expand Down Expand Up @@ -112,5 +118,15 @@ describe('hasScope', () => {
{ mode: 'allOf' },
),
).toBe(false);

expect(
hasScope(
[],
{
global: memberPermissions,
},
{ mode: 'allOf' },
),
).toBe(false);
});
});
9 changes: 3 additions & 6 deletions packages/cli/src/databases/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { WithTimestamps, jsonColumnType } from './AbstractEntity';
import type { IPersonalizationSurveyAnswers } from '@/Interfaces';
import type { AuthIdentity } from './AuthIdentity';
import { ownerPermissions, memberPermissions } from '@/permissions/roles';
import { hasScope, type HasScopeOptions, type Scope } from '@n8n/permissions';
import { hasScope, type ScopeOptions, type Scope } from '@n8n/permissions';

export const MIN_PASSWORD_LENGTH = 8;

Expand Down Expand Up @@ -137,16 +137,13 @@ export class User extends WithTimestamps implements IUser {
return STATIC_SCOPE_MAP[this.globalRole?.name] ?? [];
}

async hasGlobalScope(
scope: Scope | Scope[],
hasScopeOptions?: HasScopeOptions,
): Promise<boolean> {
async hasGlobalScope(scope: Scope | Scope[], scopeOptions?: ScopeOptions): Promise<boolean> {
return hasScope(
scope,
{
global: this.globalScopes,
},
hasScopeOptions,
scopeOptions,
);
}
}

0 comments on commit e2ffd39

Please sign in to comment.