Compare commits

...
Author SHA1 Message Date
bruno 3c4bbbf2f1 Tests CORS alertes : origine dev autorisee en preflight
CI / test (push) Failing after 1s
CI / test (pull_request) Failing after 0s
2026-10-28 13:45:35 -04:00
bruno 7556cd0bee Tests CORS recherche : prod+dev autorises, tiers refuse
CI / test (push) Failing after 1s
2026-10-28 13:45:33 -04:00
bruno aa98eb6866 CORS alertes (fusion #69) : allowlist /api/events prod+dev
CI / test (push) Failing after 0s
2026-10-28 13:45:30 -04:00
bruno 409e67d57b CORS recherche/alertes (fusion #68/#69) : allowlist soeurs prod+dev explicites
CI / test (push) Failing after 3m51s
2026-10-28 13:45:27 -04:00
4 changed files with 46 additions and 8 deletions
+8 -2
View File
@@ -438,10 +438,16 @@ class DracoAlertesCorsMiddleware(BaseHTTPMiddleware):
if request.url.path != "/api/events":
return await call_next(request)
from app.config import resoudre_urls_draco
from app.recherche import DEV_ORIGINS, PROD_ORIGINS, origin_of
urls = resoudre_urls_draco(request.headers.get("host"))
soeurs = {urls.sso_url, urls.flowdeck_url, urls.tiktak_url,
urls.public_url}
# Correctif CORS alertes (fusion #69, 2026-10-28) : comme
# pour /api/search, les origines sœurs prod ET dev sont
# listées explicitement — l'ancienne allowlist (environnement
# résolu seul) refusait les pages de l'autre environnement.
soeurs = ({origin_of(u) for u in (urls.sso_url, urls.flowdeck_url,
urls.tiktak_url, urls.public_url)}
| set(PROD_ORIGINS) | set(DEV_ORIGINS)) - {None}
origine = request.headers.get("origin", "")
if origine not in soeurs:
return await call_next(request)
+12 -4
View File
@@ -9,9 +9,14 @@ testables sans base ni serveur.
recherche Gitea de la page ``/search`` (titres + corps, tous états,
tous projets du workspace) et la regroupe par dépôt.
- La CORS bornée n'autorise que ``GET /api/search``, et seulement
pour les origines sœurs résolues par ``app.config`` (explicite >
hôte de la requête > dérivation > défaut officiel — donc ``-dev``
reste ``-dev``), jamais ``*``. Le middleware est un ASGI pur :
pour les origines sœurs officielles **prod ET dev, listées
explicitement** (``PROD_ORIGINS | DEV_ORIGINS``), **plus** les
origines résolues par ``app.config`` — jamais ``*``. Correctif
2026-10-28 (fusion #68/#69, matrice constatée par Bruno) :
l'ancienne allowlist ne contenait que l'environnement résolu
(prod OU dev), donc une page de l'autre environnement voyait sa
section afficher « Injoignable depuis cet hôte (CORS ou réseau) ».
Le middleware est un ASGI pur :
il répond lui-même aux preflights OPTIONS (qui ne portent jamais
de cookies, donc qu'``AuthMiddleware`` refuserait sinon) et ajoute
les en-têtes aux réponses de recherche, sans toucher aux autres
@@ -97,7 +102,10 @@ def sister_origins(
origin = origin_of((resolved or {}).get(key))
if origin:
origins.add(origin)
return frozenset(origins)
# Correctif 2026-10-28 : prod ET dev toujours, quelle que soit
# la résolution — ``resolved`` n'ajoute que des origines de
# configuration sans jamais retirer une origine officielle.
return frozenset(origins) | PROD_ORIGINS | DEV_ORIGINS
def cors_headers_for(
+7
View File
@@ -44,3 +44,10 @@ def test_cors_events_soeur_preflight_get_et_tiers(client):
headers={"Origin": "https://evil.example",
"Access-Control-Request-Method": "GET"})
assert "access-control-allow-origin" not in r3.headers
# Correctif 2026-10-28 : une origine sœur dev passe aussi.
r4 = client.options("/api/events",
headers={"Origin": "https://tiktak-dev.dracodev.net",
"Access-Control-Request-Method": "GET"})
assert r4.status_code == 200
assert r4.headers.get("access-control-allow-origin") == "https://tiktak-dev.dracodev.net"
assert r4.headers.get("access-control-allow-credentials") == "true"
+19 -2
View File
@@ -34,7 +34,13 @@ def test_origin_of():
def test_sister_origins_dev():
# Correctif 2026-10-28 : 8 origines officielles, prod ET dev,
# quelle que soit la résolution.
assert sister_origins(RESOLVED_DEV) == {
"https://og.dracodev.net",
"https://flowdeck.dracodev.net",
"https://tiktak.dracodev.net",
"https://kanbante.dracodev.net",
"https://og-dev.dracodev.net",
"https://flowdeck-dev.dracodev.net",
"https://tiktak-dev.dracodev.net",
@@ -47,7 +53,11 @@ def test_cors_headers():
assert headers["Access-Control-Allow-Origin"] == "https://tiktak-dev.dracodev.net"
assert headers["Access-Control-Allow-Credentials"] == "true"
assert cors_headers_for("https://evil.example", RESOLVED_DEV) is None
assert cors_headers_for("https://og.dracodev.net", RESOLVED_DEV) is None
assert cors_headers_for("https://og-dev.dracodev.net.evil.example",
RESOLVED_DEV) is None
# Autre environnement autorisé (correctif 2026-10-28).
assert cors_headers_for("https://og.dracodev.net",
RESOLVED_DEV) is not None
def test_preflight_headers():
@@ -86,7 +96,14 @@ def test_groupes_vers_json_vide():
def test_sister_origins_hote_requete():
# Configuration muette + hôte dev : le repli inclut les -dev.
# Correctif 2026-10-28 : configuration muette, quel que soit
# l'hôte de la requête, prod ET dev passent.
assert "https://og-dev.dracodev.net" in sister_origins(
{}, request_host="kanbante-dev.dracodev.net"
)
assert "https://og.dracodev.net" in sister_origins(
{}, request_host="kanbante-dev.dracodev.net"
)
assert "https://og-dev.dracodev.net" in sister_origins(
{}, request_host="kanbante.dracodev.net"
)