Files
messages/docs/entitlements.md
Sylvain Zimmer d153f5ad69 (entitlements) add DeployCenter backend for syncing maildomain admins (#572)
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
2026-03-03 15:06:55 +01:00

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): Public get_user_entitlements() function and EntitlementsUnavailableError exception.
  • Backend factory (core/entitlements/factory.py): @functools.cache singleton that imports and instantiates the configured backend class.
  • Abstract base (core/entitlements/backends/base.py): Defines the EntitlementsBackend interface.
  • Local backend (core/entitlements/backends/local.py): Always grants access, returns None for can_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): Syncs MailDomainAccess ADMIN records on every login based on can_admin_maildomains.

Error Handling

  • Login sync is fail-open: if the entitlements service is unavailable during OIDC login, existing MailDomainAccess records are preserved and the user is allowed in.
  • The DeployCenter backend falls back to stale cached data when the API is unavailable.
  • EntitlementsUnavailableError is 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:

  1. Calls get_user_entitlements with force_refresh=True (resets cache)
  2. Syncs MailDomainAccess ADMIN records based on can_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
  3. If can_admin_maildomains is None (e.g. local backend), sync is skipped entirely
  4. 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=True bypasses 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.