Skip to content

SCIM 2.0 Users provider (spec 015)#33

Merged
hendrikebbers merged 9 commits into
mainfrom
feat/015-scim-users-provider
Jul 15, 2026
Merged

SCIM 2.0 Users provider (spec 015)#33
hendrikebbers merged 9 commits into
mainfrom
feat/015-scim-users-provider

Conversation

@herbie-bot

Copy link
Copy Markdown
Collaborator

Summary

Adds the first slice of a SCIM 2.0 service provider as a new optional module spring-services-scim: a bearer-token-authenticated, fully isolated /scim/v2/** surface that lets an IdP (Authentik / any RFC 7644 client) push-provision users onto spec 012's UserEntity. Inert unless openelements.scim.token is set. Groups, PATCH, and Group→Role mapping are deferred to follow-ups.

Spec

  • Spec folder: docs/specs/015-scim-users-provider/
  • Design: docs/specs/015-scim-users-provider/design.md
  • Behaviors: docs/specs/015-scim-users-provider/behaviors.md

Changes

  • Core: additive deleted / deleted_at columns on UserEntity (soft delete, distinct from deactivation); SystemUserInitializer INSERT updated for the new column.
  • spring-services-scim (registered in reactor, BOM, -all; @ConditionalOnProperty("openelements.scim.token")):
    • Isolated /scim/v2/** security chain ordered ahead of the JWT chain; constant-time static-token filter; SCIM-shaped 401 entry point; reserved, inactive SCIM service principal as the audit actor.
    • Users: POST (201 / 409 uniqueness / 400 invalidValue), GET/{id} (404 on missing/deleted), GET list (userName eq/externalId eq, pagination, excludes soft-deleted + reserved principals), PUT full-replace (undelete on active:true; never writes sub), soft DELETE.
    • Discovery (ServiceProviderConfig/ResourceTypes/Schemas), Groups stub (empty list + 501 on writes), SCIM Error envelope via a scoped @RestControllerAdvice, all application/scim+json.
    • SCIM writes audited explicitly against the SCIM principal with the correct action.
  • Docs: docs/releases/upgrade-to-1.4.md (enable SCIM: dependency, migration SQL, token, caveats) + README module entry.

Test coverage

  • 20 tests: MockMvc + Testcontainers integration across auth (incl. no-leak to the JWT chain), discovery, create/409/400, read/404/deleted, list/filter/pagination/exclusions, PUT replace/deactivate/undelete/404, soft delete + deactivation distinction, audit attribution, Groups stub; plus an inert-without-token guard test.
  • Full -Pfull-build clean verify -Dmaven.javadoc.failOnWarnings=true green across all 10 modules (0 javadoc warnings).
  • spec-review + quality-review clean (no Critical/Improvement findings).

Notes

  • The spec's Authentik-harness verification (Capture Authentik SCIM Provider wire-protocol traffic (research spike for SCIM Provider spec) #21) — 409→recovery and group-stub tolerance against real Authentik — can't run in CI (no Authentik instance); implemented to the spec's expected behavior. Per the design, a deviation opens a follow-up spec rather than amending this one.
  • Optional feature, no change for consumers who don't set openelements.scim.token. Soft DELETE retains PII (FK integrity); GDPR erasure is a deferred module.
  • Release-doc versioning: I added upgrade-to-1.4.md for the 1.4.0 line; consolidate with the other 1.4 upgrade content as you see fit.

Closes #21

🤖 Generated with Claude Code

hendrikebbers and others added 9 commits July 15, 2026 19:18
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Step 1 of spec 015. Adds the additive nullable/default `deleted` and `deleted_at`
columns (+ accessors) to core's UserEntity so a SCIM DELETE can soft-delete a row
while keeping it referentially intact, distinct from a merely deactivated user.

Spec: docs/specs/015-scim-users-provider (#21)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Steps 2-7 of spec 015. New optional feature module (registered in the reactor, BOM
and -all), inert unless openelements.scim.token is set (@ConditionalOnProperty):

- Isolated /scim/v2/** security filter chain ordered ahead of the JWT chain, with a
  constant-time static-token filter (ScimTokenAuthenticationFilter) and a SCIM-shaped
  401 entry point.
- Reserved, permanently-inactive SCIM service principal (bootstrapped like the System
  user) used as the audit actor for every SCIM write.
- Users resource: POST (201/409 uniqueness/400), GET/{id} (404 on missing/deleted),
  GET list (userName eq / externalId eq, pagination, excludes soft-deleted + reserved),
  PUT full-replace (undelete when deleted && active:true; never writes sub), DELETE
  (soft: active=false/deleted=true/deleted_at=now), all in application/scim+json.
- Discovery (ServiceProviderConfig/ResourceTypes/Schemas), graceful Groups stub
  (empty list + 501 on writes), SCIM Error envelope via a scoped @RestControllerAdvice.
- SCIM writes audited explicitly against the SCIM principal with the correct action.

Maps onto spec 012's UserEntity; SCIM never reads/writes sub, so JIT-login correlation
by externalId still adopts the row on first interactive login.

Spec: docs/specs/015-scim-users-provider (#21)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rovider

Step 8 of spec 015. MockMvc + Testcontainers integration test covering the SCIM
behaviors — token auth (valid/missing/wrong + no leak to the JWT chain), discovery,
create/409/400, get/404/deleted, list/filter/pagination/exclusions, PUT
replace/deactivate/undelete/404, soft DELETE + deactivation distinction, audit
attribution to the SCIM principal, and the Groups stub — plus an
ApplicationContextRunner test proving the module is inert without a token. 20 tests.

Spec: docs/specs/015-scim-users-provider (#21)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntry

Step 9 of spec 015. docs/releases/upgrade-to-1.4.md documents enabling the optional
SCIM module: depend on spring-services-scim (or -all), apply the additive
deleted/deleted_at migration + reserved SCIM principal, and set openelements.scim.token,
with the soft-delete/GDPR and Groups-stub/501 caveats. README lists the new module.

Spec: docs/specs/015-scim-users-provider (#21)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new NOT NULL deleted column has no DB default under Hibernate create-drop DDL,
so SystemUserInitializer's raw INSERT (and the AuditLogIntegrationTest seed) must set
it explicitly; otherwise the System user row fails to insert and audit actor lookup
throws EntityNotFound. Sets deleted = FALSE.

Spec: docs/specs/015-scim-users-provider (#21)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hendrikebbers
hendrikebbers merged commit f2a1096 into main Jul 15, 2026
1 check 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.

Capture Authentik SCIM Provider wire-protocol traffic (research spike for SCIM Provider spec)

2 participants