From 9f6f3668707c37aec326cb53693ab9b5acd23cd4 Mon Sep 17 00:00:00 2001 From: Spark Date: Fri, 6 Nov 2026 11:07:27 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20HUB-22=20=E2=80=94=20API=20machine=20de?= =?UTF-8?q?s=20sessions=20draco=20:=20liste=20et=20r=C3=A9vocation=20des?= =?UTF-8?q?=20sessions=20(2.85.0,=20#227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_draco_session.py | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/test_draco_session.py b/tests/test_draco_session.py index a2949c3..7eeca91 100644 --- a/tests/test_draco_session.py +++ b/tests/test_draco_session.py @@ -18,9 +18,13 @@ def _draco_env(monkeypatch, tmp_path): monkeypatch.setenv("DRACO_SESSION_SECRET", "secret-de-test") monkeypatch.setattr(draco_session, "REVOKED_FILE", tmp_path / "draco_revoked.json") + monkeypatch.setattr(draco_session, "ISSUED_FILE", + tmp_path / "draco_issued.json") draco_session._revoked = None + draco_session._issued = None yield draco_session._revoked = None + draco_session._issued = None # ── Jeton : émission / vérification ───────────────────────────────────── @@ -232,3 +236,58 @@ def test_revocations_protegees_par_cle_machine(admin_client, monkeypatch): headers={"X-Draco-Machine-Key": "cle-machine"}) assert r.status_code == 200 assert {"jti": jti, "exp": exp} in r.json()["revoked"] + + +# ── Registre des sessions (HUB-22, #227) ──────────────────────────────── + +AUTRE = {"id": "user-2", "username": "autre", "display_name": "Autre"} + + +def test_sessions_emises_listees_triees_et_marquees(): + _t1, jti1, exp1 = draco_session.issue_token(USER) + t2, jti2, exp2 = draco_session.issue_token(USER) + draco_session.issue_token(AUTRE) # autre sub : exclu + draco_session.revoke(jti2, exp2) + + sessions = draco_session.issued_sessions_for_user("admin-1") + assert [s["jti"] for s in sessions] == [jti1, jti2] # exp croissant + assert sessions[0] == {"jti": jti1, "exp": exp1, "revoquee": False} + assert sessions[1] == {"jti": jti2, "exp": exp2, "revoquee": True} + assert draco_session.verify_token(t2) is None # cohérent + + +def test_sessions_emises_expirees_purgees(): + now = int(time.time()) + with draco_session._lock: + store = draco_session._load_issued() + store["admin-1"] = {"ancien": now - 10, "frais": now + 600} + assert [s["jti"] for s in + draco_session.issued_sessions_for_user("admin-1")] == ["frais"] + with draco_session._lock: + assert "ancien" not in draco_session._load_issued()["admin-1"] + assert draco_session.issued_sessions_for_user("personne") == [] + assert draco_session.issued_sessions_for_user("") == [] + + +def test_revoquer_une_session_appartenante(): + token, jti, _exp = draco_session.issue_token(USER) + assert draco_session.revoke_one_for_user("admin-1", jti) is True + assert draco_session.verify_token(token) is None + assert draco_session.is_revoked(jti) + + +def test_revoquer_une_session_cross_utilisateur_refusee(): + token, jti, _exp = draco_session.issue_token(USER) + # Le jti existe, mais pas dans l'ensemble émis de cet autre sub. + assert draco_session.revoke_one_for_user("user-2", jti) is False + assert draco_session.verify_token(token) is not None + assert draco_session.revoke_one_for_user("admin-1", "inconnu") is False + assert draco_session.revoke_one_for_user("", jti) is False + + +def test_revoquer_une_session_expiree_refusee(): + now = int(time.time()) + with draco_session._lock: + draco_session._load_issued()["admin-1"] = {"perime": now - 10} + assert draco_session.revoke_one_for_user("admin-1", "perime") is False + assert not draco_session.is_revoked("perime")