Skip to content

feat(sso): OIDC/SAML federation + SCIM 2.0 provisioning (Step 6.8) - #159

Merged
officialCodeWork merged 1 commit into
mainfrom
build/phase-6/step-6.8-sso-scim
Jun 8, 2026
Merged

feat(sso): OIDC/SAML federation + SCIM 2.0 provisioning (Step 6.8)#159
officialCodeWork merged 1 commit into
mainfrom
build/phase-6/step-6.8-sso-scim

Conversation

@officialCodeWork

Copy link
Copy Markdown
Owner

Summary

Step 6.8 — SSO / SCIM: OIDC + SAML IdP federation, SCIM 2.0 user provisioning, and per-tenant IdP config, in one PR. New rag-sso package.

  • FederationFederatedAuth is an Auth SPI backend over per-tenant OidcProvider / SamlProvider. The gateway middleware already calls auth.authenticate(token, tenant_id) → Principal at the boundary, so wiring it is the whole integration — no middleware change. IdP group claims → acl_labels, so Step 6.3 ACL push-down + 6.5 PII egress govern federated users unchanged (authorize stays a coarse allow — federation establishes who, the PolicyEngine decides what).
  • Dependency-free defaults, heavy crypto behind extras — stdlib HS256 JWT verifier (full exp/nbf/iss/aud + constant-time compare) and defusedxml SAML (Issuer / Conditions / Audience); asymmetric OIDC (PyJWT, [oidc]) + SAML XML-DSig (signxml, [saml], injected verifier → fail-closed). An algorithm allowlist designs out alg:none / RS↔HS confusion.
  • Provisioning — SCIM 2.0 ScimService over a new tenant-scoped ScimStore SPI (+ NoopScimStore) drives /scim/v2/{Users,Groups} CRUD incl. the IdP deactivation PATCH, authenticated by a per-tenant SCIM bearer token (independent of the JWT Auth backend); SCIM-shaped errors (RFC 7644); disabled → 404.
  • Per-tenant IdP on tenants[].sso; cfg.sso / cfg.scim; GET /v1/status/sso; PII-free sso.* / scim.* events (subject hashed, never email/userName); ragctl sso / ragctl scim demos.
  • New core types FederatedIdentity / SsoProtocol / ScimUser / ScimGroup + SsoError/ScimError/ScimNotFoundError/ScimConflictError; wire types ScimListResponse/ScimPatchOp/ScimErrorBody/SsoStatusResponse. dist/schemas + dist/openapi + dist/rag.schema regenerated.
  • No new governed SPI call → the PolicyEngine coverage linter passes with no allowlist entry.

Deferred: remote JWKS discovery + rotation (configured static keys only), SAML SP-initiated redirect + metadata, SCIM bulk / /Me / ETag, directory-backed deprovisioning at authenticate-time, an admin-console SSO/SCIM card.

Documentation

Test plan

  • ScimStore contract suite (CRUD, pagination, tenant isolation)
  • rag-sso unit tests: JWT (expiry/iss/aud/tamper/alg:none/allowlist), OIDC, SAML (conditions/audience/issuer/signature-required), FederatedAuth, ScimService
  • Gateway routes: SCIM CRUD + token guard (401) + disabled (404) + tenant isolation + discovery; SSO status + real JWT through middleware
  • ragctl sso / ragctl scim
  • ruff, mypy --strict (320 files), RAG001, full pytest (excl integration), schema/openapi/config-drift, policy-coverage, log-schema/event-registry/PII

🤖 Generated with Claude Code

New rag-sso package delivering enterprise identity in two surfaces:

- Federation: FederatedAuth is an Auth SPI backend over per-tenant
  OidcProvider / SamlProvider, so a presented IdP token / assertion is
  verified into a Principal at the existing authenticate(token, tenant_id)
  boundary with no middleware change. IdP group claims map to acl_labels,
  so Step 6.3 ACL push-down + 6.5 PII egress govern federated users.
  Dependency-free defaults (stdlib HS256 JWT with full exp/nbf/iss/aud +
  constant-time compare; defusedxml SAML validating Issuer/Conditions/
  Audience); asymmetric OIDC (PyJWT) + SAML XML-DSig (signxml) behind
  [oidc] / [saml] extras; an algorithm allowlist designs out alg:none /
  RS<->HS confusion.

- Provisioning: SCIM 2.0 ScimService over a new tenant-scoped ScimStore
  SPI (+ NoopScimStore) drives /scim/v2/{Users,Groups} CRUD with the IdP
  deactivation PATCH, authenticated by a per-tenant SCIM bearer token
  (independent of the JWT Auth backend); SCIM-shaped errors; disabled -> 404.

Per-tenant IdP config on tenants[].sso; cfg.sso / cfg.scim; GET
/v1/status/sso; PII-free sso.* / scim.* events (hashed subject); ragctl
sso / scim demos. Core types FederatedIdentity / ScimUser / ScimGroup +
SsoError/ScimError/ScimNotFoundError/ScimConflictError; dist schemas /
openapi / rag.schema regenerated.

All gates green: ruff, mypy --strict (320 files), RAG001, full pytest
(excl integration), schema/openapi/config-drift, policy-coverage,
log-schema/event-registry/PII.

Docs: docs/reference/sso.md, docs/architecture/sso-scim.md,
docs/adr/ADR-0040-sso-scim.md, docs/README.md. TRACKER: 6.8 done (72/84).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@officialCodeWork
officialCodeWork merged commit 9163c4c into main Jun 8, 2026
12 of 17 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.

1 participant