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>
68 lines
2 KiB
Python
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()
|