Skip to content

Phase 2: SCIM 2.0 user provisioning - #40

Merged
alikahwaji merged 1 commit into
developfrom
feat/scim
Oct 24, 2026
Merged

alikahwaji merged 1 commit into
developfrom
feat/scim

Conversation

@alikahwaji

Copy link
Copy Markdown
Contributor

Phase 2: SCIM, after SSO (#39).

What's new

Administration → Integrations → User provisioning (SCIM) (admins):

  • the card shows the tenant URL (<PUBLIC_API_URL>/scim/v2)
  • create a SCIM token (shown once, expires in a year) and revoke it

Entra, Okta and other identity providers can then create accounts, update names, emails and roles, and deactivate people who leave. Accounts that SCIM creates have no usable password; they sign in with SSO.

How it's built

  • Migration 024:
    • api_keys scopes may be scim (exactly one scope per key)
    • users.scim_external_id
  • SCIM token: an API key with the scim scope.
    • It can call only /scim/v2, and read keys can't call /scim/v2.
    • The middleware swaps it for a one-minute internal token with the role scim, which the SCIM endpoints require. A person's token is refused.
  • /scim/v2 (RFC 7643/7644):
    • ServiceProviderConfig, ResourceTypes, Schemas
    • Users list (filters userName eq / externalId eq, paging), get, create, replace
    • PATCH: add/replace, with or without a path, "True"/"False" strings (the Entra and Okta forms)
    • DELETE deactivates; accounts are kept for the audit trail
    • errors use the SCIM error format, and responses are application/scim+json
  • Rules:
    • emails are unique across SCIRM (409 uniqueness)
    • the last active admin can't be deactivated or demoted (409 mutability)
    • roles must be SCIRM roles; the default is viewer
  • Audit: the audit middleware now also covers /scim/v2, so every SCIM change is recorded under the token (api-key:<name> (<prefix>)). SCIM and SSO events are in the "Access and team" category.
  • Docs: docs/scim.md; the SSO guide points to it.

Testing

  • New tests/security/test_scim.py (14 tests):
    • the token reaches only SCIM; read keys and person tokens are refused; unknown scopes are refused
    • create, find by userName (case-insensitive) and externalId, get
    • default viewer; unknown roles refused
    • email uniqueness within and across organizations, including a rename into another organization's email
    • bad filters refused
    • an Entra PATCH deactivates, and that person's sign-in and refresh stop
    • an Okta PATCH without a path, and name.familyName
    • PUT replace; DELETE deactivates; remove op refused
    • changes are audited under the token
    • another organization's users are invisible
    • the last admin can't be deprovisioned
  • tests/db: {scim} is accepted and {read,scim} refused (one scope). Migrations round-trip.
  • Suites: 585 in-memory; security + db + evidence on PostgreSQL: 383. Frontend: 39, tsc clean, vite build OK. Strict docs build OK.
  • Live on the gate stack (migrated 023 → 024):
    • created a SCIM token in the Integrations card (shown once, listed)
    • acting as the identity provider, provisioned a user (name, role and external id stored)
    • that user then signed in with SSO through the fake identity provider
    • an Entra-style PATCH ("value": "False") deactivated them, and their browser session's refresh was refused
    • the SCIM token was refused on /api/v1/suppliers, and the create appears in the audit log under the token

Not supported

Groups, bulk operations, sorting, ETags, remove operations, password changes.

🤖 Generated with Claude Code

Phase 2: SCIM after SSO.

- Migration 024: api_keys may have the scope 'scim' (one scope per key);
  users.scim_external_id.
- A SCIM token (Integrations > User provisioning) can call only /scim/v2;
  read keys cannot. It is swapped for a one-minute internal token with the
  role "scim"; SCIM endpoints require it.
- /scim/v2: ServiceProviderConfig, ResourceTypes, Schemas; Users list
  (userName eq / externalId eq filters, paging), get, create (no usable
  password: SSO or a reset link), replace, PATCH (add/replace, with or
  without a path, "True"/"False" strings: Entra and Okta forms), delete
  (deactivates). SCIM error format and application/scim+json.
- Emails unique across SCIRM (409 uniqueness); the last active admin
  cannot be deactivated or demoted (409 mutability); deactivation stops
  sign-in and session renewal.
- SCIM writes are audited under the token (the audit middleware now also
  covers /scim/v2); SCIM and SSO events in the access category.
- docs/scim.md; SSO docs point to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alikahwaji
alikahwaji merged commit 4d56423 into develop Oct 24, 2026
21 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