chore(core): Add custom role management service and endpoints (#18717)

This commit is contained in:
Andreas Fitzek
2025-08-29 14:20:32 +02:00
committed by GitHub
parent 3b574306f3
commit 7cfef64799
34 changed files with 3783 additions and 238 deletions

View File

@@ -131,6 +131,8 @@ exports[`Scope Information ensure scopes are defined correctly 1`] = `
"workflowTags:update",
"workflowTags:list",
"workflowTags:*",
"role:manage",
"role:*",
"*",
]
`;

View File

@@ -29,6 +29,7 @@ export const RESOURCES = {
dataStore: [...DEFAULT_OPERATIONS, 'readRow', 'writeRow', 'listProject'] as const,
execution: ['delete', 'read', 'list', 'get'] as const,
workflowTags: ['update', 'list'] as const,
role: ['manage'] as const,
} as const;
export const API_KEY_RESOURCES = {

View File

@@ -6,7 +6,7 @@ export * from './scope-information';
export * from './roles/role-maps.ee';
export * from './roles/all-roles';
export { projectRoleSchema, teamRoleSchema } from './schemas.ee';
export { projectRoleSchema, teamRoleSchema, roleSchema, Role, scopeSchema } from './schemas.ee';
export { hasScope } from './utilities/has-scope.ee';
export { hasGlobalScope } from './utilities/has-global-scope.ee';

View File

@@ -27,18 +27,27 @@ const ROLE_NAMES: Record<AllRoleTypes, string> = {
'workflow:editor': 'Workflow Editor',
};
const mapToRoleObject = <T extends keyof typeof ROLE_NAMES>(roles: Record<T, Scope[]>) =>
const mapToRoleObject = <T extends keyof typeof ROLE_NAMES>(
roles: Record<T, Scope[]>,
roleType: 'global' | 'project' | 'credential' | 'workflow',
) =>
(Object.keys(roles) as T[]).map((role) => ({
role,
name: ROLE_NAMES[role],
slug: role,
displayName: ROLE_NAMES[role],
scopes: getRoleScopes(role),
description: ROLE_NAMES[role],
licensed: false,
systemRole: true,
roleType,
}));
export const ALL_ROLES: AllRolesMap = {
global: mapToRoleObject(GLOBAL_SCOPE_MAP),
project: mapToRoleObject(PROJECT_SCOPE_MAP),
credential: mapToRoleObject(CREDENTIALS_SHARING_SCOPE_MAP),
workflow: mapToRoleObject(WORKFLOW_SHARING_SCOPE_MAP),
global: mapToRoleObject(GLOBAL_SCOPE_MAP, 'global'),
project: mapToRoleObject(PROJECT_SCOPE_MAP, 'project'),
credential: mapToRoleObject(CREDENTIALS_SHARING_SCOPE_MAP, 'credential'),
workflow: mapToRoleObject(WORKFLOW_SHARING_SCOPE_MAP, 'workflow'),
};
export const isBuiltInRole = (role: string): role is AllRoleTypes => {
return Object.prototype.hasOwnProperty.call(ROLE_NAMES, role);
};

View File

@@ -80,6 +80,7 @@ export const GLOBAL_OWNER_SCOPES: Scope[] = [
'folder:move',
'oidc:manage',
'dataStore:list',
'role:manage',
];
export const GLOBAL_ADMIN_SCOPES = GLOBAL_OWNER_SCOPES.concat();

View File

@@ -1,6 +1,7 @@
import { z } from 'zod';
import { PROJECT_OWNER_ROLE_SLUG } from './constants.ee';
import { ALL_SCOPES } from './scope-information';
export const roleNamespaceSchema = z.enum(['global', 'project', 'credential', 'workflow']);
@@ -25,3 +26,21 @@ export const projectRoleSchema = z.union([personalRoleSchema, teamRoleSchema]);
export const credentialSharingRoleSchema = z.enum(['credential:owner', 'credential:user']);
export const workflowSharingRoleSchema = z.enum(['workflow:owner', 'workflow:editor']);
const ALL_SCOPES_LOOKUP_SET = new Set(ALL_SCOPES as string[]);
export const scopeSchema = z.string().refine((val) => ALL_SCOPES_LOOKUP_SET.has(val), {
message: 'Invalid scope',
});
export const roleSchema = z.object({
slug: z.string().min(1),
displayName: z.string().min(1),
description: z.string().nullable(),
systemRole: z.boolean(),
roleType: roleNamespaceSchema,
licensed: z.boolean(),
scopes: z.array(scopeSchema),
});
export type Role = z.infer<typeof roleSchema>;

View File

@@ -6,6 +6,7 @@ import type {
credentialSharingRoleSchema,
globalRoleSchema,
projectRoleSchema,
Role,
roleNamespaceSchema,
teamRoleSchema,
workflowSharingRoleSchema,
@@ -63,19 +64,11 @@ export type CustomRole = string;
/** Union of all possible role types in the system */
export type AllRoleTypes = GlobalRole | ProjectRole | WorkflowSharingRole | CredentialSharingRole;
type RoleObject<T extends AllRoleTypes> = {
role: T;
name: string;
description?: string | null;
scopes: Scope[];
licensed: boolean;
};
export type AllRolesMap = {
global: Array<RoleObject<GlobalRole>>;
project: Array<RoleObject<ProjectRole>>;
credential: Array<RoleObject<CredentialSharingRole>>;
workflow: Array<RoleObject<WorkflowSharingRole>>;
global: Role[];
project: Role[];
credential: Role[];
workflow: Role[];
};
export type DbScope = {

View File

@@ -34,6 +34,7 @@ describe('permissions', () => {
folder: {},
insights: {},
dataStore: {},
role: {},
});
});
it('getResourcePermissions', () => {
@@ -134,6 +135,7 @@ describe('permissions', () => {
dataStore: {},
execution: {},
workflowTags: {},
role: {},
};
expect(getResourcePermissions(scopes)).toEqual(permissionRecord);