WeblateOrg / WeblateOrg/weblate

SCIM 2.0 endpoint for automatic user and team provisioning and deprovisioning

Open
#21,567 4 comments 1 reaction 0 assignees View on GitHub
Area: Auth Waiting for: Demand
Dominant language
Python
Stars
6.1k
Forks
1.4k
Avg merge
9h 53m
Merged PRs (30d)
395

Description

### Describe the problem

Organizations manage joiners and leavers in a directory service (Entra ID, Okta, Google Workspace). Weblate cannot consume that data today. Accounts can be provisioned via OAuth/OIDC/SAML just in time provisioning, but access and revoking of accounts needs to be managed manually:

- Onboarding: accounts and team memberships must be created by hand or by custom scripts against the REST API.
- Offboarding: nothing revokes access when a user leaves. Sessions, team memberships and API tokens stay valid until an admin notices.

SCIM 2.0 is the industry standard for this. Every major IdP can push user and group changes to a SCIM endpoint.

### Solution brainstorm

Add an optional SCIM 2.0 endpoint based on django-scim2 (https://github.com/15five/django-scim2).

I have a working prototype as a customization layer, about 350 lines (implementation + smoke tests), no changes to Weblate core. Verified against Weblate 2026.8.1:

- Users: create, filter (userName, externalId), GET, PUT, PATCH (active, displayName, emails), DELETE.
- Groups: map to Weblate teams by name. Add, remove and replace members
- Deactivation via `active=false` makes the user's API token return 401 and disables the user
- DELETE calls the existing `remove_user` anonymization.

Design points that need maintainer input before I open a PR, If it's interesting:

1. Where to store `externalId` - ID used to map the user created via SCIM to the internal user record: new fields on User/Group or separate link tables (prototype uses link tables).
2. Authentication for the endpoint: prototype accepts a Weblate API token of a user with `user.edit`.
3. DELETE semantics: anonymize vs deactivate -> I've opted for anonymize in my prototype
5. `REQUIRE_LOGIN` installs `LoginRequiredMiddleware`, so SCIM views need `login_not_required` wrappers to remain accessible and do its own API token auth.

### Describe alternatives you have considered

- SSO just-in-time provisioning: covers first login only. No team sync, no deprovisioning.
- External scripts against the REST API: every organization rebuilds the same sync, with polling delay and no standard IdP support.
- LDAP sync: only for LDAP shops and still no deprovisioning.

### Screenshots

_No response_

### Additional context

_No response_

Contributor guide

Open the contributing guide

Research direction

Start by reviewing the proposed django-scim2 integration, the existing REST API authentication, and the prototype's smoke tests. Resolve the stated choices for externalId storage, endpoint authentication, DELETE behavior, and middleware handling; done means an agreed core design with coverage for user and group provisioning, synchronization, and deprovisioning.

Written by the indexing model from the issue text.

Assessment

Tech stack
django, python
Domain
api, authentication, authorization, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.