chore(core): Use dynamic role resolution for access control (#19400)

This commit is contained in:
Andreas Fitzek
2025-09-17 11:15:31 +02:00
committed by GitHub
parent 8086a21eb2
commit 33a2d5de17
21 changed files with 1581 additions and 201 deletions

View File

@@ -1,5 +1,5 @@
import { Service } from '@n8n/di';
import type { CredentialSharingRole, ProjectRole } from '@n8n/permissions';
import type { CredentialSharingRole } from '@n8n/permissions';
import type { EntityManager, FindOptionsWhere } from '@n8n/typeorm';
import { DataSource, In, Not, Repository } from '@n8n/typeorm';
@@ -108,8 +108,8 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
async findCredentialsByRoles(
userIds: string[],
projectRoles: ProjectRole[],
credentialRoles: CredentialSharingRole[],
projectRoles: string[],
credentialRoles: string[],
trx?: EntityManager,
) {
trx = trx ?? this.manager;

View File

@@ -20,7 +20,7 @@ export {
export { hasScope } from './utilities/has-scope.ee';
export { hasGlobalScope } from './utilities/has-global-scope.ee';
export { combineScopes } from './utilities/combine-scopes.ee';
export { rolesWithScope } from './utilities/roles-with-scope.ee';
export { staticRolesWithScope } from './utilities/static-roles-with-scope.ee';
export { getGlobalScopes } from './utilities/get-global-scopes.ee';
export { getRoleScopes, getAuthPrincipalScopes } from './utilities/get-role-scopes.ee';
export { getResourcePermissions } from './utilities/get-resource-permissions.ee';

View File

@@ -1,5 +1,5 @@
import type { GlobalRole, Scope } from '../../types.ee';
import { rolesWithScope } from '../roles-with-scope.ee';
import { staticRolesWithScope } from '../static-roles-with-scope.ee';
describe('rolesWithScope', () => {
describe('global roles', () => {
@@ -8,14 +8,14 @@ describe('rolesWithScope', () => {
['user:list', ['global:owner', 'global:admin', 'global:member']],
['invalid:scope', []],
] as Array<[Scope, GlobalRole[]]>)('%s -> %s', (scope, expected) => {
expect(rolesWithScope('global', scope)).toEqual(expected);
expect(staticRolesWithScope('global', scope)).toEqual(expected);
});
});
describe('multiple scopes', () => {
test('returns roles with all scopes', () => {
expect(
rolesWithScope('global', [
staticRolesWithScope('global', [
// all global roles have this scope
'tag:create',
// only owner and admin have this scope

View File

@@ -1,37 +0,0 @@
import { ALL_ROLE_MAPS } from '../roles/role-maps.ee';
import type {
CredentialSharingRole,
GlobalRole,
ProjectRole,
RoleNamespace,
Scope,
WorkflowSharingRole,
} from '../types.ee';
/**
* Retrieves roles within a specific namespace that have all the given scopes.
* @param namespace - The role namespace to search in
* @param scopes - Scope(s) to filter by
*/
export function rolesWithScope(namespace: 'global', scopes: Scope | Scope[]): GlobalRole[];
export function rolesWithScope(namespace: 'project', scopes: Scope | Scope[]): ProjectRole[];
export function rolesWithScope(
namespace: 'credential',
scopes: Scope | Scope[],
): CredentialSharingRole[];
export function rolesWithScope(
namespace: 'workflow',
scopes: Scope | Scope[],
): WorkflowSharingRole[];
export function rolesWithScope(namespace: RoleNamespace, scopes: Scope | Scope[]) {
if (!Array.isArray(scopes)) {
scopes = [scopes];
}
return Object.keys(ALL_ROLE_MAPS[namespace]).filter((k) => {
return scopes.every((s) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
((ALL_ROLE_MAPS[namespace] as any)[k] as Scope[]).includes(s),
);
});
}

View File

@@ -0,0 +1,24 @@
import { ALL_ROLE_MAPS } from '../roles/role-maps.ee';
import type { RoleNamespace, Scope } from '../types.ee';
/**
* Retrieves roles within a specific namespace that have all the given scopes.
*
* This is only valid for static roles defined in ALL_ROLE_MAPS, with custom roles
* being handled in the RoleService.
*
* @param namespace - The role namespace to search in
* @param scopes - Scope(s) to filter by
*/
export function staticRolesWithScope(namespace: RoleNamespace, scopes: Scope | Scope[]) {
if (!Array.isArray(scopes)) {
scopes = [scopes];
}
return Object.keys(ALL_ROLE_MAPS[namespace]).filter((k) => {
return scopes.every((s) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
((ALL_ROLE_MAPS[namespace] as any)[k] as Scope[]).includes(s),
);
});
}