Skip to content

Commit

Permalink
feat: allow editing root role/description on SCIM group (#7874)
Browse files Browse the repository at this point in the history
  • Loading branch information
sighphyre authored Aug 14, 2024
1 parent 5e82e47 commit f276728
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const CreateGroup = () => {
handleSubmit={handleSubmit}
handleCancel={handleCancel}
mode={CREATE}
isScimGroup={false}
>
<Button
type='submit'
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/component/admin/groups/EditGroup/EditGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,15 @@ export const EditGroup = ({
handleSubmit={handleSubmit}
handleCancel={handleCancel}
mode={EDIT}
isScimGroup={isScimGroup}
>
<Tooltip title={isScimGroup ? scimGroupTooltip : ''} arrow>
<div>
<Button
type='submit'
variant='contained'
color='primary'
disabled={isScimGroup || !isValid}
disabled={!isValid}
data-testid={UG_SAVE_BTN_ID}
>
Save
Expand Down
1 change: 0 additions & 1 deletion frontend/src/component/admin/groups/Group/Group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@ export const Group: VFC = () => {
? scimGroupTooltip
: 'Edit group',
}}
disabled={isScimGroup}
>
<Edit />
</PermissionIconButton>
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/component/admin/groups/GroupForm/GroupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ interface IGroupForm {
mappingsSSO: string[];
users: IGroupUser[];
rootRole: number | null;
isScimGroup: boolean;
setName: (name: string) => void;
setDescription: React.Dispatch<React.SetStateAction<string>>;
setMappingsSSO: React.Dispatch<React.SetStateAction<string[]>>;
Expand All @@ -99,6 +100,7 @@ export const GroupForm: FC<IGroupForm> = ({
mappingsSSO,
users,
rootRole,
isScimGroup,
setName,
setDescription,
setMappingsSSO,
Expand Down Expand Up @@ -138,6 +140,7 @@ export const GroupForm: FC<IGroupForm> = ({
onChange={(e) => setName(e.target.value)}
data-testid={UG_NAME_ID}
required
disabled={isScimGroup}
/>
<StyledInputDescription>
How would you describe your group?
Expand All @@ -152,7 +155,7 @@ export const GroupForm: FC<IGroupForm> = ({
data-testid={UG_DESC_ID}
/>
<ConditionallyRender
condition={isGroupSyncingEnabled}
condition={isGroupSyncingEnabled && !isScimGroup}
show={
<>
<StyledInputDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export const GroupCardActions: FC<IGroupCardActions> = ({
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
type='button'
disabled={isScimGroup}
>
<MoreVert />
</IconButton>
Expand Down Expand Up @@ -103,6 +102,7 @@ export const GroupCardActions: FC<IGroupCardActions> = ({
</ListItemText>
</MenuItem>
<MenuItem
disabled={isScimGroup}
onClick={() => {
onEditUsers();
handleClose();
Expand All @@ -122,6 +122,7 @@ export const GroupCardActions: FC<IGroupCardActions> = ({
onRemove();
handleClose();
}}
disabled={isScimGroup}
>
<ListItemIcon>
<Delete />
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/component/admin/groups/group-constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const scimGroupTooltip =
'This group is managed by your SCIM provider and cannot be changed manually';
'This group is managed by your SCIM provider, only the role and description can be changed manually';
26 changes: 26 additions & 0 deletions src/lib/services/group-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import type { IUser } from '../types/user';
import type EventService from '../features/events/event-service';
import { SSO_SYNC_USER } from '../db/group-store';

const setsAreEqual = (firstSet, secondSet) =>
firstSet.size === secondSet.size &&
[...firstSet].every((x) => secondSet.has(x));

export class GroupService {
private groupStore: IGroupStore;

Expand Down Expand Up @@ -231,6 +235,28 @@ export class GroupService {
throw new NameExistsError('Group name already exists');
}
}

if (existingGroup && Boolean(existingGroup.scimId)) {
if (existingGroup.name !== group.name) {
throw new BadDataError(
'Cannot update the name of a SCIM group',
);
}

const existingUsers = new Set(
(
await this.groupStore.getAllUsersByGroups([
existingGroup.id,
])
).map((g) => g.userId),
);

const newUsers = new Set(group.users?.map((g) => g.user.id) || []);

if (!setsAreEqual(existingUsers, newUsers)) {
throw new BadDataError('Cannot update users of a SCIM group');
}
}
}

async getRolesForProject(projectId: string): Promise<IGroupRole[]> {
Expand Down
98 changes: 98 additions & 0 deletions src/test/e2e/services/group-service.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,101 @@ test('adding a nonexistent role to a group should fail', async () => {
'Request validation failed: your request body or params contain invalid data: Incorrect role id 100',
);
});

test('trying to modify a SCIM group name should fail', async () => {
const group = await groupStore.create({
name: 'scim_group',
description: 'scim_group',
});

await db.rawDatabase('groups').where({ id: group.id }).update({
scim_id: 'some-faux-uuid',
scim_external_id: 'external-id',
});

await expect(() => {
return groupService.updateGroup(
{
id: group.id,
name: 'new_name',
users: [],
rootRole: 100,
createdAt: new Date(),
createdBy: 'test',
},
TEST_AUDIT_USER,
);
}).rejects.toThrow(
'Request validation failed: your request body or params contain invalid data: Cannot update the name of a SCIM group',
);
});

test('trying to modify users in a SCIM group should fail', async () => {
const group = await groupStore.create({
name: 'another_scim_group',
description: 'scim_group',
});

await db.rawDatabase('groups').where({ id: group.id }).update({
scim_id: 'some-other-faux-uuid',
scim_external_id: 'some-external-id',
});

await expect(() => {
return groupService.updateGroup(
{
id: group.id,
name: 'new_name',
users: [
{
user: {
id: 1,
isAPI: false,
permissions: [],
},
},
],
rootRole: 100,
createdAt: new Date(),
createdBy: 'test',
},
TEST_AUDIT_USER,
);
}).rejects.toThrow(
'Request validation failed: your request body or params contain invalid data: Cannot update the name of a SCIM group',
);
});

test('can modify a SCIM group description and root role', async () => {
const group = await groupStore.create({
name: 'yet_another_scim_group',
description: 'scim_group',
});

await db.rawDatabase('groups').where({ id: group.id }).update({
scim_id: 'completely-different-faux-uuid',
scim_external_id: 'totally-not-the-same-external-id',
});

await groupService.updateGroup(
{
id: group.id,
name: 'yet_another_scim_group',
description: 'new description',
users: [],
rootRole: 1,
createdAt: new Date(),
createdBy: 'test',
},
TEST_AUDIT_USER,
);

const updatedGroup = await groupService.getGroup(group.id);
expect(updatedGroup).toMatchObject({
description: 'new description',
id: group.id,
mappingsSSO: [],
name: 'yet_another_scim_group',
rootRole: 1,
});
});

0 comments on commit f276728

Please sign in to comment.