feat(dashboard): drop token auth, trust oauth2-proxy X-Forwarded-User

This commit is contained in:
Claude
2026-08-22 18:03:33 +00:00
parent c9809bce9b
commit 99255bb167
4 changed files with 141 additions and 203 deletions
+33 -29
View File
@@ -2,7 +2,7 @@
We mock the Gitea HTTP layer (urllib.request.urlopen) so the test never
touches the network. The dashboard handler is responsible for:
* auth + CSRF
* auth (X-Forwarded-User set by oauth2-proxy) + CSRF
* read .pr-review.json via GET (404 → start from {})
* validate model against cost_model.PRICES
* PUT the updated file back, with sha + base64 content
@@ -35,12 +35,12 @@ def _free_port() -> int:
return port
def _post(port: int, path: str, body: bytes, *, cookie: str | None = None) -> tuple[int, dict, bytes]:
def _post(port: int, path: str, body: bytes, *, headers: dict | None = None) -> tuple[int, dict, bytes]:
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
headers = {"Content-Type": "application/x-www-form-urlencoded"}
if cookie:
headers["Cookie"] = cookie
conn.request("POST", path, body=body, headers=headers)
hdrs = {"Content-Type": "application/x-www-form-urlencoded"}
if headers:
hdrs.update(headers)
conn.request("POST", path, body=body, headers=hdrs)
r = conn.getresponse()
body_b = r.read()
h = dict(r.getheaders())
@@ -68,7 +68,6 @@ class TestDashboardEdit(unittest.TestCase):
self.tmp = tempfile.TemporaryDirectory()
self.db = f"{self.tmp.name}/f.db"
os.environ["PRAGENT_FEEDBACK_DB"] = self.db
os.environ["PRAGENT_DASHBOARD_TOKEN"] = "secret-edit-token"
os.environ["PRAGENT_BOT_TOKEN"] = "bot-token"
# Seed a row so the repo page is meaningful.
conn = feedback.init(self.db)
@@ -82,7 +81,10 @@ class TestDashboardEdit(unittest.TestCase):
self.srv = ThreadingHTTPServer(("127.0.0.1", self.port), dash.Handler)
self.thread = threading.Thread(target=self.srv.serve_forever, daemon=True)
self.thread.start()
self.cookie = "pragent_dash=secret-edit-token"
# Pull the per-process CSRF secret from the rendered repo page — the
# edit form embeds the same token as a hidden input.
self.csrf = dash._CSRF_SECRET
self.auth_hdr = {"X-Forwarded-User": "marcos@example.com"}
# Records of HTTP calls made by the handler.
self.calls: list[tuple[str, str, dict | None, bytes | None]] = []
@@ -91,7 +93,7 @@ class TestDashboardEdit(unittest.TestCase):
self.srv.shutdown()
self.srv.server_close()
self.thread.join(timeout=2)
for k in ("PRAGENT_FEEDBACK_DB", "PRAGENT_DASHBOARD_TOKEN", "PRAGENT_BOT_TOKEN"):
for k in ("PRAGENT_FEEDBACK_DB", "PRAGENT_BOT_TOKEN"):
os.environ.pop(k, None)
self.tmp.cleanup()
@@ -114,12 +116,12 @@ class TestDashboardEdit(unittest.TestCase):
def test_edit_updates_static_message_and_model(self):
form = (
b"_csrf=secret-edit-token"
b"&static_message=Hello%20world"
b"&model=claude-sonnet-5"
)
f"_csrf={self.csrf}"
f"&static_message=Hello%20world"
f"&model=claude-sonnet-5"
).encode()
with patch.object(dash.urllib.request, "urlopen", side_effect=self._urlopen):
status, h, _b = _post(self.port, "/r/o/r/edit", form, cookie=self.cookie)
status, h, _b = _post(self.port, "/r/o/r/edit", form, headers=self.auth_hdr)
self.assertEqual(status, 302)
self.assertEqual(h.get("Location"), "/r/o/r")
@@ -139,24 +141,24 @@ class TestDashboardEdit(unittest.TestCase):
def test_edit_strips_static_message_to_400(self):
long_msg = "x" * 600
form = (
f"_csrf=secret-edit-token"
f"_csrf={self.csrf}"
f"&static_message={long_msg}"
f"&model=claude-haiku-4-5"
).encode()
with patch.object(dash.urllib.request, "urlopen", side_effect=self._urlopen):
_post(self.port, "/r/o/r/edit", form, cookie=self.cookie)
_post(self.port, "/r/o/r/edit", form, headers=self.auth_hdr)
put = next(c for c in self.calls if c[0] == "PUT")
cfg = json.loads(base64.b64decode(json.loads(put[3])["content"]))
self.assertEqual(len(cfg["static_message"]), 400)
def test_edit_rejects_unknown_model_with_flash(self):
form = (
b"_csrf=secret-edit-token"
b"&static_message=hi"
b"&model=does-not-exist"
)
f"_csrf={self.csrf}"
f"&static_message=hi"
f"&model=does-not-exist"
).encode()
with patch.object(dash.urllib.request, "urlopen", side_effect=self._urlopen):
status, h, _b = _post(self.port, "/r/o/r/edit", form, cookie=self.cookie)
status, h, _b = _post(self.port, "/r/o/r/edit", form, headers=self.auth_hdr)
self.assertEqual(status, 302)
self.assertIn("flash=", h.get("Location", ""))
# No PUT should have been issued.
@@ -164,16 +166,18 @@ class TestDashboardEdit(unittest.TestCase):
self.assertEqual(put_calls, [])
def test_edit_requires_auth(self):
form = b"_csrf=secret-edit-token&static_message=x&model=claude-haiku-4-5"
form = f"_csrf={self.csrf}&static_message=x&model=claude-haiku-4-5".encode()
with patch.object(dash.urllib.request, "urlopen", side_effect=self._urlopen):
status, h, _b = _post(self.port, "/r/o/r/edit", form)
self.assertEqual(status, 302)
self.assertEqual(h.get("Location"), "/login")
self.assertEqual(status, 401)
self.assertEqual(h.get("WWW-Authenticate"), 'Basic realm="pragent-dashboard"')
# No Gitea calls at all — auth gate fires first.
self.assertEqual(self.calls, [])
def test_edit_csrf_mismatch_redirects_without_save(self):
form = b"_csrf=wrong&static_message=x&model=claude-haiku-4-5"
form = f"_csrf=wrong&static_message=x&model=claude-haiku-4-5".encode()
with patch.object(dash.urllib.request, "urlopen", side_effect=self._urlopen):
status, h, _b = _post(self.port, "/r/o/r/edit", form, cookie=self.cookie)
status, h, _b = _post(self.port, "/r/o/r/edit", form, headers=self.auth_hdr)
self.assertEqual(status, 302)
self.assertEqual(h.get("Location"), "/r/o/r")
put_calls = [c for c in self.calls if c[0] == "PUT"]
@@ -192,9 +196,9 @@ class TestDashboardEdit(unittest.TestCase):
return _FakeResp(201, b"{}")
return _FakeResp(404, b"")
form = b"_csrf=secret-edit-token&static_message=hi&model=claude-haiku-4-5"
form = f"_csrf={self.csrf}&static_message=hi&model=claude-haiku-4-5".encode()
with patch.object(dash.urllib.request, "urlopen", side_effect=_route):
status, h, _b = _post(self.port, "/r/o/r/edit", form, cookie=self.cookie)
status, h, _b = _post(self.port, "/r/o/r/edit", form, headers=self.auth_hdr)
self.assertEqual(status, 302)
put = next(c for c in self.calls if c[0] == "PUT")
payload = json.loads(put[3])
@@ -202,4 +206,4 @@ class TestDashboardEdit(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
unittest.main()