feat: HUB-22 — API machine des sessions draco : liste et révocation des sessions (2.85.0, #227)
This commit is contained in:
@@ -7,6 +7,12 @@
|
|||||||
# profil étendu, créer, modifier, désactiver/réactiver, supprimer,
|
# profil étendu, créer, modifier, désactiver/réactiver, supprimer,
|
||||||
# réinitialiser le mot de passe et le MFA.
|
# réinitialiser le mot de passe et le MFA.
|
||||||
#
|
#
|
||||||
|
# HUB-22 (#227, vague C du plan Hub v2.1) ajoute, sous la même garde et
|
||||||
|
# avec la même journalisation, les routes « sessions » : lister les
|
||||||
|
# sessions draco actives d'un compte, en révoquer une (par son `jti`,
|
||||||
|
# jamais celle d'un autre compte) ou toutes (déconnexion globale, sans
|
||||||
|
# désactiver le compte), via le registre des émissions de HUB-13.
|
||||||
|
#
|
||||||
# Emplacement : ``/api/admin/machine/comptes`` — dans l'espace
|
# Emplacement : ``/api/admin/machine/comptes`` — dans l'espace
|
||||||
# d'administration existant (``/api/admin/…``), avec un segment
|
# d'administration existant (``/api/admin/…``), avec un segment
|
||||||
# ``machine`` explicite qui le distingue des routes d'administration par
|
# ``machine`` explicite qui le distingue des routes d'administration par
|
||||||
@@ -36,6 +42,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel, validator
|
||||||
@@ -122,6 +129,16 @@ def _compte_ou_404(username: str) -> dict:
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def _identifiant_sessions(user: dict) -> str:
|
||||||
|
"""Clé du registre des émissions draco pour un compte.
|
||||||
|
|
||||||
|
C'est le ``sub`` des jetons draco : l'identifiant du compte
|
||||||
|
(``id``), à défaut son nom d'utilisateur — exactement la clé
|
||||||
|
qu'utilise ``_revoquer_sessions`` depuis HUB-13.
|
||||||
|
"""
|
||||||
|
return str(user.get("id") or user.get("username") or "")
|
||||||
|
|
||||||
|
|
||||||
def _revoquer_sessions(user: dict) -> int:
|
def _revoquer_sessions(user: dict) -> int:
|
||||||
"""Révoque les sessions d'un compte ; retourne le nombre de jti draco.
|
"""Révoque les sessions d'un compte ; retourne le nombre de jti draco.
|
||||||
|
|
||||||
@@ -132,8 +149,7 @@ def _revoquer_sessions(user: dict) -> int:
|
|||||||
avancé par l'appelant (désactivation, réinitialisation du mot de
|
avancé par l'appelant (désactivation, réinitialisation du mot de
|
||||||
passe) ou par le drapeau ``active`` tant que le compte est inactif.
|
passe) ou par le drapeau ``active`` tant que le compte est inactif.
|
||||||
"""
|
"""
|
||||||
sub = str(user.get("id") or user.get("username") or "")
|
return draco_session.revoke_all_for_user(_identifiant_sessions(user))
|
||||||
return draco_session.revoke_all_for_user(sub)
|
|
||||||
|
|
||||||
|
|
||||||
def _audit(request: Request, acteur: str, action: str, cible: str,
|
def _audit(request: Request, acteur: str, action: str, cible: str,
|
||||||
@@ -349,3 +365,60 @@ async def reinitialiser_mfa(username: str, request: Request,
|
|||||||
_audit(request, acteur, "mfa_reinitialise", username,
|
_audit(request, acteur, "mfa_reinitialise", username,
|
||||||
{"webauthn_conserves": webauthn_restantes})
|
{"webauthn_conserves": webauthn_restantes})
|
||||||
return _profil(updated)
|
return _profil(updated)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Sessions draco (HUB-22) ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
@router.get("/{username}/sessions")
|
||||||
|
async def lister_sessions(username: str,
|
||||||
|
acteur: str = Depends(require_machine_admin)):
|
||||||
|
"""Liste les sessions draco actives d'un compte.
|
||||||
|
|
||||||
|
Une session par ``jti`` émis et non expiré (registre des émissions
|
||||||
|
de HUB-13), triées par expiration croissante ; ``revoquee`` signale
|
||||||
|
un ``jti`` déjà présent dans la liste des révocations et
|
||||||
|
``expire_le`` est l'échéance ``exp`` en ISO 8601 UTC.
|
||||||
|
"""
|
||||||
|
user = _compte_ou_404(username)
|
||||||
|
sessions = [{
|
||||||
|
"jti": s["jti"],
|
||||||
|
"exp": s["exp"],
|
||||||
|
"expire_le": datetime.fromtimestamp(s["exp"], tz=UTC).isoformat(),
|
||||||
|
"revoquee": s["revoquee"],
|
||||||
|
} for s in draco_session.issued_sessions_for_user(
|
||||||
|
_identifiant_sessions(user))]
|
||||||
|
return {"username": username, "sessions": sessions}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{username}/sessions/{jti}/revoquer")
|
||||||
|
async def revoquer_session(username: str, jti: str, request: Request,
|
||||||
|
acteur: str = Depends(require_machine_admin)):
|
||||||
|
"""Révoque une seule session draco d'un compte, par son ``jti``.
|
||||||
|
|
||||||
|
404 si le ``jti`` ne figure pas dans l'ensemble actif de ce
|
||||||
|
compte : une session d'un autre utilisateur n'est jamais révoquée
|
||||||
|
par cette route (aucune révocation cross-utilisateur).
|
||||||
|
"""
|
||||||
|
user = _compte_ou_404(username)
|
||||||
|
if not draco_session.revoke_one_for_user(
|
||||||
|
_identifiant_sessions(user), jti):
|
||||||
|
raise HTTPException(404, "Session introuvable")
|
||||||
|
_audit(request, acteur, "session_revoquee", username, {"jti": jti})
|
||||||
|
return {"jti": jti, "revoquee": True}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{username}/sessions/revoquer-toutes")
|
||||||
|
async def revoquer_toutes_sessions(
|
||||||
|
username: str, request: Request,
|
||||||
|
acteur: str = Depends(require_machine_admin)):
|
||||||
|
"""Révoque toutes les sessions draco actives d'un compte.
|
||||||
|
|
||||||
|
C'est la « déconnexion globale » : contrairement à la
|
||||||
|
désactivation, le compte reste actif — seule l'émission d'une
|
||||||
|
nouvelle session (nouvelle connexion) rouvre l'accès.
|
||||||
|
"""
|
||||||
|
user = _compte_ou_404(username)
|
||||||
|
nb_jti = _revoquer_sessions(user)
|
||||||
|
_audit(request, acteur, "sessions_revoquees", username,
|
||||||
|
{"jti_revoques": nb_jti})
|
||||||
|
return {"revoquees": nb_jti}
|
||||||
|
|||||||
Reference in New Issue
Block a user