feat: Fusion — barre d'apps commune (#28) et pont SSO draco_session (#27)
CI / test (push) Successful in 1m37s
CI / test (push) Successful in 1m37s
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
"""Fusion ACC-1 — barre d'apps commune (carte kanbante#28).
|
||||
|
||||
Le snippet figé de dracodev-commons est vendangé (CSS/JS statiques +
|
||||
partiel _draco_bar.html) et inclus par le cadre partagé _header.html :
|
||||
toutes les pages applicatives l'affichent avec Kanbante comme app
|
||||
courante ; l'écran de connexion, autonome, ne l'affiche pas.
|
||||
"""
|
||||
|
||||
|
||||
def test_barre_presente_sur_le_dashboard(client):
|
||||
r = client.get("/")
|
||||
assert r.status_code == 200
|
||||
assert 'class="draco-bar"' in r.text
|
||||
assert 'data-current="kanbante"' in r.text
|
||||
assert "draco-barre.css" in r.text
|
||||
assert "draco-barre.js" in r.text
|
||||
assert 'window.DRACO_BAR_CONFIG' in r.text
|
||||
# Les quatre applications de la barre, aux URL publiques.
|
||||
for url in ("https://og.dracodev.net/", "https://flowdeck.dracodev.net/",
|
||||
"https://tiktak.dracodev.net/", "https://kanbante.dracodev.net/"):
|
||||
assert url in r.text
|
||||
|
||||
|
||||
def test_statiques_de_la_barre_servis(client):
|
||||
assert client.get("/static/css/draco-barre.css").status_code == 200
|
||||
assert client.get("/static/js/draco-barre.js").status_code == 200
|
||||
|
||||
|
||||
def test_pas_de_barre_sur_le_login(monkeypatch, fake_gitea):
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import app.main as main
|
||||
|
||||
monkeypatch.setenv("KANBANTE_AUTH_USER", "bruno")
|
||||
monkeypatch.setenv("KANBANTE_AUTH_PASSWORD", "motdepasse")
|
||||
with TestClient(main.app) as client:
|
||||
r = client.get("/login")
|
||||
assert r.status_code == 200
|
||||
assert "draco-bar" not in r.text
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Fusion ACC-3.2 — pont SSO draco_session (carte kanbante#27).
|
||||
|
||||
Le cookie émis par ObsiGate (ACC-3.1) est vérifié localement
|
||||
(secret partagé, HS256) et converti en session Kanbante native par
|
||||
GET /auth/draco. Kanbante est mono-utilisateur : un jeton valide
|
||||
ouvre la session de l'opérateur configuré. Sans
|
||||
DRACO_SESSION_SECRET, le pont renvoie simplement au login.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import app.main as main
|
||||
from app import draco
|
||||
|
||||
SECRET = "secret-partage-de-test"
|
||||
|
||||
|
||||
def _jeton(sub="admin-1", secret=SECRET, exp=None, jti="jti-test"):
|
||||
def b64(d: bytes) -> str:
|
||||
return base64.urlsafe_b64encode(d).rstrip(b"=").decode()
|
||||
|
||||
header = b64(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
|
||||
payload = b64(json.dumps({
|
||||
"sub": sub, "name": "Bruno", "iat": int(time.time()),
|
||||
"exp": exp if exp is not None else int(time.time()) + 3600,
|
||||
"jti": jti,
|
||||
}).encode())
|
||||
sig = hmac.new(secret.encode(), f"{header}.{payload}".encode(),
|
||||
hashlib.sha256).digest()
|
||||
return f"{header}.{payload}.{b64(sig)}"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client_sso(monkeypatch, fake_gitea):
|
||||
monkeypatch.setenv("KANBANTE_AUTH_USER", "bruno")
|
||||
monkeypatch.setenv("KANBANTE_AUTH_PASSWORD", "motdepasse")
|
||||
monkeypatch.setenv("DRACO_SESSION_SECRET", SECRET)
|
||||
monkeypatch.setattr(draco, "_refresh_revocations", lambda: None)
|
||||
draco._revoked_cache.clear()
|
||||
with TestClient(main.app) as client:
|
||||
yield client
|
||||
draco._revoked_cache.clear()
|
||||
|
||||
|
||||
def test_verify_token(monkeypatch):
|
||||
monkeypatch.setenv("DRACO_SESSION_SECRET", SECRET)
|
||||
assert draco.verify_token(_jeton())["sub"] == "admin-1"
|
||||
assert draco.verify_token(_jeton(secret="mauvais")) is None
|
||||
assert draco.verify_token(_jeton(exp=int(time.time()) - 5)) is None
|
||||
assert draco.verify_token("pas.un.jeton") is None
|
||||
|
||||
|
||||
def test_pont_sans_cookie_vers_login(client_sso):
|
||||
r = client_sso.get("/auth/draco", follow_redirects=False)
|
||||
assert r.status_code == 303
|
||||
assert r.headers["location"] == "/login"
|
||||
|
||||
|
||||
def test_pont_cookie_valide_ouvre_la_session(client_sso):
|
||||
client_sso.cookies.set("draco_session", _jeton())
|
||||
r = client_sso.get("/auth/draco", follow_redirects=False)
|
||||
assert r.status_code == 303
|
||||
assert r.headers["location"] == "/"
|
||||
assert "kanbante_session" in r.headers.get("set-cookie", "")
|
||||
# La session native créée par le pont donne accès à l'app.
|
||||
assert client_sso.get("/").status_code == 200
|
||||
|
||||
|
||||
def test_pont_cookie_falsifie_vers_login(client_sso):
|
||||
client_sso.cookies.set("draco_session", _jeton(secret="mauvais"))
|
||||
r = client_sso.get("/auth/draco", follow_redirects=False)
|
||||
assert r.headers["location"] == "/login"
|
||||
|
||||
|
||||
def test_pont_jeton_revoque_refuse(client_sso):
|
||||
draco._revoked_cache["jti-revoque"] = int(time.time()) + 3600
|
||||
client_sso.cookies.set("draco_session", _jeton(jti="jti-revoque"))
|
||||
r = client_sso.get("/auth/draco", follow_redirects=False)
|
||||
assert r.headers["location"] == "/login"
|
||||
|
||||
|
||||
def test_pont_inerte_sans_secret(monkeypatch, fake_gitea):
|
||||
monkeypatch.setenv("KANBANTE_AUTH_USER", "bruno")
|
||||
monkeypatch.setenv("KANBANTE_AUTH_PASSWORD", "motdepasse")
|
||||
monkeypatch.delenv("DRACO_SESSION_SECRET", raising=False)
|
||||
with TestClient(main.app) as client:
|
||||
client.cookies.set("draco_session", _jeton())
|
||||
r = client.get("/auth/draco", follow_redirects=False)
|
||||
assert r.headers["location"] == "/login"
|
||||
|
||||
|
||||
def test_bouton_sso_sur_la_page_login(client_sso):
|
||||
r = client_sso.get("/login")
|
||||
assert r.status_code == 200
|
||||
assert "og.dracodev.net/sso/authorize" in r.text
|
||||
assert "kanbante.dracodev.net%2Fauth%2Fdraco" in r.text
|
||||
Reference in New Issue
Block a user