121 lines
3.7 KiB
Python
121 lines
3.7 KiB
Python
"""DeployCenter (Espace Operateur) entitlements backend."""
|
|
|
|
import logging
|
|
|
|
from django.conf import settings
|
|
from django.core.cache import cache
|
|
|
|
import requests
|
|
|
|
from core.entitlements import EntitlementsUnavailableError
|
|
from core.entitlements.backends.base import EntitlementsBackend
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DeployCenterEntitlementsBackend(EntitlementsBackend):
|
|
"""Backend that fetches entitlements from the DeployCenter API.
|
|
|
|
Args:
|
|
base_url: Full URL of the entitlements endpoint
|
|
(e.g. "https://dc.example.com/api/v1.0/entitlements/").
|
|
service_id: The service identifier in DeployCenter.
|
|
api_key: API key for X-Service-Auth header.
|
|
timeout: HTTP request timeout in seconds.
|
|
oidc_claims: List of OIDC claim names to extract from user_info
|
|
and forward as query params (e.g. ["siret"]).
|
|
"""
|
|
|
|
def __init__( # pylint: disable=too-many-arguments
|
|
self,
|
|
base_url,
|
|
service_id,
|
|
api_key,
|
|
*,
|
|
timeout=10,
|
|
oidc_claims=None,
|
|
):
|
|
self.base_url = base_url
|
|
self.service_id = service_id
|
|
self.api_key = api_key
|
|
self.timeout = timeout
|
|
self.oidc_claims = oidc_claims or []
|
|
|
|
def _cache_key(self, user_sub):
|
|
return f"entitlements:user:{user_sub}"
|
|
|
|
def _make_request(self, user_email, user_info=None):
|
|
"""Make a request to the DeployCenter entitlements API.
|
|
|
|
Returns:
|
|
dict | None: The response data, or None on failure.
|
|
"""
|
|
params = {
|
|
"service_id": self.service_id,
|
|
"account_type": "user",
|
|
"account_email": user_email,
|
|
}
|
|
|
|
# Forward configured OIDC claims as query params
|
|
if user_info:
|
|
for claim in self.oidc_claims:
|
|
if claim in user_info:
|
|
params[claim] = user_info[claim]
|
|
|
|
headers = {
|
|
"X-Service-Auth": f"Bearer {self.api_key}",
|
|
}
|
|
|
|
try:
|
|
response = requests.get(
|
|
self.base_url,
|
|
params=params,
|
|
headers=headers,
|
|
timeout=self.timeout,
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except (requests.RequestException, ValueError):
|
|
email_domain = user_email.split("@")[-1] if "@" in user_email else "?"
|
|
logger.warning(
|
|
"DeployCenter entitlements request failed for user@%s",
|
|
email_domain,
|
|
exc_info=True,
|
|
)
|
|
return None
|
|
|
|
def get_user_entitlements(
|
|
self, user_sub, user_email, user_info=None, force_refresh=False
|
|
):
|
|
"""Fetch user entitlements from DeployCenter with caching.
|
|
|
|
On cache miss or force_refresh: fetches from the API.
|
|
On API failure: falls back to stale cache if available,
|
|
otherwise raises EntitlementsUnavailableError.
|
|
"""
|
|
cache_key = self._cache_key(user_sub)
|
|
|
|
if not force_refresh:
|
|
cached = cache.get(cache_key)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
data = self._make_request(user_email, user_info=user_info)
|
|
|
|
if data is None:
|
|
# API failed — try stale cache as fallback
|
|
cached = cache.get(cache_key)
|
|
if cached is not None:
|
|
return cached
|
|
raise EntitlementsUnavailableError(
|
|
"Failed to fetch user entitlements from DeployCenter"
|
|
)
|
|
|
|
entitlements = data.get("entitlements", {})
|
|
result = {
|
|
"can_access": entitlements.get("can_access", False),
|
|
}
|
|
|
|
cache.set(cache_key, result, settings.ENTITLEMENTS_CACHE_TIMEOUT)
|
|
return result
|