"""Tests de sécurité : anti-forgery du cookie de session. Avant le correctif, n'importe qui pouvait forger le cookie `user_id=admin` et accéder aux données d'un autre utilisateur. La signature HMAC doit rendre toute altération détectable. """ import sys # DATA_DIR temporaire est positionné par conftest.py avant l'import de models. import models # noqa: F401 sys.path.insert(0, ".") import main from fastapi.testclient import TestClient client = TestClient(main.app) def _seed_user(uid: str): import bcrypt models.save_auth(uid, email=f"{uid}@x.fr", password_hash=bcrypt.hashpw(b"x" * 12, bcrypt.gensalt()).decode()) def _login(uid: str) -> dict: _seed_user(uid) r = client.post("/login/password", data={"email": f"{uid}@x.fr", "password": "x" * 12}, follow_redirects=False) assert r.status_code == 302, r.text return {"user_id": r.cookies.get("user_id")} # ── Forgery du cookie ───────────────────────────────────────────────────────── def test_unsigned_cookie_rejected(): """Un cookie `user_id=admin` forgé à la main doit être rejeté.""" _seed_user("alice") r = client.get("/semaine/2026/29", cookies={"user_id": "alice"}, follow_redirects=False) assert r.status_code == 302 assert "/login" in r.headers["location"] def test_tampered_signature_rejected(): """Un cookie valide dont on modifie la signature doit être rejeté.""" cookies = _login("alice") raw = cookies["user_id"] user_id, _, sig = raw.rpartition(".") # Inverse un caractère de la signature bad_sig = ("0" if sig[0] != "0" else "1") + sig[1:] tampered = f"{user_id}.{bad_sig}" r = client.get("/semaine/2026/29", cookies={"user_id": tampered}, follow_redirects=False) assert r.status_code == 302 assert "/login" in r.headers["location"] def test_valid_signed_cookie_accepted(): """Le cookie signé délivré par /login/password doit ouvrir la session.""" cookies = _login("alice") r = client.get("/semaine/2026/29", cookies=cookies, follow_redirects=False) assert r.status_code == 200 def test_signed_cookie_cannot_be_replayed_for_other_user(): """La signature d'alice ne doit pas valider pour bob (user_id modifié).""" cookies = _login("alice") raw = cookies["user_id"] _, _, sig = raw.rpartition(".") # Remplace alice par bob en conservant la signature d'alice forged = f"bob.{sig}" _seed_user("bob") r = client.get("/semaine/2026/29", cookies={"user_id": forged}, follow_redirects=False) assert r.status_code == 302 assert "/login" in r.headers["location"] # ── Helpers de signature ────────────────────────────────────────────────────── def test_sign_and_verify_roundtrip(): sig = main._sign_user_id("alice") assert main._verify_signed_cookie(sig) == "alice" def test_verify_rejects_malformed_inputs(): for bad in ("", "alice", "alice.", ".", "alice.deadbeef", "inva/lid.xxx"): assert main._verify_signed_cookie(bad) is None def test_secret_is_stable_across_calls(): s1 = models.load_or_create_secret() s2 = models.load_or_create_secret() assert s1 == s2