104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
"""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
|