Kanbante 0.46.2 : client du registre des rôles du Hub (contrat D3(a), cache 60 s par sub, repli rôle inconnu) (app/hub_roles.py)
CI / test (push) Failing after 0s
CI / test (push) Failing after 0s
This commit is contained in:
@@ -0,0 +1,120 @@
|
|||||||
|
"""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
|
||||||
Reference in New Issue
Block a user