feat: add read-only gitea_whoami authenticated-user lookup (#11)
Add a read-only MCP tool that calls Gitea's authenticated-user endpoint (GET /api/v1/user) and returns safe identity metadata only: username, display name, user id, email, server, and remote. This lets future review/merge workflows prove which Gitea account the MCP server is authenticated as, so self-review/self-merge can be detected before acting — the blocker discovered during PR #8 dogfooding. - Never returns the token, Authorization header, password, or secrets. - Fails closed with a clear error if identity cannot be determined. - No mutation; no profile switching; no review/approve/merge behavior. Tests: identity mapping, secret-redaction, fail-closed, unknown-remote. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ from mcp_server import ( # noqa: E402
|
||||
gitea_edit_pr,
|
||||
gitea_get_file,
|
||||
gitea_commit_files,
|
||||
gitea_whoami,
|
||||
)
|
||||
|
||||
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
|
||||
@@ -466,5 +467,52 @@ class TestCommitFiles(unittest.TestCase):
|
||||
self.assertEqual(payload["files"], files)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Whoami (authenticated-user identity lookup)
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestWhoami(unittest.TestCase):
|
||||
|
||||
@patch("mcp_server.api_request")
|
||||
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_returns_safe_identity(self, _auth, mock_api):
|
||||
mock_api.return_value = {
|
||||
"id": 42,
|
||||
"login": "reviewer-bot",
|
||||
"full_name": "Reviewer Bot",
|
||||
"email": "reviewer@example.com",
|
||||
}
|
||||
result = gitea_whoami(remote="prgs")
|
||||
self.assertTrue(result["authenticated"])
|
||||
self.assertEqual(result["username"], "reviewer-bot")
|
||||
self.assertEqual(result["display_name"], "Reviewer Bot")
|
||||
self.assertEqual(result["user_id"], 42)
|
||||
self.assertEqual(result["server"], "https://gitea.prgs.cc")
|
||||
self.assertEqual(result["remote"], "prgs")
|
||||
# Read-only: GET against the authenticated-user endpoint.
|
||||
call_args = mock_api.call_args
|
||||
self.assertEqual(call_args[0][0], "GET")
|
||||
self.assertTrue(call_args[0][1].endswith("/api/v1/user"))
|
||||
|
||||
@patch("mcp_server.api_request")
|
||||
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_never_exposes_secrets(self, _auth, mock_api):
|
||||
mock_api.return_value = {"id": 1, "login": "someone"}
|
||||
result = gitea_whoami(remote="prgs")
|
||||
blob = repr(result).lower()
|
||||
for secret in ("token", "authorization", "basic ", "password", FAKE_AUTH.lower()):
|
||||
self.assertNotIn(secret, blob)
|
||||
|
||||
@patch("mcp_server.api_request")
|
||||
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_fails_closed_without_login(self, _auth, mock_api):
|
||||
mock_api.return_value = {"id": 1} # no 'login'
|
||||
with self.assertRaises(RuntimeError):
|
||||
gitea_whoami(remote="prgs")
|
||||
|
||||
def test_rejects_unknown_remote(self):
|
||||
with self.assertRaises(ValueError):
|
||||
gitea_whoami(remote="nope")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user