1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

fix: add method to change role for project memeber

This commit is contained in:
Ivar Conradi Østhus 2022-02-21 14:39:59 +01:00
parent d56611675f
commit 8364c3b396
7 changed files with 191 additions and 18 deletions

View File

@ -247,6 +247,19 @@ export class AccessStore implements IAccessStore {
.delete();
}
async updateUserProjectRole(
userId: number,
roleId: number,
projectId: string,
): Promise<void> {
return this.db(T.ROLE_USER)
.where({
user_id: userId,
project: projectId,
})
.update('role_id', roleId);
}
async removeRolesOfTypeForUser(
userId: number,
roleType: string,

View File

@ -220,6 +220,14 @@ export class AccessService {
return this.store.removeUserFromRole(userId, roleId, projectId);
}
async updateUserProjectRole(
userId: number,
roleId: number,
projectId: string,
): Promise<void> {
return this.store.updateUserProjectRole(userId, roleId, projectId);
}
//This actually only exists for testing purposes
async addPermissionToRole(
roleId: number,

View File

@ -8,6 +8,7 @@ import NotFoundError from '../error/notfound-error';
import {
ProjectUserAddedEvent,
ProjectUserRemovedEvent,
ProjectUserUpdateRoleEvent,
PROJECT_CREATED,
PROJECT_DELETED,
PROJECT_UPDATED,
@ -309,23 +310,9 @@ export default class ProjectService {
userId: number,
createdBy?: string,
): Promise<void> {
const roles = await this.accessService.getRolesForProject(projectId);
const role = roles.find((r) => r.id === roleId);
if (!role) {
throw new NotFoundError(
`Couldn't find roleId=${roleId} on project=${projectId}`,
);
}
const role = await this.findProjectRole(projectId, roleId);
if (role.name === RoleName.OWNER) {
const users = await this.accessService.getProjectUsersForRole(
role.id,
projectId,
);
if (users.length < 2) {
throw new Error('A project must have at least one owner');
}
}
await this.validateAtLeastOneOwner(projectId, role);
await this.accessService.removeUserFromRole(userId, role.id, projectId);
@ -338,6 +325,76 @@ export default class ProjectService {
);
}
async findProjectRole(
projectId: string,
roleId: number,
): Promise<IRoleDescriptor> {
const roles = await this.accessService.getRolesForProject(projectId);
const role = roles.find((r) => r.id === roleId);
if (!role) {
throw new NotFoundError(
`Couldn't find roleId=${roleId} on project=${projectId}`,
);
}
return role;
}
async validateAtLeastOneOwner(
projectId: string,
currentRole: IRoleDescriptor,
): Promise<void> {
if (currentRole.name === RoleName.OWNER) {
const users = await this.accessService.getProjectUsersForRole(
currentRole.id,
projectId,
);
if (users.length < 2) {
throw new Error('A project must have at least one owner');
}
}
}
async changeRole(
projectId: string,
roleId: number,
userId: number,
createdBy: string,
): Promise<void> {
const role = await this.findProjectRole(projectId, roleId);
const usersWithRoles = await this.getUsersWithAccess(projectId);
const user = usersWithRoles.users.find((u) => u.id === userId);
const currentRole = usersWithRoles.roles.find(
(r) => r.id === user.roleId,
);
if (currentRole.id === roleId) {
// Nothing to do....
return;
}
await this.validateAtLeastOneOwner(projectId, currentRole);
await this.accessService.updateUserProjectRole(
userId,
roleId,
projectId,
);
await this.eventStore.store(
new ProjectUserUpdateRoleEvent({
project: projectId,
createdBy,
preData: {
userId,
roleId: currentRole.id,
roleName: currentRole.name,
},
data: { userId, roleId, roleName: role.name },
}),
);
}
async getMembers(projectId: string): Promise<number> {
return this.store.getMembers(projectId);
}
@ -366,5 +423,3 @@ export default class ProjectService {
};
}
}
module.exports = ProjectService;

View File

@ -41,6 +41,7 @@ export const PROJECT_DELETED = 'project-deleted';
export const PROJECT_IMPORT = 'project-import';
export const PROJECT_USER_ADDED = 'project-user-added';
export const PROJECT_USER_REMOVED = 'project-user-removed';
export const PROJECT_USER_ROLE_CHANGED = 'project-user-role-changed';
export const DROP_PROJECTS = 'drop-projects';
export const TAG_CREATED = 'tag-created';
export const TAG_DELETED = 'tag-deleted';
@ -412,3 +413,24 @@ export class ProjectUserRemovedEvent extends BaseEvent {
this.preData = preData;
}
}
export class ProjectUserUpdateRoleEvent extends BaseEvent {
readonly project: string;
readonly data: any;
readonly preData: any;
constructor(p: {
project: string;
createdBy: string;
data: any;
preData: any;
}) {
super(PROJECT_USER_REMOVED, p.createdBy);
const { project, data, preData } = p;
this.project = project;
this.data = data;
this.preData = preData;
}
}

View File

@ -19,6 +19,7 @@ export interface IRoleWithPermissions extends IRole {
}
export interface IRoleDescriptor {
id: number;
name: string;
description?: string;
type: string;
@ -51,6 +52,11 @@ export interface IAccessStore extends Store<IRole, number> {
roleId: number,
projectId?: string,
): Promise<void>;
updateUserProjectRole(
userId: number,
roleId: number,
projectId: string,
): Promise<void>;
removeRolesOfTypeForUser(userId: number, roleType: string): Promise<void>;
addPermissionsToRole(
role_id: number,

View File

@ -652,3 +652,64 @@ test('should change a users role in the project', async () => {
expect(customUser[0].id).toBe(projectUser.id);
expect(customUser[0].name).toBe(projectUser.name);
});
test('should update role for user on project', async () => {
const project = {
id: 'update-users',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'update99@getunleash.io',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
const ownerRole = await stores.roleStore.getRoleByName(RoleName.OWNER);
await projectService.addUser(project.id, memberRole.id, projectMember1.id);
await projectService.changeRole(
project.id,
ownerRole.id,
projectMember1.id,
'test',
);
const { users } = await projectService.getUsersWithAccess(project.id, user);
const memberUsers = users.filter((u) => u.roleId === memberRole.id);
const ownerUsers = users.filter((u) => u.roleId === ownerRole.id);
expect(memberUsers).toHaveLength(0);
expect(ownerUsers).toHaveLength(2);
});
test('should not update role for user on project when she is the owner', async () => {
const project = {
id: 'update-users-not-allowed',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'update991@getunleash.io',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
await projectService.addUser(project.id, memberRole.id, projectMember1.id);
await expect(async () => {
await projectService.changeRole(
project.id,
memberRole.id,
user.id,
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
});

View File

@ -9,6 +9,14 @@ import {
import { IAvailablePermissions, IPermission } from 'lib/types/model';
class AccessStoreMock implements IAccessStore {
updateUserProjectRole(
userId: number,
roleId: number,
projectId: string,
): Promise<void> {
throw new Error('Method not implemented.');
}
removeUserFromRole(
userId: number,
roleId: number,