Skip to content

SCIM error responses: RFC 7644 JSON + idempotent CREATE on conflict#114

Open
zivisaiah wants to merge 2 commits into
Metatavu:developfrom
zivisaiah:fix/scim-json-errors-and-idempotent-create
Open

SCIM error responses: RFC 7644 JSON + idempotent CREATE on conflict#114
zivisaiah wants to merge 2 commits into
Metatavu:developfrom
zivisaiah:fix/scim-json-errors-and-idempotent-create

Conversation

@zivisaiah
Copy link
Copy Markdown

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

  • Okta SCIM 2.0 Test App (OAuth Bearer Token)
  • Keycloak 26.6.0 with Organizations
  • 7 SCIM-provisioned users across multiple group assignments
  • Remove group, re-add group: all users provision cleanly, zero error badges

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>
@zivisaiah zivisaiah force-pushed the fix/scim-json-errors-and-idempotent-create branch 2 times, most recently from 977a9e2 to 0c0f7eb Compare May 25, 2026 15:19
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.
@zivisaiah zivisaiah force-pushed the fix/scim-json-errors-and-idempotent-create branch from 0c0f7eb to f90eacc Compare May 25, 2026 15:28
@zivisaiah
Copy link
Copy Markdown
Author

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 .entity("string") error responses with RFC 7644 JSON, and making POST /Users idempotent when the user already exists. Neither change introduces new duplication.

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.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
10.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@zivisaiah zivisaiah marked this pull request as ready for review May 25, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants