1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

fix: setRolesForUser and setRolesForGroup role check (#6380)

In order to stop privilege escalation via
`/api/admin/projects/:project/users/:userId/roles` and
`/api/admin/projects/:project/groups/:groupId/roles` this PR adds the
same check we added to setAccess methods to the methods updating access
for these two methods.

Also adds tests that verify that we throw an exception if you try to
assign roles you do not have.

Thank you @nunogois for spotting this during testing.
This commit is contained in:
Christopher Kolstad 2024-02-29 09:38:32 +01:00 committed by GitHub
parent 80d89ab260
commit 0887999dd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 133 additions and 40 deletions

View File

@ -728,8 +728,8 @@ export default class ProjectService {
) { ) {
return true; return true;
} }
return rolesBeingAdded.every((role) => return rolesBeingAdded.every((roleId) =>
userRoles.some((userRole) => userRole.id === role), userRoles.some((userRole) => userRole.id === roleId),
); );
} }
async addAccess( async addAccess(
@ -785,7 +785,6 @@ export default class ProjectService {
projectId, projectId,
userId, userId,
); );
const ownerRole = await this.accessService.getRoleByName( const ownerRole = await this.accessService.getRoleByName(
RoleName.OWNER, RoleName.OWNER,
); );
@ -795,7 +794,12 @@ export default class ProjectService {
if (hasOwnerRole && isRemovingOwnerRole) { if (hasOwnerRole && isRemovingOwnerRole) {
await this.validateAtLeastOneOwner(projectId, ownerRole); await this.validateAtLeastOneOwner(projectId, ownerRole);
} }
const isAllowedToAssignRoles = await this.isAllowedToAddAccess(
createdByUserId,
projectId,
newRoles,
);
if (isAllowedToAssignRoles) {
await this.accessService.setProjectRolesForUser( await this.accessService.setProjectRolesForUser(
projectId, projectId,
userId, userId,
@ -816,6 +820,11 @@ export default class ProjectService {
}, },
}), }),
); );
} else {
throw new InvalidOperationError(
'User tried to assign a role they did not have access to',
);
}
} }
async setRolesForGroup( async setRolesForGroup(
@ -838,7 +847,12 @@ export default class ProjectService {
if (hasOwnerRole && isRemovingOwnerRole) { if (hasOwnerRole && isRemovingOwnerRole) {
await this.validateAtLeastOneOwner(projectId, ownerRole); await this.validateAtLeastOneOwner(projectId, ownerRole);
} }
const isAllowedToAssignRoles = await this.isAllowedToAddAccess(
createdByUserId,
projectId,
newRoles,
);
if (isAllowedToAssignRoles) {
await this.accessService.setProjectRolesForGroup( await this.accessService.setProjectRolesForGroup(
projectId, projectId,
groupId, groupId,
@ -860,6 +874,11 @@ export default class ProjectService {
}, },
}), }),
); );
} else {
throw new InvalidOperationError(
'User tried to assign a role they did not have access to',
);
}
} }
async findProjectGroupRole( async findProjectGroupRole(

View File

@ -25,6 +25,7 @@ import {
SYSTEM_USER_ID, SYSTEM_USER_ID,
} from '../../../lib/types'; } from '../../../lib/types';
import { User } from '../../../lib/server-impl'; import { User } from '../../../lib/server-impl';
import { InvalidOperationError } from '../../../lib/error';
let stores: IUnleashStores; let stores: IUnleashStores;
let db: ITestDb; let db: ITestDb;
@ -619,7 +620,80 @@ describe('Managing Project access', () => {
projectUser.username, projectUser.username,
projectUser.id, projectUser.id,
), ),
).resolves.not.toThrow(); ).resolves.not.toThrow(
new InvalidOperationError(
'User tried to assign a role they did not have access to',
),
);
});
test('Users can not assign roles they do not have to a user through explicit roles endpoint', async () => {
const project = {
id: 'user_fail_assign_to_user',
name: 'user_fail_assign_to_user',
description: '',
mode: 'open' as const,
defaultStickiness: 'clientId',
};
await projectService.createProject(project, user);
const projectUser = await stores.userStore.insert({
name: 'Some project user',
email: 'fail_assign_role_to_user@example.com',
});
const secondUser = await stores.userStore.insert({
name: 'Some other user',
email: 'otheruser_no_roles@example.com',
});
const customRole = await stores.roleStore.create({
name: 'role_that_noone_has',
roleType: 'custom',
description:
'Used to prove that you can not assign a role you do not have via setRolesForUser',
});
expect(
projectService.setRolesForUser(
project.id,
secondUser.id,
[customRole.id],
projectUser.username,
projectUser.id,
),
).rejects.toThrow();
});
test('Users can not assign roles they do not have to a group through explicit roles endpoint', async () => {
const project = {
id: 'user_fail_assign_to_group',
name: 'user_fail_assign_to_group',
description: '',
mode: 'open' as const,
defaultStickiness: 'clientId',
};
await projectService.createProject(project, user);
const projectUser = await stores.userStore.insert({
name: 'Some project user',
email: 'fail_assign_role_to_group@example.com',
});
const group = await stores.groupStore.create({
name: 'Some group_awaiting_role',
});
const customRole = await stores.roleStore.create({
name: 'role_that_noone_has_fail_assign_group',
roleType: 'custom',
description:
'Used to prove that you can not assign a role you do not have via setRolesForGroup',
});
expect(
projectService.setRolesForGroup(
project.id,
group.id,
[customRole.id],
projectUser.username,
projectUser.id,
),
).rejects.toThrow(
new InvalidOperationError(
'User tried to assign a role they did not have access to',
),
);
}); });
}); });