Enforce SCIM attribute mutability on PATCH operations - #51580
Conversation
There was a problem hiding this comment.
Pull request overview
Enforces SCIM immutable attribute handling during PATCH requests.
Changes:
- Rejects PATCH mutations with HTTP 400 and
scimType=mutability. - Adds mutability exception handling.
- Adds Group and User integration tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
scim/core/.../AbstractModelSchema.java |
Enforces immutable attributes during PATCH. |
scim/core/.../ScimMutabilityException.java |
Defines the mutability exception. |
scim/services/.../Error.java |
Maps mutability errors to SCIM responses. |
scim/tests/.../GroupTest.java |
Tests immutable Group attributes. |
scim/tests/.../UserTest.java |
Tests immutable User metadata. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
1735885 to
4eb5095
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
scim/core/src/main/java/org/keycloak/scim/resource/schema/AbstractModelSchema.java:363
- The error message uses
attribute.getName(), which may be ambiguous for nested attributes (e.g., clients patchingmeta.createdmay only seecreated). Consider including the full SCIM path being patched (or otherwise a fully-qualified attribute path) in the exception message to make client-side debugging and TCK diagnostics more actionable.
throw new ScimMutabilityException(
"Attribute '" + attribute.getName() + "' is immutable");
scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/GroupTest.java:176
- These repeated try/catch blocks assert the same response shape multiple times. To reduce duplication and make future mutability tests easier to extend, consider extracting a small helper (e.g.,
assertMutabilityError(Runnable patchCall, @Nullable String expectedDetailContains)) used by replace/add/remove cases.
// PATCH replace on immutable externalId should fail
try {
client.groups().patch(group.getId(), PatchRequest.create()
.replace("externalId", "new-value")
.build());
fail("should fail because externalId is immutable");
} catch (ScimClientException sce) {
ErrorResponse error = sce.getError();
assertNotNull(error);
assertEquals(400, error.getStatusInt());
assertEquals("mutability", error.getScimType());
assertTrue(error.getDetail().contains("externalId"));
}
// PATCH add on immutable externalId should fail
try {
client.groups().patch(group.getId(), PatchRequest.create()
.add("externalId", "new-value")
.build());
fail("should fail because externalId is immutable");
} catch (ScimClientException sce) {
ErrorResponse error = sce.getError();
assertNotNull(error);
assertEquals(400, error.getStatusInt());
assertEquals("mutability", error.getScimType());
}
// PATCH remove on immutable externalId should fail
try {
client.groups().patch(group.getId(), PatchRequest.create()
.remove("externalId")
.build());
fail("should fail because externalId is immutable");
} catch (ScimClientException sce) {
ErrorResponse error = sce.getError();
assertNotNull(error);
assertEquals(400, error.getStatusInt());
assertEquals("mutability", error.getScimType());
}
4eb5095 to
b5219c4
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
scim/core/src/main/java/org/keycloak/scim/resource/schema/AbstractModelSchema.java:360
- The presence check cannot detect either immutable
meta.createdvalue:GroupCoreModelSchema.getAttributeValue()has nocreatedTimestampcase, and the user schema delegates it to profile attributes, which do not exposeUserModel.getCreatedTimestamp(). As a result, PATCH remove/add/replace seesnull, returns/no-ops instead of throwing, and the newtestPatchImmutableMetaCreatedwill fail; make both schema getters return the model creation timestamp and cover the group path too.
String modelAttrName = attribute.getModelAttributeName();
if (modelAttrName == null || getAttributeValue(model, modelAttrName) != null) {
b5219c4 to
0dc8129
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (1)
scim/core/src/main/java/org/keycloak/scim/resource/schema/AbstractModelSchema.java:359
GroupCoreModelSchema.getAttributeValue()returnsnullfor itscreatedTimestampmapping, so PATCHing the already-set Groupmeta.createdbypasses this check instead of returningscimType=mutability. Add acreatedTimestampcase backed byGroupModel.getCreatedTimestamp()and cover Groupmeta.created, which is one of the attributes reported in #51155.
String modelAttrName = attribute.getModelAttributeName();
if (modelAttrName == null || getAttributeValue(model, modelAttrName) != null) {
Closes keycloak#51155 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
0dc8129 to
e2db7e6
Compare
Closes #51155