"""Unit tests for OAuth tool input-schema hardening (issue #626 finding 3). These tools must derive `user_id` from the verified MCP access token and must never accept it as an MCP-level input. Otherwise an LLM (or any MCP client) could supply an arbitrary user_id and reach cross-user revoke or status-disclosure operations. """ import pytest from mcp.server.fastmcp import FastMCP from nextcloud_mcp_server.server.oauth_tools import register_oauth_tools pytestmark = pytest.mark.unit HARDENED_TOOLS = ( "provision_nextcloud_access", "revoke_nextcloud_access", "check_provisioning_status", "check_logged_in", ) @pytest.fixture def registered_tools(): """Register the OAuth tools against a fresh FastMCP and return them by name. Uses FastMCP's `_tool_manager.list_tools()`; flagged as internal and may break on SDK upgrades, but this is the supported way to inspect a tool's JSON input schema in unit tests (see tests/unit/test_stdio.py). """ mcp = FastMCP("test-oauth-tools") register_oauth_tools(mcp) tools = mcp._tool_manager.list_tools() return {t.name: t for t in tools} def test_oauth_tools_registered(registered_tools): for name in HARDENED_TOOLS: assert name in registered_tools, f"{name} should be registered" @pytest.mark.parametrize("tool_name", HARDENED_TOOLS) def test_oauth_tool_schema_does_not_accept_user_id(tool_name, registered_tools): """user_id must not appear in the tool's JSON input schema.""" tool = registered_tools[tool_name] properties = tool.parameters.get("properties", {}) required = tool.parameters.get("required", []) assert "user_id" not in properties, ( f"{tool_name} accepts user_id as an MCP input — must be derived from " f"the verified access token (issue #626 finding 3). " f"properties={list(properties.keys())}" ) assert "user_id" not in required