Revert "chore(core): Use roles from database in global roles (#17853)" (#18738)

This commit is contained in:
Andreas Fitzek
2025-08-25 14:29:28 +02:00
committed by GitHub
parent b62c957c71
commit a21a03d4b0
117 changed files with 410 additions and 875 deletions

View File

@@ -95,8 +95,6 @@ exports[`Scope Information ensure scopes are defined correctly 1`] = `
"workflow:share",
"workflow:execute",
"workflow:move",
"workflow:activate",
"workflow:deactivate",
"workflow:create",
"workflow:read",
"workflow:update",
@@ -123,14 +121,6 @@ exports[`Scope Information ensure scopes are defined correctly 1`] = `
"dataStore:writeRow",
"dataStore:listProject",
"dataStore:*",
"execution:delete",
"execution:read",
"execution:list",
"execution:get",
"execution:*",
"workflowTags:update",
"workflowTags:list",
"workflowTags:*",
"*",
]
`;

View File

@@ -22,13 +22,11 @@ export const RESOURCES = {
user: ['resetPassword', 'changeRole', 'enforceMfa', ...DEFAULT_OPERATIONS] as const,
variable: [...DEFAULT_OPERATIONS] as const,
workersView: ['manage'] as const,
workflow: ['share', 'execute', 'move', 'activate', 'deactivate', ...DEFAULT_OPERATIONS] as const,
workflow: ['share', 'execute', 'move', ...DEFAULT_OPERATIONS] as const,
folder: [...DEFAULT_OPERATIONS, 'move'] as const,
insights: ['list'] as const,
oidc: ['manage'] as const,
dataStore: [...DEFAULT_OPERATIONS, 'readRow', 'writeRow', 'listProject'] as const,
execution: ['delete', 'read', 'list', 'get'] as const,
workflowTags: ['update', 'list'] as const,
} as const;
export const API_KEY_RESOURCES = {

View File

@@ -13,7 +13,7 @@ export { hasGlobalScope } from './utilities/has-global-scope.ee';
export { combineScopes } from './utilities/combine-scopes.ee';
export { rolesWithScope } from './utilities/roles-with-scope.ee';
export { getGlobalScopes } from './utilities/get-global-scopes.ee';
export { getRoleScopes, getAuthPrincipalScopes } from './utilities/get-role-scopes.ee';
export { getRoleScopes } from './utilities/get-role-scopes.ee';
export { getResourcePermissions } from './utilities/get-resource-permissions.ee';
export type { PermissionsRecord } from './utilities/get-resource-permissions.ee';
export * from './public-api-permissions.ee';

View File

@@ -1,4 +1,4 @@
import { isApiKeyScope, type ApiKeyScope, type AuthPrincipal, type GlobalRole } from './types.ee';
import type { ApiKeyScope, GlobalRole } from './types.ee';
export const OWNER_API_KEY_SCOPES: ApiKeyScope[] = [
'user:read',
@@ -65,46 +65,14 @@ export const MEMBER_API_KEY_SCOPES: ApiKeyScope[] = [
'credential:delete',
];
/**
* This is a bit of a mess, because we are handing out scopes in API keys that are only
* valid for the personal project, which is enforced in the public API, because the workflows,
* execution endpoints are limited to the personal project.
* This is a temporary solution until we have a better way to handle personal projects and API key scopes!
*/
export const API_KEY_SCOPES_FOR_IMPLICIT_PERSONAL_PROJECT: ApiKeyScope[] = [
'workflowTags:update',
'workflowTags:list',
'workflow:create',
'workflow:read',
'workflow:update',
'workflow:delete',
'workflow:list',
'workflow:move',
'workflow:activate',
'workflow:deactivate',
'execution:delete',
'execution:read',
'execution:list',
'credential:create',
'credential:move',
'credential:delete',
];
const MAP_ROLE_SCOPES: Record<GlobalRole, ApiKeyScope[]> = {
'global:owner': OWNER_API_KEY_SCOPES,
'global:admin': ADMIN_API_KEY_SCOPES,
'global:member': MEMBER_API_KEY_SCOPES,
};
export const getApiKeyScopesForRole = (user: AuthPrincipal) => {
return [
...new Set(
user.role.scopes
.map((scope) => scope.slug)
.concat(API_KEY_SCOPES_FOR_IMPLICIT_PERSONAL_PROJECT)
.filter(isApiKeyScope),
),
];
export const getApiKeyScopesForRole = (role: GlobalRole) => {
return MAP_ROLE_SCOPES[role];
};
export const getOwnerOnlyApiKeyScopes = () => {

View File

@@ -1,5 +1,5 @@
import { API_KEY_RESOURCES, RESOURCES } from './constants.ee';
import type { ApiKeyScope, Scope, ScopeInformation } from './types.ee';
import { RESOURCES } from './constants.ee';
import type { Scope, ScopeInformation } from './types.ee';
function buildResourceScopes() {
const resourceScopes = Object.entries(RESOURCES).flatMap(([resource, operations]) => [
@@ -11,19 +11,8 @@ function buildResourceScopes() {
return resourceScopes;
}
function buildApiKeyScopes() {
const apiKeyScopes = Object.entries(API_KEY_RESOURCES).flatMap(([resource, operations]) => [
...operations.map((op) => `${resource}:${op}` as const),
]) as ApiKeyScope[];
return new Set(apiKeyScopes);
}
export const ALL_SCOPES = buildResourceScopes();
// Keep the type of Scope[] to ensure that ApiKeyScopes are a subset of Scopes!
export const ALL_API_KEY_SCOPES = buildApiKeyScopes();
export const scopeInformation: Partial<Record<Scope, ScopeInformation>> = {
'annotationTag:create': {
displayName: 'Create Annotation Tag',

View File

@@ -10,7 +10,6 @@ import type {
teamRoleSchema,
workflowSharingRoleSchema,
} from './schemas.ee';
import { ALL_API_KEY_SCOPES } from './scope-information';
export type ScopeInformation = {
displayName: string;
@@ -77,21 +76,12 @@ export type AllRolesMap = {
workflow: Array<RoleObject<WorkflowSharingRole>>;
};
export type DbScope = {
slug: Scope;
};
export type DbRole = {
slug: string;
scopes: DbScope[];
};
/**
* Represents an authenticated entity in the system that can have specific permissions via a role.
* @property role - The global role this principal has
*/
export type AuthPrincipal = {
role: DbRole;
role: GlobalRole;
};
// #region Public API
@@ -111,9 +101,4 @@ type AllApiKeyScopesObject = {
export type ApiKeyScope = AllApiKeyScopesObject[PublicApiKeyResources];
export function isApiKeyScope(scope: Scope): scope is ApiKeyScope {
// We are casting with as for runtime type checking
return ALL_API_KEY_SCOPES.has(scope as ApiKeyScope);
}
// #endregion

View File

@@ -1,19 +1,19 @@
import { GLOBAL_SCOPE_MAP } from '../../roles/role-maps.ee';
import type { GlobalRole } from '../../types.ee';
import { getGlobalScopes } from '../get-global-scopes.ee';
import { createAuthPrinicipal } from './utils';
describe('getGlobalScopes', () => {
test.each(['global:owner', 'global:admin', 'global:member'] as const)(
'should return correct scopes for %s',
(role) => {
const scopes = getGlobalScopes(createAuthPrinicipal(role));
const scopes = getGlobalScopes({ role });
expect(scopes).toEqual(GLOBAL_SCOPE_MAP[role]);
},
);
test('should return empty array for non-existent role', () => {
const scopes = getGlobalScopes(createAuthPrinicipal('non:existent'));
const scopes = getGlobalScopes({ role: 'non:existent' as GlobalRole });
expect(scopes).toEqual([]);
});

View File

@@ -15,7 +15,6 @@ describe('permissions', () => {
externalSecretsProvider: {},
externalSecret: {},
eventBusDestination: {},
execution: {},
ldap: {},
license: {},
logStreaming: {},
@@ -30,7 +29,6 @@ describe('permissions', () => {
variable: {},
workersView: {},
workflow: {},
workflowTags: {},
folder: {},
insights: {},
dataStore: {},
@@ -132,8 +130,6 @@ describe('permissions', () => {
list: true,
},
dataStore: {},
execution: {},
workflowTags: {},
};
expect(getResourcePermissions(scopes)).toEqual(permissionRecord);

View File

@@ -1,6 +1,5 @@
import type { GlobalRole, Scope } from '../../types.ee';
import { hasGlobalScope } from '../has-global-scope.ee';
import { createAuthPrinicipal } from './utils';
describe('hasGlobalScope', () => {
describe('single scope checks', () => {
@@ -12,7 +11,7 @@ describe('hasGlobalScope', () => {
] as Array<{ role: GlobalRole; scope: Scope; expected: boolean }>)(
'$role with $scope -> $expected',
({ role, scope, expected }) => {
expect(hasGlobalScope(createAuthPrinicipal(role), scope)).toBe(expected);
expect(hasGlobalScope({ role }, scope)).toBe(expected);
},
);
});
@@ -20,7 +19,7 @@ describe('hasGlobalScope', () => {
describe('multiple scopes', () => {
test('oneOf mode (default)', () => {
expect(
hasGlobalScope(createAuthPrinicipal('global:member'), [
hasGlobalScope({ role: 'global:member' }, [
'tag:create',
'user:list',
// a member cannot create users
@@ -32,7 +31,7 @@ describe('hasGlobalScope', () => {
test('allOf mode', () => {
expect(
hasGlobalScope(
createAuthPrinicipal('global:member'),
{ role: 'global:member' },
[
'tag:create',
'user:list',
@@ -46,6 +45,6 @@ describe('hasGlobalScope', () => {
});
test('edge cases', () => {
expect(hasGlobalScope(createAuthPrinicipal('global:owner'), [])).toBe(false);
expect(hasGlobalScope({ role: 'global:owner' }, [])).toBe(false);
});
});

View File

@@ -1,40 +0,0 @@
import { GLOBAL_SCOPE_MAP } from '@/roles/role-maps.ee';
import { globalRoleSchema } from '@/schemas.ee';
import type { AuthPrincipal, GlobalRole, Scope } from '@/types.ee';
function createBuildInAuthPrinicipal(role: GlobalRole): AuthPrincipal {
return {
role: {
slug: role,
scopes:
GLOBAL_SCOPE_MAP[role].map((scope) => {
return {
slug: scope,
};
}) || [],
},
};
}
export function createAuthPrinicipal(role: string, scopes: Scope[] = []): AuthPrincipal {
try {
const isGlobalRole = globalRoleSchema.parse(role);
if (isGlobalRole) {
return createBuildInAuthPrinicipal(isGlobalRole);
}
} catch (error) {
// If the role is not a valid global role, we proceed
// to create a custom role with the provided scopes.
}
return {
role: {
slug: role,
scopes:
scopes.map((scope) => {
return {
slug: scope,
};
}) || [],
},
};
}

View File

@@ -1,3 +1,4 @@
import { GLOBAL_SCOPE_MAP } from '../roles/role-maps.ee';
import type { AuthPrincipal } from '../types.ee';
/**
@@ -5,5 +6,4 @@ import type { AuthPrincipal } from '../types.ee';
* @param principal - Contains the role to look up
* @returns Array of scopes for the role, or empty array if not found
*/
export const getGlobalScopes = (principal: AuthPrincipal) =>
principal.role.scopes.map((scope) => scope.slug) ?? [];
export const getGlobalScopes = (principal: AuthPrincipal) => GLOBAL_SCOPE_MAP[principal.role] ?? [];

View File

@@ -1,5 +1,5 @@
import { ALL_ROLE_MAPS } from '../roles/role-maps.ee';
import type { AllRoleTypes, AuthPrincipal, Resource, Scope } from '../types.ee';
import type { AllRoleTypes, Resource, Scope } from '../types.ee';
export const COMBINED_ROLE_MAP = Object.fromEntries(
Object.values(ALL_ROLE_MAPS).flatMap((o: Record<string, Scope[]>) => Object.entries(o)),
@@ -10,8 +10,6 @@ export const COMBINED_ROLE_MAP = Object.fromEntries(
* @param role - The role to look up
* @param filters - Optional resources to filter scopes by
* @returns Array of matching scopes
*
* @deprecated Use the 'getRoleScopes' from the AuthRolesService instead.
*/
export function getRoleScopes(role: AllRoleTypes, filters?: Resource[]): Scope[] {
let scopes = COMBINED_ROLE_MAP[role];
@@ -20,22 +18,3 @@ export function getRoleScopes(role: AllRoleTypes, filters?: Resource[]): Scope[]
}
return scopes;
}
/**
* Gets scopes for an auth principal, optionally filtered by resource types.
* @param user - The auth principal to search scopes for
* @param filters - Optional resources to filter scopes by
* @returns Array of matching scopes
*/
export function getAuthPrincipalScopes(user: AuthPrincipal, filters?: Resource[]): Scope[] {
if (!user.role) {
const e = new Error('AuthPrincipal does not have a role defined');
console.error('AuthPrincipal does not have a role defined', e);
throw e;
}
let scopes = user.role.scopes.map((s) => s.slug);
if (filters) {
scopes = scopes.filter((s) => filters.includes(s.split(':')[0] as Resource));
}
return scopes;
}

View File

@@ -1,6 +1,6 @@
import { getGlobalScopes } from './get-global-scopes.ee';
import { hasScope } from './has-scope.ee';
import type { AuthPrincipal, Scope, ScopeOptions } from '../types.ee';
import { getAuthPrincipalScopes } from './get-role-scopes.ee';
/**
* Checks if an auth-principal has specified global scope(s).
@@ -12,6 +12,6 @@ export const hasGlobalScope = (
scope: Scope | Scope[],
scopeOptions?: ScopeOptions,
): boolean => {
const global = getAuthPrincipalScopes(principal);
const global = getGlobalScopes(principal);
return hasScope(scope, { global }, undefined, scopeOptions);
};