- oauth_login_callback's integrated-mode token-exchange branch now reuses the shared discovery cache via get_oidc_discovery (round-2 finding 1). - AS proxy flow now generates an OIDC nonce in oauth_authorize, stores it on ASProxySession, forwards it to the IdP, and passes it as expected_nonce to verify_id_token in _oauth_callback_as_proxy (round-2 finding 2). - Consolidate the two parallel discovery caches: oauth_routes' local _discovery_cache and _get_cached_discovery are removed; all callers now go through token_utils.get_oidc_discovery, which acquires the follow_redirects=True knob it needs for Nextcloud installs without pretty URLs (round-2 finding 3). - Demote per-user INFO logs in oauth_tools.py (check_logged_in, get_provisioning_status) to DEBUG; the elicitation auth URL is no longer logged because it contains a sensitive state token (round-2 finding 4). Also pin nonce binding behaviour with a new unit test that asserts _oauth_callback_as_proxy forwards session.nonce to verify_id_token, and update test mocks to track the cache consolidation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
66 lines
1.9 KiB
Python
66 lines
1.9 KiB
Python
"""Unit tests for DCR proxy registration_not_supported path."""
|
|
|
|
import json
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from nextcloud_mcp_server.auth.oauth_routes import oauth_register_proxy
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
def _make_request(body: dict, oauth_config: dict) -> MagicMock:
|
|
"""Create a mock Starlette Request."""
|
|
request = AsyncMock()
|
|
request.json = AsyncMock(return_value=body)
|
|
request.client = MagicMock()
|
|
request.client.host = "127.0.0.1"
|
|
request.app = MagicMock()
|
|
request.app.state.oauth_context = {"config": oauth_config}
|
|
return request
|
|
|
|
|
|
_DCR_BODY = {
|
|
"client_name": "test",
|
|
"redirect_uris": ["http://localhost:9999/cb"],
|
|
}
|
|
|
|
|
|
async def test_registration_not_supported_when_no_endpoint():
|
|
"""When discovery doc lacks registration_endpoint, return 400."""
|
|
request = _make_request(
|
|
body=_DCR_BODY,
|
|
oauth_config={
|
|
"discovery_url": "https://idp.example.com/.well-known/openid-configuration"
|
|
},
|
|
)
|
|
|
|
discovery_doc = {
|
|
"issuer": "https://idp.example.com",
|
|
"authorization_endpoint": "https://idp.example.com/auth",
|
|
}
|
|
|
|
with patch(
|
|
"nextcloud_mcp_server.auth.oauth_routes.get_oidc_discovery",
|
|
new_callable=AsyncMock,
|
|
return_value=discovery_doc,
|
|
):
|
|
response = await oauth_register_proxy(request)
|
|
|
|
assert response.status_code == 400
|
|
body = json.loads(response.body)
|
|
assert body["error"] == "registration_not_supported"
|
|
assert "ALLOWED_MCP_CLIENTS" in body["error_description"]
|
|
|
|
|
|
async def test_registration_not_supported_when_no_discovery_url():
|
|
"""When no discovery_url is configured, return 400."""
|
|
request = _make_request(body=_DCR_BODY, oauth_config={})
|
|
|
|
response = await oauth_register_proxy(request)
|
|
|
|
assert response.status_code == 400
|
|
body = json.loads(response.body)
|
|
assert body["error"] == "registration_not_supported"
|