- 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>
63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""Unit tests for the shared OIDC discovery fetch in token_utils."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from nextcloud_mcp_server.auth import token_utils
|
|
from nextcloud_mcp_server.auth.token_utils import get_oidc_discovery
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_discovery_cache():
|
|
"""Reset the in-memory discovery cache between tests."""
|
|
token_utils._discovery_cache.clear()
|
|
yield
|
|
token_utils._discovery_cache.clear()
|
|
|
|
|
|
async def test_discovery_follows_redirect_to_index_php():
|
|
"""Discovery fetch must follow 301s.
|
|
|
|
Hetzner StorageShare and other Nextcloud installs without pretty URLs
|
|
redirect ``/.well-known/openid-configuration`` to
|
|
``/index.php/.well-known/openid-configuration``. Without follow_redirects
|
|
the OAuth authorize handler raises HTTPStatusError and returns 500
|
|
(PR #758 round-2 nit 3 consolidated the discovery cache; see
|
|
``token_utils.get_oidc_discovery``).
|
|
"""
|
|
|
|
pretty_url = "https://nx.example.com/.well-known/openid-configuration"
|
|
rewritten_url = "https://nx.example.com/index.php/.well-known/openid-configuration"
|
|
discovery_doc = {
|
|
"issuer": "https://nx.example.com",
|
|
"authorization_endpoint": "https://nx.example.com/index.php/apps/oidc/authorize",
|
|
"token_endpoint": "https://nx.example.com/index.php/apps/oidc/token",
|
|
}
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
if str(request.url) == pretty_url:
|
|
return httpx.Response(301, headers={"location": rewritten_url})
|
|
if str(request.url) == rewritten_url:
|
|
return httpx.Response(200, json=discovery_doc)
|
|
return httpx.Response(404)
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
|
|
def fake_client(**kwargs):
|
|
kwargs["transport"] = transport
|
|
return httpx.AsyncClient(**kwargs)
|
|
|
|
with patch(
|
|
"nextcloud_mcp_server.auth.token_utils.nextcloud_httpx_client",
|
|
side_effect=fake_client,
|
|
) as factory:
|
|
result = await get_oidc_discovery(pretty_url)
|
|
|
|
assert result == discovery_doc
|
|
factory.assert_called_once()
|
|
assert factory.call_args.kwargs.get("follow_redirects") is True
|