75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
"""Tests — cookies Secure, CORS same-origin explicite, avertissement bind (ROADMAP #87 T3/T8).
|
|
|
|
- `is_secure_cookies()` : `OBSIGATE_SECURE_COOKIES=true|false|auto` (défaut
|
|
`auto` : Secure si la requête arrive en https, sinon pas de flag — les
|
|
navigateurs ignorent les cookies `Secure` en clair).
|
|
- CORS same-origin EXPLICITE : `CORSMiddleware(allow_origins=[])` — aucun
|
|
`Access-Control-Allow-*` n'est émis même avec un `Origin` cross-origin,
|
|
et les preflights sont rejetés (400).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
def test_secure_cookies_default_auto(monkeypatch):
|
|
"""Défaut `auto` : sans requête → pas de flag (logins HTTP locaux préservés)."""
|
|
from backend.auth.router import is_secure_cookies
|
|
|
|
monkeypatch.delenv("OBSIGATE_SECURE_COOKIES", raising=False)
|
|
assert is_secure_cookies() is False
|
|
|
|
|
|
def test_secure_cookies_forced_values(monkeypatch):
|
|
"""`true`/`1`/`yes` → flag ; `false`/`0`/`no` → pas de flag (insensible à la casse)."""
|
|
from backend.auth.router import is_secure_cookies
|
|
|
|
for value in ("true", "True", "TRUE", "1", "yes", "on"):
|
|
monkeypatch.setenv("OBSIGATE_SECURE_COOKIES", value)
|
|
assert is_secure_cookies() is True
|
|
for value in ("false", "False", "FALSE", "0", "no", "off"):
|
|
monkeypatch.setenv("OBSIGATE_SECURE_COOKIES", value)
|
|
assert is_secure_cookies() is False
|
|
|
|
|
|
def test_no_cors_headers_on_api(client):
|
|
"""Pas de `Access-Control-Allow-Origin` : same-origin imposé par le navigateur."""
|
|
resp = client.get("/api/health")
|
|
assert resp.status_code == 200
|
|
assert "access-control-allow-origin" not in {k.lower() for k in resp.headers}
|
|
|
|
|
|
def test_no_cors_headers_on_public_share(client):
|
|
"""Idem sur la page publique de partage."""
|
|
resp = client.get("/s/jeton-inexistant")
|
|
assert resp.status_code == 404
|
|
assert "access-control-allow-origin" not in {k.lower() for k in resp.headers}
|
|
|
|
|
|
def test_cross_origin_get_emits_no_acao(client):
|
|
"""#87 T8 : même avec un `Origin` cross-origin, aucun ACAO (refus explicite)."""
|
|
resp = client.get("/api/health", headers={"Origin": "http://evil.example"})
|
|
assert resp.status_code == 200
|
|
assert "access-control-allow-origin" not in {k.lower() for k in resp.headers}
|
|
|
|
|
|
def test_cross_origin_preflight_rejected(client):
|
|
"""#87 T8 : preflight cross-origin → 400 (origine non autorisée)."""
|
|
resp = client.options(
|
|
"/api/health",
|
|
headers={
|
|
"Origin": "http://evil.example",
|
|
"Access-Control-Request-Method": "GET",
|
|
},
|
|
)
|
|
assert resp.status_code == 400
|
|
|
|
|
|
def test_security_headers_present(client):
|
|
"""En-têtes de durcissement posés par le middleware (non-régression)."""
|
|
resp = client.get("/api/health")
|
|
assert resp.headers.get("x-content-type-options") == "nosniff"
|
|
assert resp.headers.get("x-frame-options") == "SAMEORIGIN"
|
|
csp = resp.headers.get("content-security-policy", "")
|
|
assert "object-src 'none'" in csp
|
|
assert "frame-ancestors 'self'" in csp
|