mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Handle permission edge cases (empty scopes) (#7723)
This commit is contained in:
parent
9d6a68d1c7
commit
e2ffd397fc
|
@ -1,42 +1,28 @@
|
||||||
import type { Scope, ScopeLevels } from './types';
|
import type { Scope, ScopeLevels, GlobalScopes, ScopeOptions } from './types';
|
||||||
|
|
||||||
export type HasScopeMode = 'oneOf' | 'allOf';
|
|
||||||
export interface HasScopeOptions {
|
|
||||||
mode: HasScopeMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export function hasScope(
|
||||||
|
scope: Scope | Scope[],
|
||||||
|
userScopes: GlobalScopes,
|
||||||
|
options?: ScopeOptions,
|
||||||
|
): boolean;
|
||||||
export function hasScope(
|
export function hasScope(
|
||||||
scope: Scope | Scope[],
|
scope: Scope | Scope[],
|
||||||
userScopes: ScopeLevels,
|
userScopes: ScopeLevels,
|
||||||
options?: HasScopeOptions,
|
options?: ScopeOptions,
|
||||||
): boolean;
|
): boolean;
|
||||||
export function hasScope(
|
export function hasScope(
|
||||||
scope: Scope | Scope[],
|
scope: Scope | Scope[],
|
||||||
userScopes: Pick<ScopeLevels, 'global'>,
|
userScopes: unknown,
|
||||||
options?: HasScopeOptions,
|
options: ScopeOptions = { mode: 'oneOf' },
|
||||||
): boolean;
|
|
||||||
export function hasScope(
|
|
||||||
scope: Scope | Scope[],
|
|
||||||
userScopes: Omit<ScopeLevels, 'resource'>,
|
|
||||||
options?: HasScopeOptions,
|
|
||||||
): boolean;
|
|
||||||
export function hasScope(
|
|
||||||
scope: Scope | Scope[],
|
|
||||||
userScopes: Pick<ScopeLevels, 'global'> & Partial<ScopeLevels>,
|
|
||||||
options: HasScopeOptions = { mode: 'oneOf' },
|
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!Array.isArray(scope)) {
|
if (!Array.isArray(scope)) {
|
||||||
scope = [scope];
|
scope = [scope];
|
||||||
}
|
}
|
||||||
|
|
||||||
const userScopeSet = new Set([
|
const userScopeSet = new Set(Object.values(userScopes ?? {}).flat());
|
||||||
...userScopes.global,
|
|
||||||
...(userScopes.project ?? []),
|
|
||||||
...(userScopes.resource ?? []),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (options.mode === 'allOf') {
|
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));
|
return scope.some((s) => userScopeSet.has(s));
|
||||||
|
|
|
@ -34,5 +34,11 @@ export type Scope =
|
||||||
| SourceControlScope
|
| SourceControlScope
|
||||||
| ExternalSecretStoreScope;
|
| ExternalSecretStoreScope;
|
||||||
|
|
||||||
export type ScopeLevel = 'global' | 'project' | 'resource';
|
export type ScopeLevel<T extends 'global' | 'project' | 'resource'> = Record<T, Scope[]>;
|
||||||
export type ScopeLevels = Record<ScopeLevel, 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 };
|
||||||
|
|
|
@ -80,6 +80,12 @@ describe('hasScope', () => {
|
||||||
global: memberPermissions,
|
global: memberPermissions,
|
||||||
}),
|
}),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
hasScope([], {
|
||||||
|
global: memberPermissions,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should work with allOf mode', () => {
|
test('should work with allOf mode', () => {
|
||||||
|
@ -112,5 +118,15 @@ describe('hasScope', () => {
|
||||||
{ mode: 'allOf' },
|
{ mode: 'allOf' },
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
hasScope(
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
global: memberPermissions,
|
||||||
|
},
|
||||||
|
{ mode: 'allOf' },
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { WithTimestamps, jsonColumnType } from './AbstractEntity';
|
||||||
import type { IPersonalizationSurveyAnswers } from '@/Interfaces';
|
import type { IPersonalizationSurveyAnswers } from '@/Interfaces';
|
||||||
import type { AuthIdentity } from './AuthIdentity';
|
import type { AuthIdentity } from './AuthIdentity';
|
||||||
import { ownerPermissions, memberPermissions } from '@/permissions/roles';
|
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;
|
export const MIN_PASSWORD_LENGTH = 8;
|
||||||
|
|
||||||
|
@ -137,16 +137,13 @@ export class User extends WithTimestamps implements IUser {
|
||||||
return STATIC_SCOPE_MAP[this.globalRole?.name] ?? [];
|
return STATIC_SCOPE_MAP[this.globalRole?.name] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasGlobalScope(
|
async hasGlobalScope(scope: Scope | Scope[], scopeOptions?: ScopeOptions): Promise<boolean> {
|
||||||
scope: Scope | Scope[],
|
|
||||||
hasScopeOptions?: HasScopeOptions,
|
|
||||||
): Promise<boolean> {
|
|
||||||
return hasScope(
|
return hasScope(
|
||||||
scope,
|
scope,
|
||||||
{
|
{
|
||||||
global: this.globalScopes,
|
global: this.globalScopes,
|
||||||
},
|
},
|
||||||
hasScopeOptions,
|
scopeOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue