mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-28 17:22:01 +08:00
feat(editor): Warn about self-escalation via Manage project roles scope (#36458)
This commit is contained in:
@@ -2991,6 +2991,7 @@
|
||||
"instanceRoles.description.insights.view": "View instance-level insights",
|
||||
"instanceRoles.warning.manageMembers": "A user with this permission can give themselves or an account they invite permissions they don't currently hold. {link}",
|
||||
"instanceRoles.warning.manageRoles": "A user with this permission can edit their own custom role to add permissions they weren't originally granted. {link}",
|
||||
"instanceRoles.warning.manageProjectRoles": "A user with this permission can edit a custom project role they hold themselves to grant themselves permissions they weren't originally granted in that project. {link}",
|
||||
"instanceRoles.warning.viewDocs": "View docs",
|
||||
"roles.instance.newRole": "New instance role",
|
||||
"roles.instance.editRole": "Custom instance role",
|
||||
|
||||
+7
@@ -117,6 +117,13 @@ describe('ScopeGroupSelector', () => {
|
||||
expect(getByTestId('scope-escalation-warning-role')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the project-roles warning when only role:manageProject is selected', () => {
|
||||
const { getByTestId } = renderComponent(ScopeGroupSelector, {
|
||||
props: { modelValue: ['role:read', 'role:manageProject'] },
|
||||
});
|
||||
expect(getByTestId('scope-escalation-warning-role')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('does not render a warning for a non-escalating scope', () => {
|
||||
const { queryByTestId } = renderComponent(ScopeGroupSelector, {
|
||||
props: { modelValue: ['tag:read'] },
|
||||
|
||||
@@ -212,8 +212,16 @@ describe('getEscalationWarningKey', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('does not warn for "Manage project roles" alone (role:manageProject without role:manage)', () => {
|
||||
expect(getEscalationWarningKey('role', ['role:read', 'role:manageProject'])).toBeUndefined();
|
||||
it('returns the project-roles warning for "Manage project roles" alone (role:manageProject without role:manage)', () => {
|
||||
expect(getEscalationWarningKey('role', ['role:read', 'role:manageProject'])).toBe(
|
||||
'instanceRoles.warning.manageProjectRoles',
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers the roles warning over the project-roles warning when both scopes are present', () => {
|
||||
expect(
|
||||
getEscalationWarningKey('role', ['role:read', 'role:manage', 'role:manageProject']),
|
||||
).toBe('instanceRoles.warning.manageRoles');
|
||||
});
|
||||
|
||||
it('returns undefined for a non-escalating resource', () => {
|
||||
|
||||
@@ -266,20 +266,32 @@ export function toggleOptionInGroup(
|
||||
return [...next];
|
||||
}
|
||||
|
||||
/** Resource groups whose scopes enable privilege escalation, with the warning to show. */
|
||||
/**
|
||||
* Resource groups whose scopes enable privilege escalation, with the warning to show.
|
||||
* Entries are checked in order; the first matching scope's message wins.
|
||||
*/
|
||||
export const ESCALATION_WARNING_SCOPES: Partial<
|
||||
Record<InstanceResource, { scopes: Scope[]; messageKey: BaseTextKey }>
|
||||
Record<InstanceResource, Array<{ scopes: Scope[]; messageKey: BaseTextKey }>>
|
||||
> = {
|
||||
user: {
|
||||
scopes: [...GLOBAL_CUSTOM_ROLE_SCOPE_GROUPS.user.Manage],
|
||||
messageKey: 'instanceRoles.warning.manageMembers',
|
||||
},
|
||||
role: {
|
||||
// Only full instance-role management ("Manage all roles") enables self-escalation;
|
||||
// managing project roles alone cannot edit the holder's own instance role.
|
||||
scopes: ['role:manage'],
|
||||
messageKey: 'instanceRoles.warning.manageRoles',
|
||||
},
|
||||
user: [
|
||||
{
|
||||
scopes: [...GLOBAL_CUSTOM_ROLE_SCOPE_GROUPS.user.Manage],
|
||||
messageKey: 'instanceRoles.warning.manageMembers',
|
||||
},
|
||||
],
|
||||
role: [
|
||||
{
|
||||
// Full instance-role management: can edit the holder's own instance role.
|
||||
scopes: ['role:manage'],
|
||||
messageKey: 'instanceRoles.warning.manageRoles',
|
||||
},
|
||||
{
|
||||
// Project-role management alone: can edit the scopes of any custom
|
||||
// project role, including one the holder is themselves assigned in a project.
|
||||
scopes: ['role:manageProject'],
|
||||
messageKey: 'instanceRoles.warning.manageProjectRoles',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/** Warning i18n key for a resource group given the current scopes, or undefined. */
|
||||
@@ -287,8 +299,8 @@ export function getEscalationWarningKey(
|
||||
resource: InstanceResource,
|
||||
scopes: readonly string[],
|
||||
): BaseTextKey | undefined {
|
||||
const cfg = ESCALATION_WARNING_SCOPES[resource];
|
||||
return cfg?.scopes.some((s) => scopes.includes(s)) ? cfg.messageKey : undefined;
|
||||
const cfgs = ESCALATION_WARNING_SCOPES[resource];
|
||||
return cfgs?.find((cfg) => cfg.scopes.some((s) => scopes.includes(s)))?.messageKey;
|
||||
}
|
||||
|
||||
/** Total number of permission options shown in the instance role editor. */
|
||||
|
||||
Reference in New Issue
Block a user