Skip to content

[Web] Add SCIM 2.0 provider for IdP user provisioning#7148

Open
Fly7113 wants to merge 6 commits intomailcow:stagingfrom
Fly7113:feat/SCIM
Open

[Web] Add SCIM 2.0 provider for IdP user provisioning#7148
Fly7113 wants to merge 6 commits intomailcow:stagingfrom
Fly7113:feat/SCIM

Conversation

@Fly7113
Copy link
Copy Markdown

@Fly7113 Fly7113 commented Mar 17, 2026

Contribution Guidelines

What does this PR include?

Short Description

Implements a SCIM 2.0 (RFC 7643/7644) server endpoint so any Identity Provider — Keycloak, Entra ID, Okta, or any LDAP/OIDC IdP — can push user lifecycle events to mailcow in real time, independently of whichever login protocol is configured.

Protocol support

  • Full Users CRUD: POST, GET, PUT, PATCH (RFC 7644 §3.5.2), DELETE
  • SCIM DELETE is a soft-deactivate (active=0); mail data is never removed
  • Filtering: filter=userName eq "..." on list endpoint
  • Pagination: startIndex / count
  • Discovery: ServiceProviderConfig, Schemas, ResourceTypes
  • Groups: out of scope

Authentication & token management

Bearer tokens are generated in the admin UI (System > Configuration > Access > SCIM). The raw token is shown once at creation and never stored; only its SHA-256 hash is kept. Each token supports:

  • Optional domain restriction (limits which mailboxes the token can manage)
  • Optional mailbox template (applied on user creation)
  • Optional IP allow-list / skip-IP-check flag
  • Active/inactive toggle

Database schema

Two new tables added via the existing init_db migration mechanism:

  • scim_tokens: stores token metadata and hashed credentials
  • scim_maps: maps IdP externalId values to mailcow usernames per token The mailbox.authsource ENUM is extended with 'scim'.

Authsource & login design

SCIM is a provisioning protocol, not an authentication protocol. mailbox.authsource='scim' records who manages the user; login is handled by the globally configured IAM provider:

  • Keycloak / Generic-OIDC: SCIM users pass through the existing verify-sso OIDC flow (identity_provider 'verify-sso' case).
  • LDAP: SCIM users authenticate via ldap_mbox_login(), with full TFA support, matching the behaviour of authsource='ldap' users.
  • No IAM configured: SCIM users cannot log in; the admin UI shows a warning on the SCIM configuration tab.

Attempting a password login as a SCIM user when an OIDC provider is configured returns a clear error directing the user to their IdP.

Claiming pre-existing users

A SCIM POST for a user who already has authsource='scim' (e.g. set manually by the admin to prepare a migration) is treated as a claim: attributes are updated, scim_maps is upserted, and 200 is returned. A SCIM POST for a user managed by a different authsource returns 409 with an actionable message explaining how to transfer ownership.

Admin UI

  • New SCIM tab under System > Configuration > Access (alongside Identity Provider settings)
  • Token table with active toggle and delete; one-time raw token modal
  • Mailbox edit form gains a SCIM authsource option, shown only when SCIM tokens exist (or the mailbox is already set to SCIM)
  • Contextual warning when no external IdP is configured for login

Affected Containers

  • php-fpm-mailcow
  • nginx-mailcow (nginx configuration template)
  • sogo-mailcow (shared DB initialisation file)

Did you run tests?

What did you tested?

  • User creation, updating, deactivation via SCIM
  • General SCIM protocol testing
  • Migration between mailcow/external IdP and SCIM
  • Logging-in in various scenarios
  • Invalid SCIM requests (updating non-SCIM users to test the error handling)
  • Admin UI test (create/update/delete SCIM tokens)

What were the final results? (Awaited, got)

All the tests gave results conforming to the described behaviours.

@Fly7113
Copy link
Copy Markdown
Author

Fly7113 commented Mar 17, 2026

Fixes #6974

@Fly7113
Copy link
Copy Markdown
Author

Fly7113 commented Mar 18, 2026

Just a small testing update: in these past few days I had the chance to test this PR on my production system, alongside Authentik as SCIM and OIDC IdP. As far as I'm concerned all the features are working as expected, even in a fully production system having users with mixed authsources.

The are only two things of which I'm not fully convinced:

  1. The deactivation of the user when a SCIM DELETE request is received: the user can be already just deactivated leveraging the active property of the user's JSON, so that scenario is already covered, and the user should be safe to be deleted on a DELETE request. From a spec compliance point of view the current implementation is not fully compliant because RFC 7644 states that a system MAY choose not to delete the specified resource, but such resource, in the case of other SCIM operations, should then be treated as if it was actually deleted. On the other hand a true deletion of the user on a DELETE request would be easier to implement with a perfect compliance.
  2. As of now, the deactivation of the user via the active JSON parameter fully deactivates the user, but another alternative would be to put the user in the "soft" deactivation status that permits the user to still receive emails. I don't think this choice is relative to compliance to the RFCs, therefore an option would be to give both alternatives to the system admin and making the effect configurable from the admin UI.

Before moving forward with a decision I'd like to hear the consensus of the maintainers team.

@Fly7113
Copy link
Copy Markdown
Author

Fly7113 commented Mar 22, 2026

Sorry for pushing these new quite conspicuous two commits, but in these further days of testing I realised that the UI (and part of the logic about the IP check) was badly done, so I decided to work on it.
Now the SCIM tokens are modifiable, and the template mapping uses the same working principles of the ones in the OIDC template mapping, to have more consistency.
The input fields are now more robust (no more free text basically, just dropdown menus), and I added the strings in the lang.en-gb JSON which I had previously forgot (ups :D).

Fly7113 added 4 commits March 31, 2026 15:20
Implements a SCIM 2.0 (RFC 7643/7644) server endpoint so any Identity
Provider — Keycloak, Entra ID, Okta, or any LDAP/OIDC IdP — can push
user lifecycle events to mailcow in real time, independently of whichever
login protocol is configured.

## Protocol support

- Full Users CRUD: POST, GET, PUT, PATCH (RFC 7644 §3.5.2), DELETE
- SCIM DELETE is a soft-deactivate (active=0); mail data is never removed
- Filtering: filter=userName eq "..." on list endpoint
- Pagination: startIndex / count
- Discovery: ServiceProviderConfig, Schemas, ResourceTypes
- Groups: out of scope

## Authentication & token management

Bearer tokens are generated in the admin UI (System > Configuration >
Access > SCIM). The raw token is shown once at creation and never stored;
only its SHA-256 hash is kept. Each token supports:
- Optional domain restriction (limits which mailboxes the token can manage)
- Optional mailbox template (applied on user creation)
- Optional IP allow-list / skip-IP-check flag
- Active/inactive toggle

## Database schema

Two new tables added via the existing init_db migration mechanism:
- scim_tokens: stores token metadata and hashed credentials
- scim_maps: maps IdP externalId values to mailcow usernames per token
The mailbox.authsource ENUM is extended with 'scim'.

## Authsource & login design

SCIM is a provisioning protocol, not an authentication protocol.
mailbox.authsource='scim' records who manages the user; login is
handled by the globally configured IAM provider:

- Keycloak / Generic-OIDC: SCIM users pass through the existing
  verify-sso OIDC flow (identity_provider 'verify-sso' case).
- LDAP: SCIM users authenticate via ldap_mbox_login(), with full
  TFA support, matching the behaviour of authsource='ldap' users.
- No IAM configured: SCIM users cannot log in; the admin UI shows
  a warning on the SCIM configuration tab.

Attempting a password login as a SCIM user when an OIDC provider is
configured returns a clear error directing the user to their IdP.

## Claiming pre-existing users

A SCIM POST for a user who already has authsource='scim' (e.g. set
manually by the admin to prepare a migration) is treated as a claim:
attributes are updated, scim_maps is upserted, and 200 is returned.
A SCIM POST for a user managed by a different authsource returns 409
with an actionable message explaining how to transfer ownership.

## Admin UI

- New SCIM tab under System > Configuration > Access (alongside
  Identity Provider settings)
- Token table with active toggle and delete; one-time raw token modal
- Mailbox edit form gains a SCIM authsource option, shown only when
  SCIM tokens exist (or the mailbox is already set to SCIM)
- Contextual warning when no external IdP is configured for login
Restored the original protocol detection that was modified during development and badly restored.
Updated the SCIM base URL building to avoid hardcoded protocol.
The flag was redundant: the IP check logic already skips enforcement
when allow_from is empty, making "Allow from any IP" equivalent to
leaving the IP list blank. Removing it simplifies the data model and
the UI — an empty allow_from means unrestricted access, a populated
one enforces the ACL.
Each SCIM token now supports a list of mailcow_template attribute value
-> mailbox template mappings, mirroring the attribute mapping feature of
the OIDC identity providers. When a user is provisioned, the incoming
'mailcow_template' attribute (top-level or inside the enterprise
extension URN) is matched against the token's mapper list; the first
hit wins, with default_template as fallback.

The old 'template' column is replaced by 'default_template' for naming
consistency with the identity_provider table. Two new JSON columns
'mappers'/'templates' store the per-token mapping pairs.

A full token edit modal is also introduced. Previously only
active/inactive could be toggled after creation. Both the edit modal
and the add form expose the attribute mapping section, using the same
two-column (attribute value | template select) layout as the OIDC
identity provider settings.
Fly7113 added 2 commits March 31, 2026 19:23
Signed-off-by: Lorenzo Moscati <lorenzo@moscati.page>
Signed-off-by: Lorenzo Moscati <lorenzo@moscati.page>
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