145 lines
4.3 KiB
Python
145 lines
4.3 KiB
Python
"""Tests for YAML policy engine."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from aegis_gitea_mcp.config import get_settings, reset_settings
|
|
from aegis_gitea_mcp.policy import PolicyEngine, PolicyError
|
|
|
|
|
|
def _set_base_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
|
monkeypatch.setenv("GITEA_TOKEN", "token-12345")
|
|
monkeypatch.setenv("MCP_API_KEYS", "a" * 64)
|
|
|
|
|
|
def test_default_policy_allows_read_and_denies_write(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
"""Default policy should allow reads and deny writes when write mode is disabled."""
|
|
_set_base_env(monkeypatch)
|
|
reset_settings()
|
|
_ = get_settings()
|
|
|
|
engine = PolicyEngine.from_yaml_file(tmp_path / "does-not-exist.yaml")
|
|
read_decision = engine.authorize("list_repositories", is_write=False)
|
|
write_decision = engine.authorize("create_issue", is_write=True, repository="owner/repo")
|
|
|
|
assert read_decision.allowed
|
|
assert not write_decision.allowed
|
|
|
|
|
|
def test_policy_global_deny(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
"""Global deny should reject matching tool names."""
|
|
_set_base_env(monkeypatch)
|
|
policy_path = tmp_path / "policy.yaml"
|
|
policy_path.write_text(
|
|
"""
|
|
defaults:
|
|
read: allow
|
|
write: deny
|
|
tools:
|
|
deny:
|
|
- list_repositories
|
|
""".strip() + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
reset_settings()
|
|
_ = get_settings()
|
|
engine = PolicyEngine.from_yaml_file(policy_path)
|
|
|
|
decision = engine.authorize("list_repositories", is_write=False)
|
|
assert not decision.allowed
|
|
assert "denied" in decision.reason
|
|
|
|
|
|
def test_repository_path_restriction(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
"""Repository path allow-list should block unknown paths."""
|
|
_set_base_env(monkeypatch)
|
|
policy_path = tmp_path / "policy.yaml"
|
|
policy_path.write_text(
|
|
"""
|
|
repositories:
|
|
acme/app:
|
|
tools:
|
|
allow:
|
|
- get_file_contents
|
|
paths:
|
|
allow:
|
|
- src/*
|
|
""".strip() + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
reset_settings()
|
|
_ = get_settings()
|
|
engine = PolicyEngine.from_yaml_file(policy_path)
|
|
|
|
allowed = engine.authorize(
|
|
"get_file_contents",
|
|
is_write=False,
|
|
repository="acme/app",
|
|
target_path="src/main.py",
|
|
)
|
|
denied = engine.authorize(
|
|
"get_file_contents",
|
|
is_write=False,
|
|
repository="acme/app",
|
|
target_path="docs/readme.md",
|
|
)
|
|
|
|
assert allowed.allowed
|
|
assert not denied.allowed
|
|
|
|
|
|
def test_invalid_policy_structure(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
"""Invalid policy YAML should raise PolicyError."""
|
|
_set_base_env(monkeypatch)
|
|
policy_path = tmp_path / "policy.yaml"
|
|
policy_path.write_text("repositories: []\n", encoding="utf-8")
|
|
|
|
reset_settings()
|
|
_ = get_settings()
|
|
|
|
with pytest.raises(PolicyError):
|
|
PolicyEngine.from_yaml_file(policy_path)
|
|
|
|
|
|
def test_write_mode_repository_whitelist(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
"""Write mode should require repo whitelist and honor configured repository entries."""
|
|
_set_base_env(monkeypatch)
|
|
monkeypatch.setenv("WRITE_MODE", "true")
|
|
monkeypatch.setenv("WRITE_REPOSITORY_WHITELIST", "acme/app")
|
|
|
|
policy_path = tmp_path / "policy.yaml"
|
|
policy_path.write_text("defaults:\n write: allow\n", encoding="utf-8")
|
|
|
|
reset_settings()
|
|
_ = get_settings()
|
|
engine = PolicyEngine.from_yaml_file(policy_path)
|
|
|
|
allowed = engine.authorize("create_issue", is_write=True, repository="acme/app")
|
|
denied = engine.authorize("create_issue", is_write=True, repository="acme/other")
|
|
|
|
assert allowed.allowed
|
|
assert denied.allowed is False
|
|
|
|
|
|
def test_write_mode_allow_all_token_repos(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
"""Write mode can be configured to allow all repos accessible to token."""
|
|
_set_base_env(monkeypatch)
|
|
monkeypatch.setenv("WRITE_MODE", "true")
|
|
monkeypatch.setenv("WRITE_ALLOW_ALL_TOKEN_REPOS", "true")
|
|
|
|
policy_path = tmp_path / "policy.yaml"
|
|
policy_path.write_text("defaults:\n write: allow\n", encoding="utf-8")
|
|
|
|
reset_settings()
|
|
_ = get_settings()
|
|
engine = PolicyEngine.from_yaml_file(policy_path)
|
|
|
|
decision = engine.authorize("create_issue", is_write=True, repository="acme/any-repo")
|
|
assert decision.allowed
|