Entitlements system with pluggable backends and automatic mail-domain admin sync on login. Provisioning API to create/update mail domains (bulk input, custom attributes) Feature flags to enable/disable mail-domain creation and access management; UI and public config expose these flags
6.8 KiB
Entitlements System
The entitlements system provides a pluggable backend architecture for checking user access rights and synchronizing mail domain admin permissions. It integrates with the DeployCenter (Espace Operateur) API in production and uses a local backend for development.
Architecture
┌─────────────────────────────────────────────┐
│ OIDC Authentication Backend │
│ _sync_entitlements() on every login │
└──────────────┬──────────────────────────────┘
│
┌──────────────▼──────────────────────────────┐
│ Service Layer │
│ get_user_entitlements() │
└──────────────┬──────────────────────────────┘
│
┌──────────────▼──────────────────────────────┐
│ Backend Factory (singleton) │
│ get_entitlements_backend() │
└──────────────┬──────────────────────────────┘
│
┌───────┴───────┐
│ │
┌──────▼─────┐ ┌──────▼───────────────┐
│ Local │ │ DeployCenter │
│ Backend │ │ Backend │
│ (dev/test) │ │ (production, cached) │
└────────────┘ └──────────────────────┘
Components
- Service layer (
core/entitlements/__init__.py): Publicget_user_entitlements()function andEntitlementsUnavailableErrorexception. - Backend factory (
core/entitlements/factory.py):@functools.cachesingleton that imports and instantiates the configured backend class. - Abstract base (
core/entitlements/backends/base.py): Defines theEntitlementsBackendinterface. - Local backend (
core/entitlements/backends/local.py): Always grants access, returnsNoneforcan_admin_maildomains(disabling sync). - DeployCenter backend (
core/entitlements/backends/deploycenter.py): Calls the DeployCenter API with internal Django cache. - OIDC sync (
core/authentication/backends.py): SyncsMailDomainAccessADMIN records on every login based oncan_admin_maildomains.
Error Handling
- Login sync is fail-open: if the entitlements service is unavailable during OIDC login, existing
MailDomainAccessrecords are preserved and the user is allowed in. - The DeployCenter backend falls back to stale cached data when the API is unavailable.
EntitlementsUnavailableErroris only raised when the API fails and no cache exists.
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
ENTITLEMENTS_BACKEND |
core.entitlements.backends.local.LocalEntitlementsBackend |
Python import path of the backend class |
ENTITLEMENTS_BACKEND_PARAMETERS |
{} |
JSON object of parameters passed to the backend constructor |
ENTITLEMENTS_CACHE_TIMEOUT |
300 |
Cache TTL in seconds |
DeployCenter Backend Parameters
When using core.entitlements.backends.deploycenter.DeployCenterEntitlementsBackend, provide these in ENTITLEMENTS_BACKEND_PARAMETERS:
{
"base_url": "https://deploycenter.example.com/api/v1.0/entitlements/",
"service_id": "42",
"api_key": "your-api-key",
"timeout": 10,
"oidc_claims": ["siret"]
}
| Parameter | Required | Description |
|---|---|---|
base_url |
Yes | Full URL of the DeployCenter entitlements endpoint |
service_id |
Yes | Service identifier in DeployCenter |
api_key |
Yes | API key for X-Service-Auth header |
timeout |
No | HTTP timeout in seconds (default: 10) |
oidc_claims |
No | List of OIDC claim names to extract from user_info and forward as query params (e.g. ["siret"]) |
Example Production Configuration
ENTITLEMENTS_BACKEND=core.entitlements.backends.deploycenter.DeployCenterEntitlementsBackend
ENTITLEMENTS_BACKEND_PARAMETERS='{"base_url":"https://deploycenter.example.com/api/v1.0/entitlements/","service_id":"42","api_key":"secret-key","timeout":10,"oidc_claims":["siret"]}'
ENTITLEMENTS_CACHE_TIMEOUT=300
Backend Interface
Custom backends must extend EntitlementsBackend and implement:
class MyBackend(EntitlementsBackend):
def __init__(self, **kwargs):
# Receive ENTITLEMENTS_BACKEND_PARAMETERS as kwargs
pass
def get_user_entitlements(self, user_sub, user_email, user_info=None, force_refresh=False):
# Return: {"can_access": bool, "can_admin_maildomains": list[str] | None}
# Return None for can_admin_maildomains to skip domain admin sync.
# Raise EntitlementsUnavailableError on failure.
pass
DeployCenter API
The DeployCenter backend calls:
GET {base_url}?service_id=X&account_type=user&account_email=X&siret=X
Headers:
X-Service-Auth: Bearer {api_key}
Query parameters include any configured oidc_claims extracted from the OIDC user_info response (e.g. siret).
Response: {"entitlements": {"can_access": bool, "can_admin_maildomains": [str], ...}}
OIDC Login Integration
During OIDC login (post_get_or_create_user), the system:
- Calls
get_user_entitlementswithforce_refresh=True(resets cache) - Syncs
MailDomainAccessADMIN records based oncan_admin_maildomains:- Compares entitled domains with existing records (optimistic early return if in sync)
- Creates missing admin accesses for entitled domains (using
update_or_create) - Removes admin accesses for domains not in the entitled list
- If
can_admin_maildomainsisNone(e.g. local backend), sync is skipped entirely - If the entitlements service is unavailable, existing accesses are preserved (fail-open)
Caching Behavior
- The DeployCenter backend caches entitlements in Django's cache framework (TTL:
ENTITLEMENTS_CACHE_TIMEOUT). - On login,
force_refresh=Truebypasses the cache to fetch fresh data. - If the API fails during a forced refresh, stale cached data is returned as fallback.
- Logging out and back in triggers a fresh fetch, effectively resetting the cache for that user.
Deployment Consideration
Before enabling the DeployCenter backend in production, ensure that existing domain admin assignments are present in DeployCenter. The entitlements sync will remove admin accesses that are not in the DeployCenter response.