Files
ObsiGate/docs/features/hub-roles-obsigate-228.md
T
Spark dcf847e2fd
CI / lint (pull_request) Successful in 1m17s
CI / security (pull_request) Successful in 21s
CI / test (pull_request) Failing after 3m39s
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
feat: HUB-19 — rôles du Hub consommés par ObsiGate (registre D3(a)) #228
2026-11-06 16:30:30 +00:00

4.0 KiB

#228 — HUB-19 : propagation des rôles du Hub à ObsiGate

Carte : suite de HUB-19 (plan Hub v2 §2.2, décision D3(a)) — le pilote ShaarIt (0.7.0, en production) a posé le contrat ; ObsiGate le consomme à son tour, comme hôte à la fois émettrice et consommatrice du SSO. Version cible : 2.85.1 (branche depuis le main 2.84.1 ; hypothèse : à rebaser après la PR « API sessions HUB-22 » — 2.85.0 — si elle fusionne d'abord).

Contrat consommé (D3(a))

À l'ouverture de session draco — chez ObsiGate : la validation locale du cookie partagé, draco_session.user_for_draco() (ACC-3.2, #217), appelée par le repli draco de get_current_user — consultation du registre :

  • GET {hub}/api/roles/obsigate, en-tête Cookie complet de la requête relayé tel quel, Accept: application/json, délai 2 s ;
  • réponse 200 : {"app", "sub", "role", "explicite", "ttl_secondes": 60}, role ∈ {aucun, utilisateur, administrateur} ;
  • URL du Hub : draco_config.app_url("hub"), la résolution de la barre commune (#223) — DRACO_HUB_URL explicite d'abord, dérivation par l'hôte sœur (suffixe d'environnement de l'hôte de l'agent) ensuite, défaut officiel enfin. Jamais d'URL dev figée.

Client — backend/hub_roles.py

Sur le patron du pilote (src/hub_roles.rs de ShaarIt) :

  • cache mémoire par sub, TTL 60 s ;
  • horodatage posé avant l'appel : un échec conserve l'ancien cache et ne déclenche pas de rafale vers un Hub en panne ;
  • toute erreur (réseau, délai, statut ≠ 200, JSON illisible, rôle hors contrat) → rôle inconnu (None) → l'appelant garde son comportement actuel. Le registre ne crée jamais un verrouillage nouveau.

Effets (sessions établies par draco seulement)

  1. Niveau connu aucun → user_for_draco refuse : pas de session établie par draco. require_auth répond alors 403 avec un message explicite en français (niveau « aucun », matrice des rôles du Hub, accès natif toujours possible), consigné via request.state.role_hub_refuse. L'authentification native d'ObsiGate (son propre login — c'est l'émettrice, son accès propre est le secours) n'est jamais touchée.
  2. Routes d'administration /api/auth/admin/* (CRUD des comptes, backend/auth/router.py) : nouvelle garde require_admin_hub (backend/auth/middleware.py) = require_admin + , quand la requête est authentifiée par le canal draco (_draco_sub présent, _hub_role joint par user_for_draco) et que le rôle est connu, exigence du niveau administrateur du registre. Sessions natives → règles actuelles inchangées ; rôle inconnu → règles actuelles inchangées. Le registre n'élève aucun privilège : un compte local non admin reste refusé par la règle locale, quel que soit son niveau au Hub.
  3. Inchangés : validation locale du jeton, émission du cookie, révocations (jti), renouvellement glissant. Aucun rôle dans le jeton (claims minimaux de #216 préservés).

Périmètre assumé : la garde du registre vise les routes nommées par la carte, /api/auth/admin/*. Les autres surfaces gardées par require_admin (configuration, vaults, coffres…) gardent leurs règles actuelles ; les y soumettre serait un changement d'une ligne par surface (require_admin_hub est réutilisable tel quel) si Bruno le décide.

Tests

tests/test_hub_roles.py : parsing du contrat (URL, Cookie relayé, Accept, délai 2 s), rôles reconnus / hors contrat, JSON illisible, erreurs HTTP et réseau → inconnu ; résolution de l'URL du Hub (explicite, dérivée -dev, défaut officiel) ; cache par sub, TTL, échec sans rafale avec ancien cache conservé ; intégration sur le chemin draco (refus « aucun » explicite, login natif intact sous « aucun », rôle inconnu = comportement actuel, utilisateur Hub refusé sur /api/auth/admin/*, administrateur Hub accepté, compte local non admin non élevé, session native jamais bloquée par le Hub).