1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01:00

fix: rename rbac roles. (#788)

* fix: rename rbac roles.

Root-roles:
- Admin
- Editor
- Viewer

Project roles:
- Owner
- Member

* Update src/lib/services/access-service.ts

* Update src/migrations/20210415173116-rbac-rename-roles.js

Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai>
This commit is contained in:
Ivar Conradi Østhus 2021-04-16 10:45:15 +02:00 committed by GitHub
parent c45c19fedb
commit 23ea21babf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 103 deletions

View File

@ -5,8 +5,8 @@ import User from '../user';
export const ALL_PROJECTS = '*';
const PROJECT_DESCRIPTION = {
ADMIN: 'Users with the project admin role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.',
REGULAR: 'Users with the regular role within a project are allowed to view, create and update feature toggles, but have limited permissions in regards to managing the projects user access and can not archive or delete the project.',
OWNER: 'Users with this role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.',
MEMBER: 'Users with this role within a project are allowed to view, create and update feature toggles, but have limited permissions in regards to managing the projects user access and can not archive or delete the project.',
};
const { ADMIN } = p;
@ -55,8 +55,10 @@ enum PermissionType {
export enum RoleName {
ADMIN = 'Admin',
REGULAR = 'Regular',
READ = 'Read',
EDITOR = 'Editor',
VIEWER = 'Viewer',
OWNER = 'Owner',
MEMBER = 'Member'
}
export enum RoleType {
@ -199,32 +201,32 @@ export class AccessService {
throw new Error("ProjectId cannot be empty");
}
const adminRole = await this.store.createRole(
RoleName.ADMIN,
const ownerRole = await this.store.createRole(
RoleName.OWNER,
RoleType.PROJECT,
projectId,
PROJECT_DESCRIPTION.ADMIN,
PROJECT_DESCRIPTION.OWNER,
);
await this.store.addPermissionsToRole(
adminRole.id,
ownerRole.id,
PROJECT_ADMIN,
projectId,
);
// TODO: remove this when all users is guaranteed to have a unique id.
if (owner.id) {
this.logger.info(`Making ${owner.id} admin of ${projectId} via roleId=${adminRole.id}`);
await this.store.addUserToRole(owner.id, adminRole.id);
this.logger.info(`Making ${owner.id} admin of ${projectId} via roleId=${ownerRole.id}`);
await this.store.addUserToRole(owner.id, ownerRole.id);
};
const regularRole = await this.store.createRole(
RoleName.REGULAR,
const memberRole = await this.store.createRole(
RoleName.MEMBER,
RoleType.PROJECT,
projectId,
PROJECT_DESCRIPTION.REGULAR,
PROJECT_DESCRIPTION.MEMBER,
);
await this.store.addPermissionsToRole(
regularRole.id,
memberRole.id,
PROJECT_REGULAR,
projectId
);

View File

@ -95,7 +95,7 @@ class UserService {
async getAll(): Promise<IUserWithRole[]> {
const users = await this.store.getAll();
const defaultRole = await this.accessService.getRootRole(RoleName.READ);
const defaultRole = await this.accessService.getRootRole(RoleName.VIEWER);
const userRoles = await this.accessService.getRootRoleForAllUsers();
const usersWithRootRole = users.map(u => {
const rootRole = userRoles.find(r => r.userId === u.id);
@ -107,7 +107,7 @@ class UserService {
async getUser(id: number): Promise<IUserWithRole> {
const roles = await this.accessService.getUserRootRoles(id);
const defaultRole = await this.accessService.getRootRole(RoleName.READ);
const defaultRole = await this.accessService.getRootRole(RoleName.VIEWER);
const roleId = roles.length > 0 ? roles[0].id : defaultRole.id;
const user = await this.store.get({ id });
return { ...user, rootRole: roleId };
@ -201,7 +201,7 @@ class UserService {
} catch (e) {
if (autoCreateUser) {
const defaultRole = await this.accessService.getRootRole(
RoleName.REGULAR,
RoleName.EDITOR,
);
user = await this.createUser({
email,

View File

@ -0,0 +1,34 @@
'use strict';
const DESCRIPTION = {
EDITOR:
'Users with this role have access most features in Unleash, but can not manage users and roles in the global scope. If a user with a global regular role creates a project, they will become a project admin and receive superuser rights within the context of that project.',
OWNER:
'Users with this role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.',
MEMBER:
'Users with this role within a project are allowed to view, create and update feature toggles, but have limited permissions in regards to managing the projects user access and can not archive or delete the project.',
};
exports.up = function(db, cb) {
db.runSql(
`
UPDATE roles set name = 'Editor', description = '${DESCRIPTION.EDITOR}' where name = 'Regular' AND type = 'root';
UPDATE roles set name = 'Viewer' where name = 'Read' AND type = 'root';
UPDATE roles set name = 'Owner', description = '${DESCRIPTION.OWNER}' where name = 'Admin' AND type = 'project';
UPDATE roles set name = 'Member', description = '${DESCRIPTION.MEMBER}' where name = 'Regular' AND type = 'project';
`,
cb,
);
};
exports.down = function(db, cb) {
db.runSql(
`
UPDATE roles set name = 'Regular' where name = 'Editor' AND type = 'root';
UPDATE roles set name = 'Read' where name = 'Viewer' AND type = 'root';
UPDATE roles set name = 'Admin' where name = 'Owner' AND type = 'project';
UPDATE roles set name = 'Regular' where name = 'Member' AND type = 'project';
`,
cb,
);
};

View File

@ -190,7 +190,7 @@ test.serial('none-admins should only get client tokens', async t => {
const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.REGULAR);
const role = await accessService.getRootRole(RoleName.EDITOR);
const user = await userService.createUser({
email,
rootRole: role.id,
@ -231,7 +231,7 @@ test.serial('Only token-admins should be allowed to create token', async t => {
const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.REGULAR);
const role = await accessService.getRootRole(RoleName.EDITOR);
req.user = await userService.createUser({
email,
rootRole: role.id,

View File

@ -12,7 +12,7 @@ let db;
let userStore: UserStore;
let accessStore: AccessStore;
let regularRole: IRole;
let editorRole: IRole;
let adminRole: IRole;
test.before(async () => {
@ -21,7 +21,7 @@ test.before(async () => {
userStore = stores.userStore;
accessStore = stores.accessStore;
const roles = await accessStore.getRootRoles();
regularRole = roles.find(r => r.name === RoleName.REGULAR);
editorRole = roles.find(r => r.name === RoleName.EDITOR);
adminRole = roles.find(r => r.name === RoleName.ADMIN);
});
@ -57,7 +57,7 @@ test.serial('creates and returns all users', async t => {
.send({
email: `some${i}@getunleash.ai`,
name: `Some Name ${i}`,
rootRole: regularRole.id,
rootRole: editorRole.id,
})
.set('Content-Type', 'application/json'),
);
@ -70,11 +70,11 @@ test.serial('creates and returns all users', async t => {
.expect(200)
.expect(res => {
t.is(res.body.users.length, 20);
t.is(res.body.users[2].rootRole, regularRole.id);
t.is(res.body.users[2].rootRole, editorRole.id);
});
});
test.serial('creates regular-user without password', async t => {
test.serial('creates editor-user without password', async t => {
t.plan(3);
const request = await setupApp(stores);
return request
@ -82,13 +82,13 @@ test.serial('creates regular-user without password', async t => {
.send({
email: 'some@getunelash.ai',
name: 'Some Name',
rootRole: regularRole.id,
rootRole: editorRole.id,
})
.set('Content-Type', 'application/json')
.expect(201)
.expect(res => {
t.is(res.body.email, 'some@getunelash.ai');
t.is(res.body.rootRole, regularRole.id);
t.is(res.body.rootRole, editorRole.id);
t.truthy(res.body.id);
});
});
@ -143,7 +143,7 @@ test.serial('update user name', async t => {
.send({
email: 'some@getunelash.ai',
name: 'Some Name',
rootRole: regularRole.id,
rootRole: editorRole.id,
})
.set('Content-Type', 'application/json');
@ -157,7 +157,6 @@ test.serial('update user name', async t => {
.expect(res => {
t.is(res.body.email, 'some@getunelash.ai');
t.is(res.body.name, 'New name');
// t.is(res.body.rootRole, 'Regular');
t.is(res.body.id, body.id);
});
});

View File

@ -15,16 +15,16 @@ let db;
let stores;
let accessService;
let regularUser;
let editorUser;
let superUser;
let regularRole;
let editorRole;
let adminRole;
let readRole;
const createUserWithRegularAccess = async (name, email) => {
const createUserEditorAccess = async (name, email) => {
const { userStore } = stores;
const user = await userStore.insert(new User({ name, email }));
await accessService.addUserToRole(user.id, regularRole.id);
await accessService.addUserToRole(user.id, editorRole.id);
return user;
};
@ -43,14 +43,11 @@ test.before(async () => {
// projectStore = stores.projectStore;
accessService = new AccessService(stores, { getLogger });
const roles = await accessService.getRootRoles();
regularRole = roles.find(r => r.name === RoleName.REGULAR);
editorRole = roles.find(r => r.name === RoleName.EDITOR);
adminRole = roles.find(r => r.name === RoleName.ADMIN);
readRole = roles.find(r => r.name === RoleName.READ);
readRole = roles.find(r => r.name === RoleName.VIEWER);
regularUser = await createUserWithRegularAccess(
'Bob Test',
'bob@getunleash.io',
);
editorUser = await createUserEditorAccess('Bob Test', 'bob@getunleash.io');
superUser = await createSuperUser();
});
@ -60,7 +57,7 @@ test.after(async () => {
test.serial('should have access to admin addons', async t => {
const { CREATE_ADDON, UPDATE_ADDON, DELETE_ADDON } = permissions;
const user = regularUser;
const user = editorUser;
t.true(await accessService.hasPermission(user, CREATE_ADDON));
t.true(await accessService.hasPermission(user, UPDATE_ADDON));
t.true(await accessService.hasPermission(user, DELETE_ADDON));
@ -68,7 +65,7 @@ test.serial('should have access to admin addons', async t => {
test.serial('should have access to admin strategies', async t => {
const { CREATE_STRATEGY, UPDATE_STRATEGY, DELETE_STRATEGY } = permissions;
const user = regularUser;
const user = editorUser;
t.true(await accessService.hasPermission(user, CREATE_STRATEGY));
t.true(await accessService.hasPermission(user, UPDATE_STRATEGY));
t.true(await accessService.hasPermission(user, DELETE_STRATEGY));
@ -80,7 +77,7 @@ test.serial('should have access to admin contexts', async t => {
UPDATE_CONTEXT_FIELD,
DELETE_CONTEXT_FIELD,
} = permissions;
const user = regularUser;
const user = editorUser;
t.true(await accessService.hasPermission(user, CREATE_CONTEXT_FIELD));
t.true(await accessService.hasPermission(user, UPDATE_CONTEXT_FIELD));
t.true(await accessService.hasPermission(user, DELETE_CONTEXT_FIELD));
@ -88,19 +85,19 @@ test.serial('should have access to admin contexts', async t => {
test.serial('should have access to create projects', async t => {
const { CREATE_PROJECT } = permissions;
const user = regularUser;
const user = editorUser;
t.true(await accessService.hasPermission(user, CREATE_PROJECT));
});
test.serial('should have access to update applications', async t => {
const { UPDATE_APPLICATION } = permissions;
const user = regularUser;
const user = editorUser;
t.true(await accessService.hasPermission(user, UPDATE_APPLICATION));
});
test.serial('should not have admin permission', async t => {
const { ADMIN } = permissions;
const user = regularUser;
const user = editorUser;
t.false(await accessService.hasPermission(user, ADMIN));
});
@ -112,7 +109,7 @@ test.serial('should have project admin to default project', async t => {
UPDATE_FEATURE,
DELETE_FEATURE,
} = permissions;
const user = regularUser;
const user = editorUser;
t.true(await accessService.hasPermission(user, DELETE_PROJECT, 'default'));
t.true(await accessService.hasPermission(user, UPDATE_PROJECT, 'default'));
t.true(await accessService.hasPermission(user, CREATE_FEATURE, 'default'));
@ -120,12 +117,12 @@ test.serial('should have project admin to default project', async t => {
t.true(await accessService.hasPermission(user, DELETE_FEATURE, 'default'));
});
test.serial('should grant regular CREATE_FEATURE on all projects', async t => {
test.serial('should grant member CREATE_FEATURE on all projects', async t => {
const { CREATE_FEATURE } = permissions;
const user = regularUser;
const user = editorUser;
await accessService.addPermissionToRole(
regularRole.id,
editorRole.id,
permissions.CREATE_FEATURE,
ALL_PROJECTS,
);
@ -139,7 +136,7 @@ test.serial('cannot add CREATE_FEATURE without defining project', async t => {
await t.throwsAsync(
async () => {
await accessService.addPermissionToRole(
regularRole.id,
editorRole.id,
permissions.CREATE_FEATURE,
);
},
@ -156,7 +153,7 @@ test.serial(
await t.throwsAsync(
async () => {
await accessService.removePermissionFromRole(
regularRole.id,
editorRole.id,
permissions.CREATE_FEATURE,
);
},
@ -171,16 +168,16 @@ test.serial(
test.serial('should remove CREATE_FEATURE on all projects', async t => {
const { CREATE_FEATURE } = permissions;
const user = regularUser;
const user = editorUser;
await accessService.addPermissionToRole(
regularRole.id,
editorRole.id,
permissions.CREATE_FEATURE,
ALL_PROJECTS,
);
await accessService.removePermissionFromRole(
regularRole.id,
editorRole.id,
permissions.CREATE_FEATURE,
ALL_PROJECTS,
);
@ -217,7 +214,7 @@ test.serial('should create default roles to project', async t => {
DELETE_FEATURE,
} = permissions;
const project = 'some-project';
const user = regularUser;
const user = editorUser;
await accessService.createDefaultProjectRoles(user, project);
t.true(await accessService.hasPermission(user, UPDATE_PROJECT, project));
t.true(await accessService.hasPermission(user, DELETE_PROJECT, project));
@ -231,7 +228,7 @@ test.serial(
async t => {
await t.throwsAsync(
async () => {
await accessService.createDefaultProjectRoles(regularUser);
await accessService.createDefaultProjectRoles(editorUser);
},
{ instanceOf: Error, message: 'ProjectId cannot be empty' },
);
@ -247,8 +244,8 @@ test.serial('should grant user access to project', async t => {
DELETE_FEATURE,
} = permissions;
const project = 'another-project';
const user = regularUser;
const sUser = await createUserWithRegularAccess(
const user = editorUser;
const sUser = await createUserEditorAccess(
'Some Random',
'random@getunleash.io',
);
@ -257,7 +254,7 @@ test.serial('should grant user access to project', async t => {
const roles = await accessService.getRolesForProject(project);
const projectRole = roles.find(
r => r.name === 'Regular' && r.project === project,
r => r.name === 'Member' && r.project === project,
);
await accessService.addUserToRole(sUser.id, projectRole.id);
@ -274,8 +271,8 @@ test.serial('should grant user access to project', async t => {
test.serial('should not get access if not specifying project', async t => {
const { CREATE_FEATURE, UPDATE_FEATURE, DELETE_FEATURE } = permissions;
const project = 'another-project-2';
const user = regularUser;
const sUser = await createUserWithRegularAccess(
const user = editorUser;
const sUser = await createUserEditorAccess(
'Some Random',
'random22@getunleash.io',
);
@ -284,7 +281,7 @@ test.serial('should not get access if not specifying project', async t => {
const roles = await accessService.getRolesForProject(project);
const projectRole = roles.find(
r => r.name === 'Regular' && r.project === project,
r => r.name === 'Member' && r.project === project,
);
await accessService.addUserToRole(sUser.id, projectRole.id);
@ -300,14 +297,14 @@ test.serial('should remove user from role', async t => {
new User({ name: 'Some User', email: 'random123@getunleash.io' }),
);
await accessService.addUserToRole(user.id, regularRole.id);
await accessService.addUserToRole(user.id, editorRole.id);
// check user has one role
const userRoles = await accessService.getRolesForUser(user.id);
t.is(userRoles.length, 1);
t.is(userRoles[0].name, 'Regular');
t.is(userRoles[0].name, RoleName.EDITOR);
await accessService.removeUserFromRole(user.id, regularRole.id);
await accessService.removeUserFromRole(user.id, editorRole.id);
const userRolesAfterRemove = await accessService.getRolesForUser(user.id);
t.is(userRolesAfterRemove.length, 0);
});
@ -318,11 +315,11 @@ test.serial('should return role with users', async t => {
new User({ name: 'Some User', email: 'random2223@getunleash.io' }),
);
await accessService.addUserToRole(user.id, regularRole.id);
await accessService.addUserToRole(user.id, editorRole.id);
const roleWithUsers = await accessService.getRole(regularRole.id);
const roleWithUsers = await accessService.getRole(editorRole.id);
t.is(roleWithUsers.role.name, 'Regular');
t.is(roleWithUsers.role.name, RoleName.EDITOR);
t.true(roleWithUsers.users.length > 2);
t.truthy(roleWithUsers.users.find(u => u.id === user.id));
t.truthy(roleWithUsers.users.find(u => u.email === user.email));
@ -334,11 +331,11 @@ test.serial('should return role with permissions and users', async t => {
new User({ name: 'Some User', email: 'random2244@getunleash.io' }),
);
await accessService.addUserToRole(user.id, regularRole.id);
await accessService.addUserToRole(user.id, editorRole.id);
const roleWithPermission = await accessService.getRole(regularRole.id);
const roleWithPermission = await accessService.getRole(editorRole.id);
t.is(roleWithPermission.role.name, 'Regular');
t.is(roleWithPermission.role.name, RoleName.EDITOR);
t.true(roleWithPermission.permissions.length > 2);
t.truthy(
roleWithPermission.permissions.find(
@ -375,12 +372,12 @@ test.serial('should set root role for user', async t => {
new User({ name: 'Some User', email: 'random2255@getunleash.io' }),
);
await accessService.setUserRootRole(user.id, regularRole.id);
await accessService.setUserRootRole(user.id, editorRole.id);
const roles = await accessService.getRolesForUser(user.id);
t.is(roles.length, 1);
t.is(roles[0].name, RoleName.REGULAR);
t.is(roles[0].name, RoleName.EDITOR);
});
test.serial('should switch root role for user', async t => {
@ -389,11 +386,11 @@ test.serial('should switch root role for user', async t => {
new User({ name: 'Some User', email: 'random22Read@getunleash.io' }),
);
await accessService.setUserRootRole(user.id, regularRole.id);
await accessService.setUserRootRole(user.id, editorRole.id);
await accessService.setUserRootRole(user.id, readRole.id);
const roles = await accessService.getRolesForUser(user.id);
t.is(roles.length, 1);
t.is(roles[0].name, RoleName.READ);
t.is(roles[0].name, RoleName.VIEWER);
});

View File

@ -222,17 +222,17 @@ test.serial('should get list of users with access to project', async t => {
user,
);
const admin = roles.find(role => role.name === RoleName.ADMIN);
const regular = roles.find(role => role.name === RoleName.REGULAR);
const owner = roles.find(role => role.name === RoleName.OWNER);
const member = roles.find(role => role.name === RoleName.MEMBER);
t.is(users.length, 1);
t.is(users[0].id, user.id);
t.is(users[0].name, user.name);
t.is(users[0].roleId, admin.id);
t.truthy(regular);
t.is(users[0].roleId, owner.id);
t.truthy(member);
});
test.serial('should add a regular user to the project', async t => {
test.serial('should add a member user to the project', async t => {
const project = {
id: 'add-users',
name: 'New project',
@ -248,19 +248,19 @@ test.serial('should add a regular user to the project', async t => {
);
const roles = await stores.accessStore.getRolesForProject(project.id);
const regularRole = roles.find(r => r.name === RoleName.REGULAR);
const memberRole = roles.find(r => r.name === RoleName.MEMBER);
await projectService.addUser(project.id, regularRole.id, projectMember1.id);
await projectService.addUser(project.id, regularRole.id, projectMember2.id);
await projectService.addUser(project.id, memberRole.id, projectMember1.id);
await projectService.addUser(project.id, memberRole.id, projectMember2.id);
const { users } = await projectService.getUsersWithAccess(project.id, user);
const regularUsers = users.filter(u => u.roleId === regularRole.id);
const memberUsers = users.filter(u => u.roleId === memberRole.id);
t.is(regularUsers.length, 2);
t.is(regularUsers[0].id, projectMember1.id);
t.is(regularUsers[0].name, projectMember1.name);
t.is(regularUsers[1].id, projectMember2.id);
t.is(regularUsers[1].name, projectMember2.name);
t.is(memberUsers.length, 2);
t.is(memberUsers[0].id, projectMember1.id);
t.is(memberUsers[0].name, projectMember1.name);
t.is(memberUsers[1].id, projectMember2.id);
t.is(memberUsers[1].name, projectMember2.name);
});
test.serial('should add admin users to the project', async t => {
@ -281,14 +281,14 @@ test.serial('should add admin users to the project', async t => {
const projectRoles = await stores.accessStore.getRolesForProject(
project.id,
);
const adminRole = projectRoles.find(r => r.name === RoleName.ADMIN);
const ownerRole = projectRoles.find(r => r.name === RoleName.OWNER);
await projectService.addUser(project.id, adminRole.id, projectAdmin1.id);
await projectService.addUser(project.id, adminRole.id, projectAdmin2.id);
await projectService.addUser(project.id, ownerRole.id, projectAdmin1.id);
await projectService.addUser(project.id, ownerRole.id, projectAdmin2.id);
const { users } = await projectService.getUsersWithAccess(project.id, user);
const adminUsers = users.filter(u => u.roleId === adminRole.id);
const adminUsers = users.filter(u => u.roleId === ownerRole.id);
t.is(adminUsers.length, 3);
t.is(adminUsers[1].id, projectAdmin1.id);
@ -299,15 +299,15 @@ test.serial('should add admin users to the project', async t => {
test.serial('add user only accept to add users to project roles', async t => {
const roles = await accessService.getRoles();
const regularRole = roles.find(r => r.name === RoleName.REGULAR);
const memberRole = roles.find(r => r.name === RoleName.MEMBER);
await t.throwsAsync(
async () => {
await projectService.addUser('some-id', regularRole.id, user.id);
await projectService.addUser('some-id', memberRole.id, user.id);
},
{
instanceOf: NotFoundError,
message: 'Could not find roleId=2 on project=some-id',
message: /Could not find roleId=\d+ on project=some-id/,
},
);
});
@ -325,15 +325,15 @@ test.serial('add user should fail if user already have access', async t => {
);
const roles = await stores.accessStore.getRolesForProject(project.id);
const regularRole = roles.find(r => r.name === RoleName.REGULAR);
const memberRole = roles.find(r => r.name === RoleName.MEMBER);
await projectService.addUser(project.id, regularRole.id, projectMember1.id);
await projectService.addUser(project.id, memberRole.id, projectMember1.id);
await t.throwsAsync(
async () => {
await projectService.addUser(
project.id,
regularRole.id,
memberRole.id,
projectMember1.id,
);
},
@ -357,17 +357,17 @@ test.serial('should remove user from the project', async t => {
);
const roles = await stores.accessStore.getRolesForProject(project.id);
const regularRole = roles.find(r => r.name === RoleName.REGULAR);
const memberRole = roles.find(r => r.name === RoleName.MEMBER);
await projectService.addUser(project.id, regularRole.id, projectMember1.id);
await projectService.addUser(project.id, memberRole.id, projectMember1.id);
await projectService.removeUser(
project.id,
regularRole.id,
memberRole.id,
projectMember1.id,
);
const { users } = await projectService.getUsersWithAccess(project.id, user);
const regularUsers = users.filter(u => u.roleId === regularRole.id);
const memberUsers = users.filter(u => u.roleId === memberRole.id);
t.is(regularUsers.length, 0);
t.is(memberUsers.length, 0);
});