fix(draco): résolution dev/prod par l'hôte de la requête (7.82.4)
Le mode console du panneau restait une page morte sur flowdeck-dev : CSP frame-src/connect-src et DRACO_PANNEAU_CONFIG.agent_url pointaient l'agent prod (pas encore installée). Deux trous de la 7.82.3 : agent_url() sans aucune dérivation, et aucune résolution lisant l'hôte de la requête. Pour tout ce qui est rendu à une requête (barre/DRACO_URLS, iframe du panneau, CSP, boutons et redirections SSO) : explicite > hôte public de la requête (motif fermé ^(og|flowdeck|tiktak|kanbante|agent)(SUFFIXE).dracodev.net$, suffixe appliqué aux sœurs et à l'agent) > dérivation par les adresses de l'agent connues en config (élargie : agent_url() se dérive de l'URL d'événements explicite) > défaut officiel. L'hôte est posé par le middleware CSP dans un ContextVar (patron du nonce CSP) : iframe et CSP de la même réponse consomment la même origine résolue. Contextes sans requête inchangés sinon. Tests : +12 dans test_draco_urls_devprod.py (25/25) — Host dev sans aucun env → tout en -dev ; Host prod → officiels ; Host LAN → repli config/défauts ; explicite gagne sur l'hôte ; motif fermé. Suite complète : 4 failed / 26 errors identiques sur arbre propre (familles préexistantes MCP/realtime/my_tasks/gallery/sync).
This commit is contained in:
@@ -188,3 +188,185 @@ def test_bouton_sso_login_depuis_config(monkeypatch):
|
||||
assert _sso_login_url() == (
|
||||
"https://og-dev.dracodev.net/sso/authorize?return_to="
|
||||
"https%3A%2F%2Fflowdeck-dev.dracodev.net%2Fauth%2Fdraco")
|
||||
|
||||
|
||||
# ── v7.82.4 — résolution par l'hôte de la requête ─────────────────────
|
||||
# Correctif du mode console mort sur flowdeck-dev : l'agent n'avait
|
||||
# aucune dérivation (v7.82.3) et rien ne lisait l'hôte de la requête.
|
||||
# Ordre : explicite > hôte public de la requête (motif fermé) >
|
||||
# dérivation par l'agent connu en configuration > officiel.
|
||||
|
||||
@pytest.fixture
|
||||
def avec_hote():
|
||||
"""Pose l'hôte de requête comme le middleware CSP le fait."""
|
||||
jetons = []
|
||||
|
||||
def poser(hote):
|
||||
jetons.append(draco_urls.HOTE_REQUETE.set(hote))
|
||||
|
||||
yield poser
|
||||
for jeton in reversed(jetons):
|
||||
draco_urls.HOTE_REQUETE.reset(jeton)
|
||||
|
||||
|
||||
def test_agent_url_derivee_depuis_evenements_seul(monkeypatch):
|
||||
# Trou n° 1 de la v7.82.3 : seul AGENT_EVENEMENTS_URL posé (cas
|
||||
# réel de la dev) → l'agent lui-même suit en ``-dev``, pas
|
||||
# seulement les sœurs.
|
||||
monkeypatch.setenv(
|
||||
"AGENT_EVENEMENTS_URL",
|
||||
"https://agent-dev.dracodev.net/api/v1/evenements")
|
||||
assert draco_urls.agent_url() == "https://agent-dev.dracodev.net"
|
||||
assert draco_urls.agent_evenements_url() == \
|
||||
"https://agent-dev.dracodev.net/api/v1/evenements"
|
||||
|
||||
|
||||
def test_hote_requete_dev_toutes_urls_sans_env(avec_hote):
|
||||
# Aucune variable d'environnement (fixture _env_propre) : l'hôte
|
||||
# public suffit — sœurs ET agent en ``-dev``, Gitea inchangée.
|
||||
avec_hote("flowdeck-dev.dracodev.net")
|
||||
urls = draco_urls.toutes()
|
||||
assert urls["obsigate"] == "https://og-dev.dracodev.net"
|
||||
assert urls["agent"] == "https://agent-dev.dracodev.net"
|
||||
assert urls["tiktak"] == "https://tiktak-dev.dracodev.net"
|
||||
assert urls["kanbante"] == "https://kanbante-dev.dracodev.net"
|
||||
assert urls["flowdeck"] == "https://flowdeck-dev.dracodev.net"
|
||||
assert urls["gitea"] == "https://git.dracodev.net"
|
||||
|
||||
|
||||
def test_hote_requete_quelconque_des_cinq_slugs(avec_hote):
|
||||
# L'environnement est celui de l'installation, pas de l'app
|
||||
# demandée : un hôte ``agent-dev`` ou ``og-dev`` vaut ``-dev``
|
||||
# pour toutes, port éventuel ignoré.
|
||||
for hote in ("agent-dev.dracodev.net", "og-dev.dracodev.net:443",
|
||||
"tiktak-dev.dracodev.net", "kanbante-dev.dracodev.net"):
|
||||
avec_hote(hote)
|
||||
assert draco_urls.agent_url() == "https://agent-dev.dracodev.net", hote
|
||||
assert draco_urls.obsigate_url() == "https://og-dev.dracodev.net", hote
|
||||
|
||||
|
||||
def test_hote_requete_prod_noms_officiels(avec_hote):
|
||||
avec_hote("flowdeck.dracodev.net")
|
||||
urls = draco_urls.toutes()
|
||||
assert urls["obsigate"] == "https://og.dracodev.net"
|
||||
assert urls["agent"] == "https://agent.dracodev.net"
|
||||
assert urls["flowdeck"] == "https://flowdeck.dracodev.net"
|
||||
assert all("-dev" not in u for u in urls.values())
|
||||
|
||||
|
||||
def test_hote_requete_motif_ferme_aucune_derivation(avec_hote):
|
||||
# Jamais de suffixe inventé depuis un hôte arbitraire.
|
||||
for hote in ("evil-dev.dracodev.net",
|
||||
"flowdeck-dev.dracodev.net.evil.com",
|
||||
"monflowdeck-dev.dracodev.net",
|
||||
"flowdeckdev.dracodev.net",
|
||||
"flowdeck-dev.example.net"):
|
||||
avec_hote(hote)
|
||||
urls = draco_urls.toutes()
|
||||
assert urls["agent"] == "https://agent.dracodev.net", hote
|
||||
assert urls["obsigate"] == "https://og.dracodev.net", hote
|
||||
assert urls["flowdeck"] == "https://flowdeck.dracodev.net", hote
|
||||
|
||||
|
||||
def test_hote_requete_lan_repli_configuration(avec_hote, monkeypatch):
|
||||
# Hors motif (LAN, IP) : repli configuration/défauts, comme
|
||||
# hors requête — ici la dérivation par AGENT_EVENEMENTS_URL.
|
||||
avec_hote("192.168.30.101:8080")
|
||||
urls = draco_urls.toutes()
|
||||
assert urls["agent"] == "https://agent.dracodev.net"
|
||||
assert urls["obsigate"] == "https://og.dracodev.net"
|
||||
monkeypatch.setenv(
|
||||
"AGENT_EVENEMENTS_URL",
|
||||
"https://agent-dev.dracodev.net/api/v1/evenements")
|
||||
urls = draco_urls.toutes()
|
||||
assert urls["agent"] == "https://agent-dev.dracodev.net"
|
||||
assert urls["obsigate"] == "https://og-dev.dracodev.net"
|
||||
assert urls["tiktak"] == "https://tiktak-dev.dracodev.net"
|
||||
|
||||
|
||||
def test_hote_requete_explicite_gagne_sur_hote(avec_hote, monkeypatch):
|
||||
avec_hote("flowdeck-dev.dracodev.net")
|
||||
# Explicites prioritaires, y compris quand elles pointent le
|
||||
# nom officiel ou un domaine propre.
|
||||
monkeypatch.setenv("DRACO_AGENT_URL", "https://agent.dracodev.net")
|
||||
monkeypatch.setenv("DRACO_SSO_URL", "https://og.example.net")
|
||||
urls = draco_urls.toutes()
|
||||
assert urls["agent"] == "https://agent.dracodev.net"
|
||||
assert urls["obsigate"] == "https://og.example.net"
|
||||
# Non déclarées : l'hôte de la requête fait foi.
|
||||
assert urls["tiktak"] == "https://tiktak-dev.dracodev.net"
|
||||
assert urls["kanbante"] == "https://kanbante-dev.dracodev.net"
|
||||
assert urls["flowdeck"] == "https://flowdeck-dev.dracodev.net"
|
||||
|
||||
|
||||
def test_hote_requete_bouton_sso_tout_dev(avec_hote):
|
||||
from app.routers.auth import _sso_login_url
|
||||
|
||||
avec_hote("flowdeck-dev.dracodev.net")
|
||||
# Émettrice via DRACO_SSO_URL résolu par l'hôte + callback =
|
||||
# propre origine résolue de l'instance.
|
||||
assert _sso_login_url() == (
|
||||
"https://og-dev.dracodev.net/sso/authorize?return_to="
|
||||
"https%3A%2F%2Fflowdeck-dev.dracodev.net%2Fauth%2Fdraco")
|
||||
|
||||
|
||||
def test_http_dev_sans_env_tout_en_dev(client):
|
||||
# Requête complète, aucune variable d'environnement : barre,
|
||||
# panneau (config de l'iframe console), CSP et SSO en ``-dev``.
|
||||
r = client.get("/workspaces",
|
||||
headers={"Host": "flowdeck-dev.dracodev.net"})
|
||||
assert r.status_code == 200
|
||||
html = r.text
|
||||
# Barre inter-apps (les quatre sœurs).
|
||||
for soeur in ("og-dev", "flowdeck-dev", "tiktak-dev", "kanbante-dev"):
|
||||
assert f'href="https://{soeur}.dracodev.net/"' in html
|
||||
# Panneau : la source de l'iframe console vient de cette config.
|
||||
assert 'agent_url: "https://agent-dev.dracodev.net"' in html
|
||||
# CSP de la MÊME réponse : même origine agent résolue.
|
||||
csp = r.headers.get("content-security-policy", "")
|
||||
assert "frame-src 'self' https://agent-dev.dracodev.net" in csp
|
||||
assert "connect-src 'self' https://agent-dev.dracodev.net" in csp
|
||||
# Aucun nom officiel nu ne subsiste dans la réponse.
|
||||
for officiel in ("https://og.dracodev.net", "https://agent.dracodev.net",
|
||||
"https://tiktak.dracodev.net",
|
||||
"https://kanbante.dracodev.net",
|
||||
"https://flowdeck.dracodev.net"):
|
||||
assert officiel not in html.replace("-dev.dracodev.net", ""), officiel
|
||||
assert officiel not in csp.replace("-dev.dracodev.net", ""), officiel
|
||||
# Bouton SSO de la page de connexion, même requête type.
|
||||
r = client.get("/auth/login?provider=local",
|
||||
headers={"Host": "flowdeck-dev.dracodev.net"})
|
||||
assert ("https://og-dev.dracodev.net/sso/authorize?return_to="
|
||||
"https%3A%2F%2Fflowdeck-dev.dracodev.net%2Fauth%2Fdraco") in r.text
|
||||
|
||||
|
||||
def test_http_prod_noms_officiels(client):
|
||||
r = client.get("/workspaces",
|
||||
headers={"Host": "flowdeck.dracodev.net"})
|
||||
assert r.status_code == 200
|
||||
assert 'agent_url: "https://agent.dracodev.net"' in r.text
|
||||
assert 'href="https://og.dracodev.net/"' in r.text
|
||||
csp = r.headers.get("content-security-policy", "")
|
||||
assert "frame-src 'self' https://agent.dracodev.net" in csp
|
||||
assert "-dev.dracodev.net" not in r.text
|
||||
assert "-dev.dracodev.net" not in csp
|
||||
|
||||
|
||||
def test_http_lan_repli_defauts(client):
|
||||
r = client.get("/workspaces",
|
||||
headers={"Host": "192.168.30.101:8080"})
|
||||
assert r.status_code == 200
|
||||
assert 'agent_url: "https://agent.dracodev.net"' in r.text
|
||||
csp = r.headers.get("content-security-policy", "")
|
||||
assert "frame-src 'self' https://agent.dracodev.net" in csp
|
||||
|
||||
|
||||
def test_http_explicite_gagne_sur_hote(client, monkeypatch):
|
||||
monkeypatch.setenv("DRACO_AGENT_URL", "https://agent.dracodev.net")
|
||||
r = client.get("/workspaces",
|
||||
headers={"Host": "flowdeck-dev.dracodev.net"})
|
||||
assert 'agent_url: "https://agent.dracodev.net"' in r.text
|
||||
csp = r.headers.get("content-security-policy", "")
|
||||
assert "frame-src 'self' https://agent.dracodev.net;" in csp
|
||||
# Le reste suit l'hôte de la requête.
|
||||
assert 'href="https://og-dev.dracodev.net/"' in r.text
|
||||
|
||||
Reference in New Issue
Block a user