SCIM error responses: RFC 7644 JSON + idempotent CREATE on conflict#114
SCIM error responses: RFC 7644 JSON + idempotent CREATE on conflict#114zivisaiah wants to merge 2 commits into
Conversation
Two related fixes to GroupsController:
1. updateGroup (PUT /Groups/{id}) previously only set displayName and
silently dropped the `members` array. Okta's Group Push uses PUT
(not PATCH) with the desired final member list, so this no-op meant
membership changes from Okta never propagated to Keycloak. The fix
reconciles the request's members against the current members:
removes users no longer in the desired set (with leave events) and
adds new ones (with join events).
2. After mutating membership via user.leaveGroup() / user.joinGroup(),
Keycloak's Infinispan user cache holds a stale view of the user's
group list. Subsequent reads of /users/{id}/groups return outdated
entries until the cache expires. Explicitly evict the affected
user from the UserCache to ensure immediate consistency.
Verified against Okta SCIM provisioning + Keycloak 26.6:
- PUT /Groups/{id} with added/removed members updates both the group's
members list and each user's groups list.
- No manual cache clear required after membership changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
977a9e2 to
0c0f7eb
Compare
1. Add ScimErrorResponse utility for RFC 7644 §3.12 compliant JSON
error bodies. Replace all plain-text .entity("string") error
responses so SCIM clients can parse error details.
2. Idempotent POST /Users: when the username already exists, update
the existing user and return 200 OK instead of 409 Conflict.
Okta treats 409 as a permanent provisioning failure.
Note: SonarCloud duplication flag is a false positive — the flagged
blocks are the pre-existing structural duplication between
RealmScimServer and OrganizationScimServer (identical method bodies
with different context types). This PR did not introduce that
duplication; it was inherited from the base branch.
0c0f7eb to
f90eacc
Compare
|
Note on SonarCloud duplication flag: the flagged blocks are the pre-existing structural duplication between RealmScimServer and OrganizationScimServer — both classes have near-identical method bodies for updateUser, patchUser, deleteUser, and findUser (differing only in the context type and controller reference). This duplication existed before this PR and was carried forward by #112. This PR's actual changes are minimal (~20 lines changed per file): replacing plain-text The structural duplication between the two server classes would require a template method pattern or class hierarchy refactor to resolve — a separate effort from this error-format fix. Once #112 is merged and this PR is rebased against develop, the "new code" diff will shrink to just our changes and the duplication percentage should drop well below the threshold. |
|


Depends on #112 (this branch is based on its HEAD).
Summary
Two fixes for SCIM client compatibility, tested end-to-end against Okta SCIM 2.0 Test App connector + Keycloak 26.6.0 with Organizations.
1. RFC 7644 compliant JSON error responses
All error bodies were plain-text strings (e.g., "User already exists"). SCIM clients (Okta, Azure AD) expect JSON per RFC 7644 section 3.12. Okta fails to parse the plain text and shows errors like "Unrecognized token 'User'" in the provisioning logs.
Add ScimErrorResponse utility class. Replace all ~30 plain-text .entity("string") error responses across RealmScimServer, OrganizationScimServer, and ScimResources with proper SCIM JSON error bodies.
2. Idempotent POST /Users (upsert on existing username)
When POST /Users hits a username that already exists, RealmScimServer now updates the existing user's attributes and returns 200 OK instead of 409 Conflict.
Okta re-pushes all users on group re-assignment. A 409 is treated as a permanent provisioning failure, creating persistent error badges in Okta's Assignments tab even though users are correctly provisioned. Returning 200 with the existing user makes POST idempotent and clears the error state.
Testing