diff --git a/packages/backend/src/models/role.js b/packages/backend/src/models/role.js index d8b353cf..9e0404a1 100644 --- a/packages/backend/src/models/role.js +++ b/packages/backend/src/models/role.js @@ -109,17 +109,7 @@ class Role extends Base { return await this.$query().delete(); } - async $beforeUpdate(opt, queryContext) { - await super.$beforeUpdate(opt, queryContext); - - await this.preventAlteringAdmin(); - } - - async $beforeDelete(queryContext) { - await super.$beforeDelete(queryContext); - - await this.preventAlteringAdmin(); - + async assertNoRoleUserExists() { const userCount = await this.$relatedQuery('users').limit(1).resultSize(); const hasUsers = userCount > 0; @@ -135,7 +125,9 @@ class Role extends Base { type: 'ValidationError', }); } + } + async assertNoConfigurationUsage() { const samlAuthProviderUsingDefaultRole = await SamlAuthProvider.query() .where({ default_role_id: this.id, @@ -157,6 +149,26 @@ class Role extends Base { }); } } + + async assertRoleIsNotUsed() { + await this.assertNoRoleUserExists(); + + await this.assertNoConfigurationUsage(); + } + + async $beforeUpdate(opt, queryContext) { + await super.$beforeUpdate(opt, queryContext); + + await this.preventAlteringAdmin(); + } + + async $beforeDelete(queryContext) { + await super.$beforeDelete(queryContext); + + await this.preventAlteringAdmin(); + + await this.assertRoleIsNotUsed(); + } } export default Role; diff --git a/packages/backend/src/models/role.test.js b/packages/backend/src/models/role.test.js index f665713c..780c8f0d 100644 --- a/packages/backend/src/models/role.test.js +++ b/packages/backend/src/models/role.test.js @@ -5,6 +5,8 @@ import Permission from './permission.js'; import User from './user.js'; import { createRole } from '../../test/factories/role.js'; import { createPermission } from '../../test/factories/permission.js'; +import { createUser } from '../../test/factories/user.js'; +import { createSamlAuthProvider } from '../../test/factories/saml-auth-provider.ee.js'; describe('Role model', () => { it('tableName should return correct name', () => { @@ -203,4 +205,83 @@ describe('Role model', () => { expect(rolePermissions).toStrictEqual([]); }); }); + + describe('assertNoRoleUserExists', () => { + it('should reject with an error when the role has users', async () => { + const role = await createRole({ name: 'User' }); + await createUser({ roleId: role.id }); + + await expect(() => role.assertNoRoleUserExists()).rejects.toThrowError( + `All users must be migrated away from the "User" role.` + ); + }); + + it('should resolve when the role does not have any users', async () => { + const role = await createRole(); + + expect(await role.assertNoRoleUserExists()).toBe(undefined); + }); + }); + + describe('assertNoConfigurationUsage', () => { + it('should reject with an error when the role is used in configuration', async () => { + const role = await createRole(); + await createSamlAuthProvider({ defaultRoleId: role.id }); + + await expect(() => + role.assertNoConfigurationUsage() + ).rejects.toThrowError( + 'samlAuthProvider: You need to change the default role in the SAML configuration before deleting this role.' + ); + }); + + it('should resolve when the role does not have any users', async () => { + const role = await createRole(); + + expect(await role.assertNoConfigurationUsage()).toBe(undefined); + }); + }); + + it('assertRoleIsNotUsed should call assertNoRoleUserExists and assertNoConfigurationUsage', async () => { + const role = new Role(); + + const assertNoRoleUserExistsSpy = vi + .spyOn(role, 'assertNoRoleUserExists') + .mockResolvedValue(); + + const assertNoConfigurationUsageSpy = vi + .spyOn(role, 'assertNoConfigurationUsage') + .mockResolvedValue(); + + await role.assertRoleIsNotUsed(); + + expect(assertNoRoleUserExistsSpy).toHaveBeenCalledOnce(); + expect(assertNoConfigurationUsageSpy).toHaveBeenCalledOnce(); + }); + + describe('$beforeDelete', () => { + it('should call preventAlteringAdmin', async () => { + const role = await createRole({ name: 'User' }); + + const preventAlteringAdminSpy = vi + .spyOn(role, 'preventAlteringAdmin') + .mockResolvedValue(); + + await role.$query().delete(); + + expect(preventAlteringAdminSpy).toHaveBeenCalledOnce(); + }); + + it('should call assertRoleIsNotUsed', async () => { + const role = await createRole({ name: 'User' }); + + const assertRoleIsNotUsedSpy = vi + .spyOn(role, 'assertRoleIsNotUsed') + .mockResolvedValue(); + + await role.$query().delete(); + + expect(assertRoleIsNotUsedSpy).toHaveBeenCalledOnce(); + }); + }); });