Files
ITWorx_MCP_Hub-Public/tests/test_wp230_authz.py
T

180 lines
6.6 KiB
Python

"""WP-230A regression tests: the management-API permission matrix itself.
Pure unit tests against services/control_api/authz.py, with no FastAPI app
involved -- see tests/test_wp230_rbac.py for the real-HTTP, real-routing
counterpart that proves each route actually invokes the action these tests
assume.
"""
from __future__ import annotations
import logging
from collections.abc import Iterator
from datetime import UTC, datetime
import pytest
from services.control_api.auth.models import AuthenticatedPrincipal, ManagementRole, PrincipalKind
from services.control_api.authz import (
ENDPOINT_PERMISSIONS,
InsufficientRole,
check_role,
)
_NOW = datetime.now(UTC)
_AUTHZ_LOGGER = logging.getLogger("itworx_mcp_hub.management_authz")
@pytest.fixture(autouse=True)
def _ensure_authz_logger_enabled() -> Iterator[None]:
"""tests/test_migrations.py imports alembic's env.py, which calls
`logging.config.fileConfig()`; that function's default
`disable_existing_loggers=True` silently disables every logger not
explicitly listed in alembic.ini, including this one (it already exists
by the time fileConfig runs, if test_migrations.py happens to execute
first in the suite). Reset defensively rather than depending on
suite-wide test file ordering to avoid tripping this."""
previous = _AUTHZ_LOGGER.disabled
_AUTHZ_LOGGER.disabled = False
yield
_AUTHZ_LOGGER.disabled = previous
def _principal(*roles: ManagementRole) -> AuthenticatedPrincipal:
return AuthenticatedPrincipal(
subject="test-subject",
issuer="https://fake.oidc.test",
audience=("itworx-mcp-hub",),
kind=PrincipalKind.MANAGEMENT_USER,
expires_at=_NOW,
issued_at=_NOW,
roles=frozenset(roles),
)
def test_matrix_has_no_action_granting_nothing() -> None:
for action, allowed in ENDPOINT_PERMISSIONS.items():
assert allowed, f"{action} grants no role at all -- unreachable by design or a typo"
def test_matrix_never_grants_an_action_to_nobody_but_also_never_to_everyone_implicitly() -> None:
# Every action's allowed set must be an explicit, real subset of the
# four defined roles -- catches an accidental typo'd role reference.
for action, allowed in ENDPOINT_PERMISSIONS.items():
assert allowed <= set(ManagementRole), action
@pytest.mark.parametrize(
"action",
["dashboard.read", "resource.read", "policy.simulate", "catalog.preview", "client.read"],
)
@pytest.mark.parametrize("role", list(ManagementRole))
def test_baseline_reads_available_to_every_role(action: str, role: ManagementRole) -> None:
check_role(_principal(role), action) # must not raise
@pytest.mark.parametrize(
"role", [ManagementRole.AUDITOR, ManagementRole.OPERATOR, ManagementRole.ADMIN]
)
def test_audit_read_allowed_for_auditor_and_above(role: ManagementRole) -> None:
check_role(_principal(role), "audit.read")
def test_audit_read_denied_for_viewer_alone() -> None:
with pytest.raises(InsufficientRole):
check_role(_principal(ManagementRole.VIEWER), "audit.read")
@pytest.mark.parametrize("role", [ManagementRole.OPERATOR, ManagementRole.ADMIN])
@pytest.mark.parametrize(
"action",
[
"tool.test",
"tool.invoke",
"operations.check",
"client.bootstrap",
"client.verify",
"client.disable",
"client.revoke",
],
)
def test_client_lifecycle_actions_allowed_for_operator_and_admin(
role: ManagementRole, action: str
) -> None:
check_role(_principal(role), action)
@pytest.mark.parametrize("role", [ManagementRole.VIEWER, ManagementRole.AUDITOR])
@pytest.mark.parametrize(
"action",
[
"tool.test",
"tool.invoke",
"operations.check",
"client.bootstrap",
"client.verify",
"client.disable",
"client.revoke",
],
)
def test_client_lifecycle_actions_denied_below_operator(role: ManagementRole, action: str) -> None:
with pytest.raises(InsufficientRole):
check_role(_principal(role), action)
@pytest.mark.parametrize("action", ["client.create", "catalog.apply"])
def test_admin_only_actions_allowed_for_admin(action: str) -> None:
check_role(_principal(ManagementRole.ADMIN), action)
@pytest.mark.parametrize(
"role", [ManagementRole.VIEWER, ManagementRole.AUDITOR, ManagementRole.OPERATOR]
)
@pytest.mark.parametrize("action", ["client.create", "catalog.apply"])
def test_admin_only_actions_denied_below_admin(role: ManagementRole, action: str) -> None:
with pytest.raises(InsufficientRole):
check_role(_principal(role), action)
def test_multiple_roles_grant_via_any_matching_role() -> None:
# A principal holding both viewer and admin roles can do admin things --
# roles are additive, the union of what each individual role grants.
check_role(_principal(ManagementRole.VIEWER, ManagementRole.ADMIN), "catalog.apply")
def test_principal_with_no_roles_is_denied_every_action() -> None:
principal = _principal()
for action in ENDPOINT_PERMISSIONS:
with pytest.raises(InsufficientRole):
check_role(principal, action)
def test_unknown_action_raises_value_error_not_silently_denied_or_allowed() -> None:
with pytest.raises(ValueError, match="unknown management action"):
check_role(_principal(ManagementRole.ADMIN), "not-a-real-action")
def test_denial_is_logged_as_a_warning(caplog: pytest.LogCaptureFixture) -> None:
with caplog.at_level(logging.WARNING, logger="itworx_mcp_hub.management_authz"):
with pytest.raises(InsufficientRole):
check_role(_principal(ManagementRole.VIEWER), "catalog.apply")
assert any(record.levelno == logging.WARNING for record in caplog.records)
assert any("management_authz_denied" in record.getMessage() for record in caplog.records)
def test_privileged_allow_is_logged_as_info(caplog: pytest.LogCaptureFixture) -> None:
with caplog.at_level(logging.INFO, logger="itworx_mcp_hub.management_authz"):
check_role(_principal(ManagementRole.ADMIN), "catalog.apply")
assert any(
record.levelno == logging.INFO and "management_authz_allowed" in record.getMessage()
for record in caplog.records
)
def test_baseline_read_allow_is_not_logged_at_info(caplog: pytest.LogCaptureFixture) -> None:
# Dashboard polling by every role, every few seconds, is not a
# "privileged action" -- logging every allow here would be pure noise.
with caplog.at_level(logging.INFO, logger="itworx_mcp_hub.management_authz"):
check_role(_principal(ManagementRole.VIEWER), "dashboard.read")
assert not any("management_authz_allowed" in record.getMessage() for record in caplog.records)