1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-17 13:46:47 +02:00

fix: add guard for deleting role if the role is in use

This commit is contained in:
Fredrik Oseberg 2022-01-05 10:59:05 +01:00 committed by Ivar Conradi Østhus
parent dda9cba786
commit 1e466282e5
No known key found for this signature in database
GPG Key ID: 31AC596886B0BD09
4 changed files with 99 additions and 1 deletions

View File

@ -0,0 +1,24 @@
class RoleInUseError extends Error {
constructor(message: string) {
super();
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.message = message;
}
toJSON(): object {
return {
isJoi: true,
name: this.constructor.name,
details: [
{
message: this.message,
},
],
};
}
}
export default RoleInUseError;
module.exports = RoleInUseError;

View File

@ -58,6 +58,8 @@ export const handleErrors: (
return res.status(409).json(error).end(); return res.status(409).json(error).end();
case 'FeatureHasTagError': case 'FeatureHasTagError':
return res.status(409).json(error).end(); return res.status(409).json(error).end();
case 'RoleInUseError':
return res.status(400).json(error).end();
default: default:
logger.error('Server failed executing request', error); logger.error('Server failed executing request', error);
return res.status(500).end(); return res.status(500).end();

View File

@ -22,6 +22,7 @@ import {
import { IRoleStore } from 'lib/types/stores/role-store'; import { IRoleStore } from 'lib/types/stores/role-store';
import NameExistsError from '../error/name-exists-error'; import NameExistsError from '../error/name-exists-error';
import { IEnvironmentStore } from 'lib/types/stores/environment-store'; import { IEnvironmentStore } from 'lib/types/stores/environment-store';
import RoleInUseError from '../error/role-in-use-error';
export const ALL_PROJECTS = '*'; export const ALL_PROJECTS = '*';
export const ALL_ENVS = '*'; export const ALL_ENVS = '*';
@ -427,6 +428,14 @@ export class AccessService {
} }
async deleteRole(id: number): Promise<void> { async deleteRole(id: number): Promise<void> {
const roleUsers = await this.getUsersForRole(id);
if (roleUsers.length > 0) {
throw new RoleInUseError(
'Role is in use by more than one user. You cannot delete a role that is in use without first removing the role from the users.',
);
}
return this.roleStore.delete(id); return this.roleStore.delete(id);
} }

View File

@ -10,11 +10,15 @@ import {
import * as permissions from '../../../lib/types/permissions'; import * as permissions from '../../../lib/types/permissions';
import { RoleName } from '../../../lib/types/model'; import { RoleName } from '../../../lib/types/model';
import { IUnleashStores } from '../../../lib/types'; import { IUnleashStores } from '../../../lib/types';
import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import ProjectService from '../../../lib/services/project-service';
import { createTestConfig } from '../../config/test-config';
let db: ITestDb; let db: ITestDb;
let stores: IUnleashStores; let stores: IUnleashStores;
let accessService; let accessService;
let featureToggleService;
let projectService;
let editorUser; let editorUser;
let superUser; let superUser;
let editorRole; let editorRole;
@ -182,11 +186,23 @@ beforeAll(async () => {
db = await dbInit('access_service_serial', getLogger); db = await dbInit('access_service_serial', getLogger);
stores = db.stores; stores = db.stores;
// projectStore = stores.projectStore; // projectStore = stores.projectStore;
const config = createTestConfig({
getLogger,
// @ts-ignore
experimental: { environments: { enabled: true } },
});
accessService = new AccessService(stores, { getLogger }); accessService = new AccessService(stores, { getLogger });
const roles = await accessService.getRootRoles(); const roles = await accessService.getRootRoles();
editorRole = roles.find((r) => r.name === RoleName.EDITOR); editorRole = roles.find((r) => r.name === RoleName.EDITOR);
adminRole = roles.find((r) => r.name === RoleName.ADMIN); adminRole = roles.find((r) => r.name === RoleName.ADMIN);
readRole = roles.find((r) => r.name === RoleName.VIEWER); readRole = roles.find((r) => r.name === RoleName.VIEWER);
featureToggleService = new FeatureToggleService(stores, config);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
);
editorUser = await createUserEditorAccess('Bob Test', 'bob@getunleash.io'); editorUser = await createUserEditorAccess('Bob Test', 'bob@getunleash.io');
superUser = await createSuperUser(); superUser = await createSuperUser();
@ -604,3 +620,50 @@ test('Should be denied access to delete a strategy in an environment the user do
), ),
).toBe(false); ).toBe(false);
}); });
test('Should be denied access to delete a role that is in use', async () => {
const user = editorUser;
const project = {
id: 'projectToUseRole',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user.id);
const projectMember = await stores.userStore.insert({
name: 'CustomProjectMember',
email: 'custom@getunleash.io',
});
const customRole = await accessService.createRole({
name: 'RoleInUse',
description: '',
permissions: [
{
id: 2,
name: 'CREATE_FEATURE',
environment: null,
displayName: 'Create Feature Toggles',
type: 'project',
},
{
id: 8,
name: 'DELETE_FEATURE',
environment: null,
displayName: 'Delete Feature Toggles',
type: 'project',
},
],
});
await projectService.addUser(project.id, customRole.id, projectMember.id);
try {
await accessService.deleteRole(customRole.id);
} catch (e) {
expect(e.toString()).toBe(
'RoleInUseError: Role is in use by more than one user. You cannot delete a role that is in use without first removing the role from the users.',
);
}
});