mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-31 02:17:56 +08:00
fix(core): Block deletion of the SSO role mapping default condition role (#35086)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
1f6cddd1ab
commit
587b23d886
+36
-4
@@ -1,19 +1,27 @@
|
||||
import type { ProvisioningConfigDto } from '@n8n/api-types';
|
||||
import type { RoleMappingRuleRepository } from '@n8n/db';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { ProvisioningRoleDeletionChecker } from '../role-deletion-checker.ee';
|
||||
import type { ProvisioningService } from '../provisioning.service.ee';
|
||||
|
||||
describe('ProvisioningRoleDeletionChecker', () => {
|
||||
const roleMappingRuleRepository = mock<RoleMappingRuleRepository>();
|
||||
const checker = new ProvisioningRoleDeletionChecker(roleMappingRuleRepository);
|
||||
const provisioningService = mock<ProvisioningService>();
|
||||
const checker = new ProvisioningRoleDeletionChecker(
|
||||
roleMappingRuleRepository,
|
||||
provisioningService,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
roleMappingRuleRepository.count.mockResolvedValue(0);
|
||||
provisioningService.getConfig.mockResolvedValue(
|
||||
mock<ProvisioningConfigDto>({ defaultInstanceRole: undefined }),
|
||||
);
|
||||
});
|
||||
|
||||
it('reports no blockers when no mapping rule references the role', async () => {
|
||||
roleMappingRuleRepository.count.mockResolvedValue(0);
|
||||
|
||||
it('reports no blockers when the role is unreferenced', async () => {
|
||||
const blockers = await checker.findRoleDeletionBlockers('global:auditor');
|
||||
|
||||
expect(roleMappingRuleRepository.count).toHaveBeenCalledWith({
|
||||
@@ -37,4 +45,28 @@ describe('ProvisioningRoleDeletionChecker', () => {
|
||||
|
||||
expect(blockers).toEqual(['referenced by 3 role mapping rules']);
|
||||
});
|
||||
|
||||
it('reports a blocker when the role is the default condition role', async () => {
|
||||
provisioningService.getConfig.mockResolvedValue(
|
||||
mock<ProvisioningConfigDto>({ defaultInstanceRole: 'global:auditor' }),
|
||||
);
|
||||
|
||||
const blockers = await checker.findRoleDeletionBlockers('global:auditor');
|
||||
|
||||
expect(blockers).toEqual(['configured as the role mapping default condition role']);
|
||||
});
|
||||
|
||||
it('reports both blockers when the role is referenced by a mapping rule and is the default condition role', async () => {
|
||||
roleMappingRuleRepository.count.mockResolvedValue(1);
|
||||
provisioningService.getConfig.mockResolvedValue(
|
||||
mock<ProvisioningConfigDto>({ defaultInstanceRole: 'global:auditor' }),
|
||||
);
|
||||
|
||||
const blockers = await checker.findRoleDeletionBlockers('global:auditor');
|
||||
|
||||
expect(blockers).toEqual([
|
||||
'referenced by 1 role mapping rule',
|
||||
'configured as the role mapping default condition role',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,20 +3,36 @@ import { Service } from '@n8n/di';
|
||||
|
||||
import type { RoleDeletionChecker } from '@/services/role-deletion-check-proxy.service';
|
||||
|
||||
import { ProvisioningService } from './provisioning.service.ee';
|
||||
|
||||
/**
|
||||
* Blocks deletion of a custom role while it is still targeted by an SSO role
|
||||
* mapping rule. Registered on the core `RoleDeletionCheckProxy` by the
|
||||
* provisioning module so `RoleService` stays decoupled from this module.
|
||||
* mapping rule or configured as the role mapping default condition.
|
||||
* Registered on the core `RoleDeletionCheckProxy` by the provisioning module
|
||||
* so `RoleService` stays decoupled from this module.
|
||||
*/
|
||||
@Service()
|
||||
export class ProvisioningRoleDeletionChecker implements RoleDeletionChecker {
|
||||
constructor(private readonly roleMappingRuleRepository: RoleMappingRuleRepository) {}
|
||||
constructor(
|
||||
private readonly roleMappingRuleRepository: RoleMappingRuleRepository,
|
||||
private readonly provisioningService: ProvisioningService,
|
||||
) {}
|
||||
|
||||
async findRoleDeletionBlockers(roleSlug: string): Promise<string[]> {
|
||||
const blockers: string[] = [];
|
||||
|
||||
const count = await this.roleMappingRuleRepository.count({
|
||||
where: { role: { slug: roleSlug } },
|
||||
});
|
||||
if (count === 0) return [];
|
||||
return [`referenced by ${count} role mapping rule${count === 1 ? '' : 's'}`];
|
||||
if (count > 0) {
|
||||
blockers.push(`referenced by ${count} role mapping rule${count === 1 ? '' : 's'}`);
|
||||
}
|
||||
|
||||
const { defaultInstanceRole } = await this.provisioningService.getConfig();
|
||||
if (defaultInstanceRole === roleSlug) {
|
||||
blockers.push('configured as the role mapping default condition role');
|
||||
}
|
||||
|
||||
return blockers;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user