fastapi-gsap/tests/test_mcp_intune.py
Tyler J King 782f5654ac fix: shared bearer auth, delegation depth, SQLite permissions
C-4: MCP endpoint requires verified bearer token. Unauthenticated
     requests rejected. _extract_principal() replaced by verified
     AuthResult from middleware.
C-8: All delegation endpoints require verified bearer token.
     X-Delegator-DID header removed — identity from token only.
     delegator_ac_id validated to belong to authenticated principal.
     Only delegators can revoke. Only delegator/delegate can view.
H-6: SQLite file permissions restricted to 0o600 (owner-only).
     Umask set before creation. WAL/SHM files also restricted.
H-7: Delegation depth tracked and enforced against max_delegation_depth.
     Sub-delegations increment depth. Exceeded depth → 403.

Shared TokenAuthenticator auto-detects identity driver from JWT
issuer claim (Keycloak or Entra). verify_bearer FastAPI dependency
for all protected endpoints. Health endpoint remains public.

ALL 10 critical findings CLOSED. ALL 10 high findings CLOSED.

Signed-off-by: Tyler King <tking@guildhouse.dev>
2026-04-14 17:31:46 -04:00

68 lines
2 KiB
Python

# Copyright 2026 Guildhouse Dev
# SPDX-License-Identifier: Apache-2.0
"""Tests for Intune MCP tools."""
import pytest
from unittest.mock import patch
from httpx import AsyncClient, ASGITransport
from gsap_broker.app import app
from gsap_broker.drivers.base import AuthResult
def _mock_auth():
"""Return a mock AuthResult for bypassing auth in tests."""
return AuthResult(
status=AuthResult.STATUS_AUTHORIZED,
principal_did="did:web:test/p/testuser",
token_jti="test-jti",
)
@pytest.fixture
async def client():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
yield c
@pytest.fixture(autouse=True)
def bypass_auth():
"""Bypass bearer auth for MCP tests (auth tested separately)."""
from gsap_broker.auth.middleware import verify_bearer
app.dependency_overrides[verify_bearer] = lambda: _mock_auth()
yield
app.dependency_overrides.pop(verify_bearer, None)
@pytest.mark.asyncio
async def test_mcp_tools_list_includes_intune(client):
"""MCP tools/list should include Intune tools."""
resp = await client.post("/mcp", json={
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1,
})
assert resp.status_code == 200
tools = resp.json()["result"]["tools"]
tool_names = [t["name"] for t in tools]
assert "list_devices" in tool_names
assert "get_device_compliance" in tool_names
assert "sync_device" in tool_names
assert "remote_lock" in tool_names
@pytest.mark.asyncio
async def test_mcp_intune_tool_without_connector(client):
"""Intune MCP tool should return error when connector not enabled."""
resp = await client.post("/mcp", json={
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_devices",
"arguments": {},
},
"id": 2,
})
assert resp.status_code == 200
content = resp.json()["result"]["content"][0]["text"]
assert "not enabled" in content.lower() or "error" in content.lower()