Skip to content

fix: surface duplicate-username and duplicate-email conflicts on POST /Users#110

Merged
nicolamacoir merged 4 commits into
Metatavu:developfrom
nicolamacoir:fix/duplicate-user-create-errors
Jun 1, 2026
Merged

fix: surface duplicate-username and duplicate-email conflicts on POST /Users#110
nicolamacoir merged 4 commits into
Metatavu:developfrom
nicolamacoir:fix/duplicate-user-create-errors

Conversation

@nicolamacoir
Copy link
Copy Markdown
Contributor

Summary

  • Fixes POST /Users returns generic 'unknown_error' on duplicate email; conflict messages don't identify the offending field #109POST /Users returned {\"error\":\"unknown_error\"} for duplicate-email collisions (no SCIM-layer pre-check; Keycloak's generic error handler surfaced the model exception). The realm-scope's existing duplicate-username message also didn't identify which field collided. The organization-scope had no pre-check at all.
  • Adds a getUserByUsername + getUserByEmail pre-check on both scopes. The 409 body now identifies the duplicated field and value: User already exists with username: <X> / User already exists with email: <X>.
  • The email pre-check is gated on RealmModel.isDuplicateEmailsAllowed() so realms configured to allow duplicate emails still create successfully.

Changes

  • RealmScimServer.createUser — distinct message on the existing username conflict, plus a new email pre-check gated on the realm flag.
  • OrganizationScimServer.createUser — adds both pre-checks symmetric to the realm scope (previously had neither).
  • RealmUserCreateTestsITtestCreateDuplicateUserReturnsConflict strengthened to assert the message names username and the offending value; new testCreateDuplicateEmailReturnsConflict.
  • OrganizationUserCreateTestsIT — same upgrade and new email test on the org scope.

Test plan

  • ./gradlew test --tests \"fi.metatavu.keycloak.scim.server.test.tests.functional.RealmUserCreateTestsIT\" passes
  • ./gradlew test --tests \"fi.metatavu.keycloak.scim.server.test.tests.functional.OrganizationUserCreateTestsIT\" passes
  • Manually verify a duplicate-email POST on a realm with Allow Duplicate Emails disabled returns 409 with the new message instead of unknown_error
  • Manually verify the same POST on a realm with Allow Duplicate Emails enabled still creates the user successfully

Notes / follow-ups

  • Keycloak's user-profile email validator can still enforce uniqueness independently of the realm-level Allow Duplicate Emails flag. In that mode the pre-check is bypassed but setEmail will throw ModelDuplicateException, which still surfaces as unknown_error. Catching that exception in the controller layer is intentionally out of scope here — happy to follow up if you'd like it bundled.
  • Once Handle path-less PatchOp on Groups + return valid JSON errors #101 lands and ScimErrors becomes the canonical SCIM error builder, these two Response.status(...).entity(...).build() calls should migrate to ScimErrors.conflict(...). Trivial follow-up.

… /Users

Two related problems on POST /Users when the request collides with an
existing user:

- The realm-scope createUser pre-checked only username, returning a
  generic "User already exists" body that did not identify the offending
  field. A duplicate email was not pre-checked, so the create reached
  UserModel.setEmail and surfaced as Keycloak's generic
  {"error":"unknown_error"} response.
- The organization-scope createUser had no pre-check at all — both
  collisions surfaced as unknown_error.

Add a getUserByUsername + getUserByEmail pre-check on both scopes and
return a 409 with a body that identifies the duplicated field and value:

  User already exists with username: <X>
  User already exists with email: <X>

The email pre-check is gated on RealmModel.isDuplicateEmailsAllowed so
realms configured to permit duplicate emails still create successfully.

Adds regression tests for both the duplicate-username and the
duplicate-email cases on both scopes; existing duplicate-username tests
are strengthened to assert the message identifies the field.

Out of scope: Keycloak's user-profile email validator can still reject
duplicates even when the realm flag is on. That path would need a
ModelDuplicateException catch in the controller layer and is left for a
follow-up.

Closes Metatavu#109
…nScimServer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The duplicate-username and duplicate-email conflict tests added in
02a11e9 used assertTrue without importing it, breaking compileTestJava.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

@nicolamacoir nicolamacoir merged commit 884e15e into Metatavu:develop Jun 1, 2026
3 checks passed
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.

POST /Users returns generic 'unknown_error' on duplicate email; conflict messages don't identify the offending field

1 participant