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:
parent
d56611675f
commit
8364c3b396
@ -247,6 +247,19 @@ export class AccessStore implements IAccessStore {
|
|||||||
.delete();
|
.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(
|
async removeRolesOfTypeForUser(
|
||||||
userId: number,
|
userId: number,
|
||||||
roleType: string,
|
roleType: string,
|
||||||
|
@ -220,6 +220,14 @@ export class AccessService {
|
|||||||
return this.store.removeUserFromRole(userId, roleId, projectId);
|
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
|
//This actually only exists for testing purposes
|
||||||
async addPermissionToRole(
|
async addPermissionToRole(
|
||||||
roleId: number,
|
roleId: number,
|
||||||
|
@ -8,6 +8,7 @@ import NotFoundError from '../error/notfound-error';
|
|||||||
import {
|
import {
|
||||||
ProjectUserAddedEvent,
|
ProjectUserAddedEvent,
|
||||||
ProjectUserRemovedEvent,
|
ProjectUserRemovedEvent,
|
||||||
|
ProjectUserUpdateRoleEvent,
|
||||||
PROJECT_CREATED,
|
PROJECT_CREATED,
|
||||||
PROJECT_DELETED,
|
PROJECT_DELETED,
|
||||||
PROJECT_UPDATED,
|
PROJECT_UPDATED,
|
||||||
@ -309,23 +310,9 @@ export default class ProjectService {
|
|||||||
userId: number,
|
userId: number,
|
||||||
createdBy?: string,
|
createdBy?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const roles = await this.accessService.getRolesForProject(projectId);
|
const role = await this.findProjectRole(projectId, roleId);
|
||||||
const role = roles.find((r) => r.id === roleId);
|
|
||||||
if (!role) {
|
|
||||||
throw new NotFoundError(
|
|
||||||
`Couldn't find roleId=${roleId} on project=${projectId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role.name === RoleName.OWNER) {
|
await this.validateAtLeastOneOwner(projectId, role);
|
||||||
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.accessService.removeUserFromRole(userId, role.id, projectId);
|
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> {
|
async getMembers(projectId: string): Promise<number> {
|
||||||
return this.store.getMembers(projectId);
|
return this.store.getMembers(projectId);
|
||||||
}
|
}
|
||||||
@ -366,5 +423,3 @@ export default class ProjectService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ProjectService;
|
|
||||||
|
@ -41,6 +41,7 @@ export const PROJECT_DELETED = 'project-deleted';
|
|||||||
export const PROJECT_IMPORT = 'project-import';
|
export const PROJECT_IMPORT = 'project-import';
|
||||||
export const PROJECT_USER_ADDED = 'project-user-added';
|
export const PROJECT_USER_ADDED = 'project-user-added';
|
||||||
export const PROJECT_USER_REMOVED = 'project-user-removed';
|
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 DROP_PROJECTS = 'drop-projects';
|
||||||
export const TAG_CREATED = 'tag-created';
|
export const TAG_CREATED = 'tag-created';
|
||||||
export const TAG_DELETED = 'tag-deleted';
|
export const TAG_DELETED = 'tag-deleted';
|
||||||
@ -412,3 +413,24 @@ export class ProjectUserRemovedEvent extends BaseEvent {
|
|||||||
this.preData = preData;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ export interface IRoleWithPermissions extends IRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IRoleDescriptor {
|
export interface IRoleDescriptor {
|
||||||
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
type: string;
|
type: string;
|
||||||
@ -51,6 +52,11 @@ export interface IAccessStore extends Store<IRole, number> {
|
|||||||
roleId: number,
|
roleId: number,
|
||||||
projectId?: string,
|
projectId?: string,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
updateUserProjectRole(
|
||||||
|
userId: number,
|
||||||
|
roleId: number,
|
||||||
|
projectId: string,
|
||||||
|
): Promise<void>;
|
||||||
removeRolesOfTypeForUser(userId: number, roleType: string): Promise<void>;
|
removeRolesOfTypeForUser(userId: number, roleType: string): Promise<void>;
|
||||||
addPermissionsToRole(
|
addPermissionsToRole(
|
||||||
role_id: number,
|
role_id: number,
|
||||||
|
@ -652,3 +652,64 @@ test('should change a users role in the project', async () => {
|
|||||||
expect(customUser[0].id).toBe(projectUser.id);
|
expect(customUser[0].id).toBe(projectUser.id);
|
||||||
expect(customUser[0].name).toBe(projectUser.name);
|
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'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
8
src/test/fixtures/fake-access-store.ts
vendored
8
src/test/fixtures/fake-access-store.ts
vendored
@ -9,6 +9,14 @@ import {
|
|||||||
import { IAvailablePermissions, IPermission } from 'lib/types/model';
|
import { IAvailablePermissions, IPermission } from 'lib/types/model';
|
||||||
|
|
||||||
class AccessStoreMock implements IAccessStore {
|
class AccessStoreMock implements IAccessStore {
|
||||||
|
updateUserProjectRole(
|
||||||
|
userId: number,
|
||||||
|
roleId: number,
|
||||||
|
projectId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
removeUserFromRole(
|
removeUserFromRole(
|
||||||
userId: number,
|
userId: number,
|
||||||
roleId: number,
|
roleId: number,
|
||||||
|
Loading…
Reference in New Issue
Block a user