227 lines
9.0 KiB
Python
227 lines
9.0 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from contextlib import contextmanager
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from flask import Flask, g, jsonify
|
||
|
|
|
||
|
|
from atlas_portal.routes import admin_access as admin
|
||
|
|
|
||
|
|
|
||
|
|
class DummyResult:
|
||
|
|
def __init__(self, row: dict[str, Any] | None = None, rows: list[dict[str, Any]] | None = None) -> None:
|
||
|
|
self.row = row
|
||
|
|
self.rows = rows or []
|
||
|
|
|
||
|
|
def fetchone(self) -> dict[str, Any] | None:
|
||
|
|
return self.row
|
||
|
|
|
||
|
|
def fetchall(self) -> list[dict[str, Any]]:
|
||
|
|
return self.rows
|
||
|
|
|
||
|
|
|
||
|
|
class DummyConn:
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
*,
|
||
|
|
row: dict[str, Any] | None = None,
|
||
|
|
rows: list[dict[str, Any]] | None = None,
|
||
|
|
fail: bool = False,
|
||
|
|
) -> None:
|
||
|
|
self.row = row
|
||
|
|
self.rows = rows or []
|
||
|
|
self.fail = fail
|
||
|
|
self.executed: list[tuple[str, object | None]] = []
|
||
|
|
|
||
|
|
def execute(self, query: str, params: object | None = None) -> DummyResult:
|
||
|
|
self.executed.append((query, params))
|
||
|
|
if self.fail:
|
||
|
|
raise RuntimeError("database failed")
|
||
|
|
return DummyResult(row=self.row, rows=self.rows)
|
||
|
|
|
||
|
|
|
||
|
|
class DummyAriadne:
|
||
|
|
def __init__(self, enabled: bool = False) -> None:
|
||
|
|
self._enabled = enabled
|
||
|
|
self.calls: list[tuple[str, str, object | None]] = []
|
||
|
|
|
||
|
|
def enabled(self) -> bool:
|
||
|
|
return self._enabled
|
||
|
|
|
||
|
|
def proxy(self, method: str, path: str, payload: object | None = None):
|
||
|
|
self.calls.append((method, path, payload))
|
||
|
|
return jsonify({"proxied": True, "method": method, "path": path, "payload": payload})
|
||
|
|
|
||
|
|
|
||
|
|
class DummyAdmin:
|
||
|
|
def __init__(self, *, ready: bool = True, groups: list[str] | None = None, fail: bool = False) -> None:
|
||
|
|
self._ready = ready
|
||
|
|
self.groups = groups or []
|
||
|
|
self.fail = fail
|
||
|
|
|
||
|
|
def ready(self) -> bool:
|
||
|
|
return self._ready
|
||
|
|
|
||
|
|
def list_group_names(self) -> list[str]:
|
||
|
|
if self.fail:
|
||
|
|
raise RuntimeError("keycloak failed")
|
||
|
|
return self.groups
|
||
|
|
|
||
|
|
|
||
|
|
def make_client(monkeypatch, *, conn: DummyConn | None = None, ariadne: DummyAriadne | None = None, is_admin: bool = True):
|
||
|
|
app = Flask(__name__)
|
||
|
|
app.secret_key = "test"
|
||
|
|
active_conn = conn or DummyConn()
|
||
|
|
active_ariadne = ariadne or DummyAriadne()
|
||
|
|
|
||
|
|
monkeypatch.setattr(admin, "require_auth", lambda fn: fn)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
admin,
|
||
|
|
"require_portal_admin",
|
||
|
|
lambda: (True, None) if is_admin else (False, (jsonify({"error": "forbidden"}), 403)),
|
||
|
|
)
|
||
|
|
monkeypatch.setattr(admin, "configured", lambda: True)
|
||
|
|
monkeypatch.setattr(admin, "ariadne_client", active_ariadne)
|
||
|
|
monkeypatch.setattr(admin, "admin_client", lambda: DummyAdmin(groups=["dev", "admin", "quality"]))
|
||
|
|
|
||
|
|
@contextmanager
|
||
|
|
def connect():
|
||
|
|
yield active_conn
|
||
|
|
|
||
|
|
monkeypatch.setattr(admin, "connect", connect)
|
||
|
|
|
||
|
|
@app.before_request
|
||
|
|
def set_user() -> None:
|
||
|
|
g.keycloak_username = "brad"
|
||
|
|
|
||
|
|
admin.register(app)
|
||
|
|
return app.test_client(), active_conn, active_ariadne
|
||
|
|
|
||
|
|
|
||
|
|
def test_admin_list_requests_preflight_proxy_and_database_paths(monkeypatch) -> None:
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, is_admin=False)
|
||
|
|
assert client.get("/api/admin/access/requests").status_code == 403
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch)
|
||
|
|
monkeypatch.setattr(admin, "configured", lambda: False)
|
||
|
|
assert client.get("/api/admin/access/requests").status_code == 503
|
||
|
|
|
||
|
|
ariadne = DummyAriadne(enabled=True)
|
||
|
|
client, _conn, proxied = make_client(monkeypatch, ariadne=ariadne)
|
||
|
|
assert client.get("/api/admin/access/requests").get_json()["proxied"] is True
|
||
|
|
assert proxied.calls == [("GET", "/api/admin/access/requests", None)]
|
||
|
|
|
||
|
|
now = datetime(2026, 4, 20, tzinfo=timezone.utc)
|
||
|
|
conn = DummyConn(
|
||
|
|
rows=[
|
||
|
|
{
|
||
|
|
"request_code": "alice~CODE",
|
||
|
|
"username": "alice",
|
||
|
|
"contact_email": "alice@example.dev",
|
||
|
|
"first_name": "Alice",
|
||
|
|
"last_name": "Atlas",
|
||
|
|
"created_at": now,
|
||
|
|
"note": "please",
|
||
|
|
}
|
||
|
|
]
|
||
|
|
)
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, conn=conn)
|
||
|
|
data = client.get("/api/admin/access/requests").get_json()
|
||
|
|
assert data["requests"][0]["id"] == "alice~CODE"
|
||
|
|
assert data["requests"][0]["created_at"].startswith("2026-04-20T00:00:00")
|
||
|
|
|
||
|
|
broken = DummyConn(fail=True)
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, conn=broken)
|
||
|
|
assert client.get("/api/admin/access/requests").status_code == 502
|
||
|
|
|
||
|
|
|
||
|
|
def test_admin_flags_paths(monkeypatch) -> None:
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, is_admin=False)
|
||
|
|
assert client.get("/api/admin/access/flags").status_code == 403
|
||
|
|
|
||
|
|
ariadne = DummyAriadne(enabled=True)
|
||
|
|
client, _conn, proxied = make_client(monkeypatch, ariadne=ariadne)
|
||
|
|
assert client.get("/api/admin/access/flags").get_json()["proxied"] is True
|
||
|
|
assert proxied.calls == [("GET", "/api/admin/access/flags", None)]
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch)
|
||
|
|
monkeypatch.setattr(admin, "admin_client", lambda: DummyAdmin(ready=False))
|
||
|
|
assert client.get("/api/admin/access/flags").status_code == 503
|
||
|
|
|
||
|
|
monkeypatch.setattr(admin, "admin_client", lambda: DummyAdmin(groups=["dev", "admin", "quality"], fail=True))
|
||
|
|
assert client.get("/api/admin/access/flags").status_code == 502
|
||
|
|
|
||
|
|
monkeypatch.setattr(admin.settings, "PORTAL_ADMIN_GROUPS", {"admin"})
|
||
|
|
monkeypatch.setattr(admin, "admin_client", lambda: DummyAdmin(groups=["dev", "admin", "quality"]))
|
||
|
|
assert client.get("/api/admin/access/flags").get_json() == {"flags": ["dev", "quality"]}
|
||
|
|
|
||
|
|
|
||
|
|
def test_admin_approve_paths(monkeypatch) -> None:
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, is_admin=False)
|
||
|
|
assert client.post("/api/admin/access/requests/alice/approve", json={}).status_code == 403
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch)
|
||
|
|
monkeypatch.setattr(admin, "configured", lambda: False)
|
||
|
|
assert client.post("/api/admin/access/requests/alice/approve", json={}).status_code == 503
|
||
|
|
|
||
|
|
ariadne = DummyAriadne(enabled=True)
|
||
|
|
client, _conn, proxied = make_client(monkeypatch, ariadne=ariadne)
|
||
|
|
assert client.post("/api/admin/access/requests/alice space/approve", json={"flags": ["dev"]}).get_json()["proxied"]
|
||
|
|
assert proxied.calls == [
|
||
|
|
("POST", "/api/admin/access/requests/alice%20space/approve", {"flags": ["dev"]})
|
||
|
|
]
|
||
|
|
|
||
|
|
conn = DummyConn(row={"request_code": "alice~CODE"})
|
||
|
|
provisioned: list[str] = []
|
||
|
|
monkeypatch.setattr(admin, "provision_access_request", lambda code: provisioned.append(code))
|
||
|
|
client, active_conn, _ariadne = make_client(monkeypatch, conn=conn)
|
||
|
|
response = client.post("/api/admin/access/requests/alice/approve", json={"flags": ["dev", 7], "note": " ok "})
|
||
|
|
assert response.get_json() == {"ok": True, "request_code": "alice~CODE"}
|
||
|
|
assert provisioned == ["alice~CODE"]
|
||
|
|
assert active_conn.executed[0][1] == ("brad", ["dev"], "ok", "alice")
|
||
|
|
|
||
|
|
monkeypatch.setattr(admin, "provision_access_request", lambda code: (_ for _ in ()).throw(RuntimeError("boom")))
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, conn=conn)
|
||
|
|
assert client.post("/api/admin/access/requests/alice/approve", json={}).status_code == 200
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, conn=DummyConn(row=None))
|
||
|
|
assert client.post("/api/admin/access/requests/alice/approve", json={}).get_json() == {
|
||
|
|
"ok": True,
|
||
|
|
"request_code": "",
|
||
|
|
}
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, conn=DummyConn(fail=True))
|
||
|
|
assert client.post("/api/admin/access/requests/alice/approve", json={}).status_code == 502
|
||
|
|
|
||
|
|
|
||
|
|
def test_admin_deny_paths(monkeypatch) -> None:
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, is_admin=False)
|
||
|
|
assert client.post("/api/admin/access/requests/alice/deny", json={}).status_code == 403
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch)
|
||
|
|
monkeypatch.setattr(admin, "configured", lambda: False)
|
||
|
|
assert client.post("/api/admin/access/requests/alice/deny", json={}).status_code == 503
|
||
|
|
|
||
|
|
ariadne = DummyAriadne(enabled=True)
|
||
|
|
client, _conn, proxied = make_client(monkeypatch, ariadne=ariadne)
|
||
|
|
assert client.post("/api/admin/access/requests/alice space/deny", json={"note": "no"}).get_json()["proxied"]
|
||
|
|
assert proxied.calls == [("POST", "/api/admin/access/requests/alice%20space/deny", {"note": "no"})]
|
||
|
|
|
||
|
|
conn = DummyConn(row={"request_code": "alice~DENY"})
|
||
|
|
client, active_conn, _ariadne = make_client(monkeypatch, conn=conn)
|
||
|
|
assert client.post("/api/admin/access/requests/alice/deny", json={"note": " no "}).get_json() == {
|
||
|
|
"ok": True,
|
||
|
|
"request_code": "alice~DENY",
|
||
|
|
}
|
||
|
|
assert active_conn.executed[0][1] == ("brad", "no", "alice")
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, conn=DummyConn(row=None))
|
||
|
|
assert client.post("/api/admin/access/requests/alice/deny", json={}).get_json() == {
|
||
|
|
"ok": True,
|
||
|
|
"request_code": "",
|
||
|
|
}
|
||
|
|
|
||
|
|
client, _conn, _ariadne = make_client(monkeypatch, conn=DummyConn(fail=True))
|
||
|
|
assert client.post("/api/admin/access/requests/alice/deny", json={}).status_code == 502
|