test: cover the per-user OAuth flow, threat cases, store and startup
This commit is contained in:
1 parent
566d0b65dd
commit
7286440d64
9 files changed
+2505
-7
No files matched your search
@@ -0,0 +1,111 @@
|
||||
"""End-to-end per-user OAuth over the real HTTP app: DCR, consent, Gitea, tokens, MCP calls."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pytest_httpx import HTTPXMock
|
||||
|
||||
from aegis_gitea_mcp.oauth.provider import TOKEN_PREFIX
|
||||
from tests.test_oauth_helpers import (
|
||||
API,
|
||||
APP_CLIENT_ID,
|
||||
APP_CLIENT_SECRET,
|
||||
BASE,
|
||||
CLIENT_STATE,
|
||||
GITEA,
|
||||
GITEA_CODE,
|
||||
GITEA_TOKEN,
|
||||
REDIRECT,
|
||||
TOKEN_URL,
|
||||
USER_TOKEN,
|
||||
browser,
|
||||
build_oauth_app,
|
||||
exchange_code,
|
||||
form_of,
|
||||
gitea_requests,
|
||||
mcp_client,
|
||||
mock_gitea_login,
|
||||
payload,
|
||||
pkce,
|
||||
query_of,
|
||||
register,
|
||||
start_flow,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _env(mock_env: None) -> None:
|
||||
"""The audit logger and the rate limiter read the process-wide settings."""
|
||||
|
||||
|
||||
async def test_full_flow_runs_tool_calls_as_the_signed_in_user(httpx_mock: HTTPXMock) -> None:
|
||||
oauth = build_oauth_app()
|
||||
client = browser(oauth.app)
|
||||
registration = register(client).json()
|
||||
verifier, challenge = pkce()
|
||||
|
||||
gitea_url = start_flow(client, registration, challenge)
|
||||
|
||||
# Our server sends the browser to Gitea with its own PKCE, callback and scopes.
|
||||
assert gitea_url.startswith(f"{GITEA}/login/oauth/authorize?")
|
||||
upstream = query_of(gitea_url)
|
||||
assert upstream["client_id"] == APP_CLIENT_ID
|
||||
assert upstream["redirect_uri"] == f"{BASE}/oauth/callback"
|
||||
assert upstream["response_type"] == "code"
|
||||
assert upstream["code_challenge_method"] == "S256"
|
||||
assert upstream["code_challenge"] != challenge # ours, not the client's
|
||||
assert upstream["scope"] == " ".join(oauth.settings.oauth_scopes)
|
||||
assert upstream["state"] != CLIENT_STATE
|
||||
|
||||
mock_gitea_login(httpx_mock, login="alice")
|
||||
callback = client.get(
|
||||
"/oauth/callback", params={"code": GITEA_CODE, "state": upstream["state"]}
|
||||
)
|
||||
assert callback.status_code == 302
|
||||
location = callback.headers["location"]
|
||||
assert location.startswith(f"{REDIRECT}?")
|
||||
back = query_of(location)
|
||||
assert back["state"] == CLIENT_STATE and back["code"]
|
||||
|
||||
# Gitea received the code with our verifier and the app credentials.
|
||||
(token_request,) = gitea_requests(httpx_mock, TOKEN_URL)
|
||||
form = form_of(token_request)
|
||||
assert form["grant_type"] == "authorization_code"
|
||||
assert form["code"] == GITEA_CODE
|
||||
assert form["client_id"] == APP_CLIENT_ID
|
||||
assert form["client_secret"] == APP_CLIENT_SECRET
|
||||
assert form["redirect_uri"] == f"{BASE}/oauth/callback"
|
||||
assert form["code_verifier"]
|
||||
(user_request,) = gitea_requests(httpx_mock, f"{API}/user")
|
||||
assert user_request.headers["Authorization"] == f"Bearer {USER_TOKEN}"
|
||||
|
||||
tokens = exchange_code(client, registration, back["code"], verifier)
|
||||
assert tokens.status_code == 200
|
||||
body = tokens.json()
|
||||
assert body["token_type"] == "Bearer"
|
||||
assert body["expires_in"] == 3600
|
||||
assert body["scope"] == "gitea"
|
||||
assert body["access_token"].startswith(TOKEN_PREFIX)
|
||||
assert body["refresh_token"].startswith(TOKEN_PREFIX)
|
||||
|
||||
httpx_mock.add_response(url=f"{API}/repos/octo/demo", json={"full_name": "octo/demo"})
|
||||
httpx_mock.add_response(
|
||||
url=f"{API}/admin/users", status_code=403, json={"message": "Only admins"}
|
||||
)
|
||||
async with mcp_client(oauth.app, body["access_token"]) as mcp:
|
||||
result = await mcp.call_tool("gitea_repo_get", {"owner": "octo", "repo": "demo"})
|
||||
admin = await mcp.call_tool("gitea_admin_search_users", {})
|
||||
|
||||
assert not result.is_error
|
||||
envelope = payload(result)
|
||||
assert envelope["identity"] == {"login": "alice", "mode": "oauth", "sudo": None}
|
||||
(repo_request,) = gitea_requests(httpx_mock, f"{API}/repos/octo/demo")
|
||||
assert repo_request.headers["Authorization"] == f"Bearer {USER_TOKEN}"
|
||||
|
||||
assert admin.is_error
|
||||
error = payload(admin)
|
||||
assert error["error"]["kind"] == "forbidden"
|
||||
assert error["identity"]["mode"] == "oauth"
|
||||
(admin_request,) = gitea_requests(httpx_mock, f"{API}/admin/users")
|
||||
assert admin_request.headers["Authorization"] == f"Bearer {USER_TOKEN}"
|
||||
assert GITEA_TOKEN not in admin_request.headers["Authorization"]
|
||||
Reference in new issue
Block a user