Skip to content

Commit

Permalink
Implement PUT + PATCH /Groups/{id}
Browse files Browse the repository at this point in the history
  • Loading branch information
fflorent committed Jan 13, 2025
1 parent d341dc3 commit 1511a19
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 14 deletions.
19 changes: 19 additions & 0 deletions app/server/lib/scim/v2/ScimGroupController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ class ScimGroupController extends BaseController {
return toSCIMMYGroup(group);
});
}

/**
* Overwrites a group with the passed data.
*
* @param resource The SCIMMY group resource performing the operation
* @param data The data to overwrite the group with
* @param context The request context
*/
public async overwriteGroup(resource: any, data: any, context: RequestContext) {
return this.runAndHandleErrors(context, async () => {
const id = this.getIdFromResource(resource);
const groupDescriptor = toGroupDescriptor(data);
const group = await this.dbManager.overwriteGroup(id, groupDescriptor);
return toSCIMMYGroup(group);
});
}
}

export const getScimGroupConfig = (
Expand All @@ -74,6 +90,9 @@ export const getScimGroupConfig = (
return await controller.getGroups(resource, context);
},
ingress: async (resource: any, data: any, context: RequestContext) => {
if (resource.id) {
return await controller.overwriteGroup(resource, data, context);
}
return await controller.createGroup(data, context);
},
};
Expand Down
4 changes: 2 additions & 2 deletions app/server/lib/scim/v2/ScimUserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ class ScimUserController extends BaseController {
}

/**
* Overrides a user with the passed data.
* Overwrite a user with the passed data.
*
* @param resource The SCIMMY user resource performing the operation
* @param data The data to override the user with
* @param data The data to overwrite the user with
* @param context The request context
*/
public async overwriteUser(resource: any, data: any, context: RequestContext) {
Expand Down
124 changes: 112 additions & 12 deletions test/server/lib/Scim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ describe('Scim', () => {
sandbox.stub(getDbManager(), 'getGroupsWithMembersByType').throws(error);
sandbox.stub(getDbManager(), 'getGroupsWithMembers').throws(error);
sandbox.stub(getDbManager(), 'createGroup').throws(error);
sandbox.stub(getDbManager(), 'overwriteGroup').throws(error);
sandbox.stub(getDbManager(), 'deleteGroup').throws(error);

const res = await makeCallWith('chimpy');
assert.deepEqual(res.data, {
Expand Down Expand Up @@ -485,7 +487,6 @@ describe('Scim', () => {
});
});


it('should deduce the name from the displayEmail when not provided', async function () {
const res = await axios.put(scimUrl(`/Users/${userToUpdateId}`), {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
Expand Down Expand Up @@ -643,6 +644,15 @@ describe('Scim', () => {
return await withGroupNames([groupName], (groupNames) => cb(groupNames[0]));
}

function getUserMember(user: keyof UserConfigByName) {
return { value: String(userIdByName[user]), display: capitalize(user), type: 'User' };
}

function getUserMemberWithRef(user: keyof UserConfigByName) {
return { ...getUserMember(user), $ref: `/api/scim/v2/Users/${userIdByName[user]}` };
}


describe('GET /Groups/{id}', function () {
it(`should return a "${Group.RESOURCE_USERS_TYPE}" group for chimpy`, async function () {
await withGroupName('test-get-group-by-id', async (groupName) => {
Expand Down Expand Up @@ -743,13 +753,13 @@ describe('Scim', () => {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
id: String(group1.id),
displayName: group1Name,
members: [{ value: '1', display: 'Chimpy', $ref: '/api/scim/v2/Users/1', type: 'User' }],
members: [getUserMemberWithRef('chimpy')],
meta: { resourceType: 'Group', location: `/api/scim/v2/Groups/${group1.id}` }
}, {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
id: String(group2.id),
displayName: group2Name,
members: [{ value: '2', display: 'Kiwi', $ref: '/api/scim/v2/Users/2', type: 'User' }],
members: [getUserMemberWithRef('kiwi')],
meta: { resourceType: 'Group', location: `/api/scim/v2/Groups/${group2.id}` }
}
]);
Expand All @@ -761,14 +771,14 @@ describe('Scim', () => {
});

describe('POST /Groups', function () {
it(`should create a new ${Group.RESOURCE_USERS_TYPE} group`, async function () {
it(`should create a new group of type "${Group.RESOURCE_USERS_TYPE}`, async function () {
await withGroupName('test-group', async (groupName) => {
const res = await axios.post(scimUrl('/Groups'), {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
displayName: groupName,
members: [
{ value: String(userIdByName['chimpy']), display: 'Chimpy', type: 'User' },
{ value: String(userIdByName['kiwi']), display: 'Kiwi', type: 'User' },
getUserMember('chimpy'),
getUserMember('kiwi'),
]
}, chimpy);
assert.equal(res.status, 201);
Expand All @@ -778,8 +788,8 @@ describe('Scim', () => {
id: String(newGroupId),
displayName: groupName,
members: [
{ value: '1', display: 'Chimpy', $ref: '/api/scim/v2/Users/1', type: 'User' },
{ value: '2', display: 'Kiwi', $ref: '/api/scim/v2/Users/2', type: 'User' },
getUserMemberWithRef('chimpy'),
getUserMemberWithRef('kiwi'),
],
meta: { resourceType: 'Group', location: `/api/scim/v2/Groups/${newGroupId}` }
});
Expand Down Expand Up @@ -858,8 +868,7 @@ describe('Scim', () => {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
displayName: 'test-group',
members: [
{ value: String(userIdByName['chimpy']), display: 'Chimpy',
$ref: `/api/scim/v2/Users/${String(userIdByName['chimpy'])}`, type: 'User' },
getUserMember('chimpy'),
{ value: '1000', display: 'Non-Existing User', type: 'User' },
]
}, chimpy);
Expand Down Expand Up @@ -893,13 +902,104 @@ describe('Scim', () => {
// We need to differ the moment we call userIdByName['chimpy'] (set during a "before()" hook)
get members() {
return [
{ value: String(userIdByName['chimpy']), display: 'Chimpy', type: 'User' },
{ value: String(userIdByName['kiwi']), display: 'Kiwi', type: 'User' },
getUserMember('chimpy'),
getUserMember('kiwi'),
];
}
});
});

describe('PUT /Groups/{id}', function () {
beforeEach(async function () {
await withGroupName('test-group', async (groupName) => {
await getDbManager().createGroup({
name: groupName,
type: Group.RESOURCE_USERS_TYPE,
memberUsers: [userIdByName['chimpy']!]
});
});
});

it('should update an existing group', async function () {
const newGroupName = 'Updated Group Name';
const res = await axios.put(scimUrl('/Groups/1'), {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
displayName: newGroupName,
members: [
getUserMember('kiwi'),
]
}, chimpy);
assert.equal(res.status, 200);
assert.deepEqual(res.data, {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
id: '1',
displayName: newGroupName,
members: [
getUserMemberWithRef('kiwi'),
],
meta: { resourceType: 'Group', location: '/api/scim/v2/Groups/1' }
});
});

it('should updating a group with members omitted', async function () {
const newGroupName = 'Updated Group Name';
const res = await axios.put(scimUrl('/Groups/1'), {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
displayName: newGroupName,
}, chimpy);
assert.equal(res.status, 200);
assert.deepEqual(res.data, {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
id: '1',
displayName: newGroupName,
members: [],
meta: { resourceType: 'Group', location: '/api/scim/v2/Groups/1' }
});
});

it('should return 404 when the group is not found', async function () {
const res = await axios.put(scimUrl('/Groups/1000'), {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
displayName: 'New Group Name',
members: [
getUserMember('kiwi'),
]
}, chimpy);
assert.deepEqual(res.data, {
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
status: '404',
detail: 'Group with id 1000 not found'
});
assert.equal(res.status, 404);
});

it('should return 400 when the group id is malformed', async function () {
const res = await axios.put(scimUrl('/Groups/not-an-id'), {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
displayName: 'New Group Name',
members: [
getUserMember('kiwi'),
]
}, chimpy);
assert.deepEqual(res.data, {
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
status: '400',
detail: 'Invalid passed group ID',
scimType: 'invalidValue'
});
assert.equal(res.status, 400);
});

checkCommonErrors('put', '/Groups/1', {
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
displayName: 'New Group Name',
// We need to differ the moment we call userIdByName['kiwi'] (set during a "before()" hook)
get members() {
return [ getUserMember('kiwi') ];
}
});
});

});

describe('POST /Bulk', function () {
Expand Down

0 comments on commit 1511a19

Please sign in to comment.