121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
"""Rôles par application — consommation du registre du Hub (HUB-19).
|
|
|
|
Portage Python du pilote ShaarIt 0.7.0 (``src/hub_roles.rs``),
|
|
contrat D3(a) du plan Hub v2 : à l'ouverture de session draco,
|
|
Kanbante demande au Hub le niveau du porteur du cookie pour
|
|
l'application ``kanbante`` — ``GET {hub}/api/roles/kanbante``,
|
|
en-tête Cookie complet de la requête relayé tel quel,
|
|
``Accept: application/json``, délai 2 s → ``{"app", "sub",
|
|
"role", "explicite", "ttl_secondes": 60}``, rôle ∈ {``aucun``,
|
|
``utilisateur``, ``administrateur``}.
|
|
|
|
Cache mémoire par ``sub``, TTL 60 s, sur le patron du cache de
|
|
révocations de ``app.draco`` : l'horodatage est posé **avant**
|
|
l'appel — un échec ne déclenche pas de rafale et l'ancien cache
|
|
reste en vigueur (il resservira aux appels suivants pendant le
|
|
TTL posé par la tentative). L'URL du Hub est résolue comme celle
|
|
de la barre commune (``app.config.resoudre_urls_draco`` :
|
|
variable explicite ``DRACO_HUB_URL`` > hôte public de la requête
|
|
> dérivation par l'hôte sœur de l'agent > défaut officiel) —
|
|
jamais d'URL dev figée.
|
|
|
|
**Repli obligatoire** : Hub injoignable, en erreur ou réponse
|
|
illisible → ``None`` (rôle **inconnu**), et l'appelant garde son
|
|
comportement actuel. Le registre ne crée jamais un verrouillage
|
|
nouveau.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import threading
|
|
import time
|
|
|
|
import httpx
|
|
|
|
from app.config import resoudre_urls_draco
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
#: Identifiant de Kanbante dans le registre des applications du Hub.
|
|
APP = "kanbante"
|
|
|
|
#: TTL du cache, aligné sur ``ttl_secondes`` annoncé par le Hub
|
|
#: et sur le cache de révocations draco.
|
|
CACHE_TTL = 60.0 # secondes
|
|
|
|
ROLES_CONNUS = ("aucun", "utilisateur", "administrateur")
|
|
|
|
_verrou = threading.Lock()
|
|
_cache: dict[str, str] = {}
|
|
_cache_fetched_at: float = 0.0
|
|
|
|
|
|
def _reset_cache() -> None:
|
|
"""Vide le cache (tests, ou redémarrage de processus)."""
|
|
global _cache_fetched_at
|
|
with _verrou:
|
|
_cache.clear()
|
|
_cache_fetched_at = 0.0
|
|
|
|
|
|
def _consulter(hub_url: str, cookie: str) -> str | None:
|
|
"""Un appel au registre ; rôle connu ou ``None`` (inconnu)."""
|
|
url = f"{hub_url.rstrip('/')}/api/roles/{APP}"
|
|
try:
|
|
reponse = httpx.get(
|
|
url,
|
|
headers={"Cookie": cookie, "Accept": "application/json"},
|
|
timeout=2.0,
|
|
)
|
|
except Exception as exc: # Hub injoignable : repli, jamais un blocage
|
|
logger.debug("Registre des rôles du Hub injoignable (%s) : %s", url, exc)
|
|
return None
|
|
if getattr(reponse, "status_code", None) != 200:
|
|
logger.debug(
|
|
"Registre des rôles du Hub en erreur (%s) : statut %s",
|
|
url, getattr(reponse, "status_code", "?"),
|
|
)
|
|
return None
|
|
try:
|
|
donnees = reponse.json()
|
|
except Exception:
|
|
return None
|
|
if not isinstance(donnees, dict):
|
|
return None
|
|
role = donnees.get("role")
|
|
return role if role in ROLES_CONNUS else None
|
|
|
|
|
|
def role_pour(cookie: str, sub: str, hote: str | None = None) -> str | None:
|
|
"""Niveau connu pour ce ``sub`` : un rôle ou ``None`` (inconnu).
|
|
|
|
``cookie`` est l'en-tête Cookie complet de la requête, relayé
|
|
tel quel au Hub ; ``hote`` est l'hôte public de la requête,
|
|
pour résoudre l'URL du Hub comme la barre commune.
|
|
"""
|
|
global _cache_fetched_at
|
|
sub = str(sub or "")
|
|
if not sub:
|
|
return None
|
|
now = time.monotonic()
|
|
with _verrou:
|
|
if now - _cache_fetched_at < CACHE_TTL and sub in _cache:
|
|
return _cache[sub]
|
|
# Horodatage posé avant l'appel (patron révocations) :
|
|
# l'ancien cache est conservé tel quel pendant l'appel.
|
|
_cache_fetched_at = now
|
|
hub_url = resoudre_urls_draco(hote).hub_url
|
|
resultat = _consulter(hub_url, cookie)
|
|
if resultat is not None:
|
|
with _verrou:
|
|
_cache[sub] = resultat
|
|
_cache_fetched_at = time.monotonic()
|
|
else:
|
|
logger.debug(
|
|
"Registre des rôles du Hub indisponible (%s/api/roles/%s)"
|
|
" — comportement actuel conservé",
|
|
hub_url, APP,
|
|
)
|
|
return resultat
|