| """ |
| Authorization security tests (SECU-02). |
| |
| Tests cover: |
| - Agent maturity permission matrix (4x4 combinations) |
| - Action complexity enforcement |
| - Governance checks |
| - Permission boundaries |
| """ |
| import pytest |
| from fastapi.testclient import TestClient |
| from sqlalchemy.orm import Session |
| from tests.factories.agent_factory import ( |
| StudentAgentFactory, |
| InternAgentFactory, |
| SupervisedAgentFactory, |
| AutonomousAgentFactory |
| ) |
| from tests.factories.user_factory import AdminUserFactory |
| from core.models import AgentStatus |
| from core.auth import get_password_hash, create_access_token |
|
|
|
|
| |
| |
| MATURITY_ACTION_MATRIX = [ |
| |
| |
| (StudentAgentFactory, "search", 1, True, "STUDENT can search (read-only)"), |
| (StudentAgentFactory, "read", 1, True, "STUDENT can read (read-only)"), |
| (StudentAgentFactory, "list", 1, True, "STUDENT can list (read-only)"), |
| (StudentAgentFactory, "stream", 2, False, "STUDENT blocked from streaming"), |
| (StudentAgentFactory, "analyze", 2, False, "STUDENT blocked from analyze"), |
| (StudentAgentFactory, "delete", 4, False, "STUDENT blocked from deletions"), |
| (StudentAgentFactory, "execute", 4, False, "STUDENT blocked from execute"), |
| (StudentAgentFactory, "update", 3, False, "STUDENT blocked from state changes"), |
|
|
| |
| (InternAgentFactory, "search", 1, True, "INTERN can search"), |
| (InternAgentFactory, "analyze", 2, True, "INTERN can analyze"), |
| (InternAgentFactory, "draft", 2, True, "INTERN can draft"), |
| (InternAgentFactory, "delete", 4, False, "INTERN blocked from deletions"), |
| (InternAgentFactory, "execute", 4, False, "INTERN blocked from execute"), |
| (InternAgentFactory, "update", 3, False, "INTERN blocked from state changes"), |
| (InternAgentFactory, "send_email", 3, False, "INTERN blocked from send_email"), |
| (InternAgentFactory, "submit", 2, True, "INTERN can submit (complexity 2 - not submit_form)"), |
|
|
| |
| (SupervisedAgentFactory, "search", 1, True, "SUPERVISED can search"), |
| (SupervisedAgentFactory, "stream", 2, True, "SUPERVISED can stream"), |
| (SupervisedAgentFactory, "analyze", 2, True, "SUPERVISED can analyze"), |
| (SupervisedAgentFactory, "update", 3, True, "SUPERVISED can update state"), |
| (SupervisedAgentFactory, "submit_form", 3, True, "SUPERVISED can submit_form"), |
| (SupervisedAgentFactory, "delete", 4, False, "SUPERVISED blocked from deletions"), |
| (SupervisedAgentFactory, "execute", 4, False, "SUPERVISED blocked from execute"), |
| (SupervisedAgentFactory, "send_email", 3, True, "SUPERVISED can send_email"), |
|
|
| |
| (AutonomousAgentFactory, "search", 1, True, "AUTONOMOUS can search"), |
| (AutonomousAgentFactory, "stream", 2, True, "AUTONOMOUS can stream"), |
| (AutonomousAgentFactory, "update", 3, True, "AUTONOMOUS can update state"), |
| (AutonomousAgentFactory, "delete", 4, True, "AUTONOMOUS can delete"), |
| (AutonomousAgentFactory, "execute", 4, True, "AUTONOMOUS can execute"), |
| (AutonomousAgentFactory, "submit", 2, True, "AUTONOMOUS can submit"), |
| (AutonomousAgentFactory, "send_email", 3, True, "AUTONOMOUS can send_email"), |
| (AutonomousAgentFactory, "analyze", 2, True, "AUTONOMOUS can analyze"), |
| ] |
|
|
|
|
| class TestMaturityPermissionMatrix: |
| """Test agent maturity permission matrix enforcement.""" |
|
|
| @pytest.mark.parametrize("agent_factory,action,complexity,allowed,description", MATURITY_ACTION_MATRIX) |
| def test_maturity_action_combinations( |
| self, |
| db_session: Session, |
| agent_factory, |
| action, |
| complexity, |
| allowed, |
| description |
| ): |
| """ |
| Test all maturity level x action complexity combinations. |
| |
| This is a CRITICAL security test ensuring governance enforcement. |
| """ |
| |
| agent = agent_factory(_session=db_session) |
|
|
| |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| result = governance.can_perform_action( |
| agent_id=agent.id, |
| action_type=action |
| ) |
|
|
| |
| assert result["allowed"] == allowed, \ |
| f"Failed for {description}: got allowed={result['allowed']}, expected allowed={allowed}" |
|
|
| |
| assert result["agent_status"] == agent.status |
|
|
| |
| assert result["action_complexity"] == complexity |
|
|
| |
| assert "reason" in result |
| assert len(result["reason"]) > 0 |
|
|
| def test_student_blocked_from_all_triggers(self, db_session: Session): |
| """Test STUDENT agent is completely blocked from automated triggers.""" |
| agent = StudentAgentFactory(_session=db_session) |
|
|
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| for action in ["delete", "execute", "update", "send_email"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["allowed"] == False, f"STUDENT should be blocked from {action}" |
| assert "lacks maturity" in result["reason"].lower() or "blocked" in result["reason"].lower() |
|
|
| def test_intern_requires_approval_for_state_changes(self, db_session: Session): |
| """Test INTERN agent requires approval for state changes.""" |
| agent = InternAgentFactory(_session=db_session) |
|
|
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| for action in ["delete", "execute", "update", "send_email", "submit_form"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["allowed"] == False, f"INTERN should be blocked from {action}" |
|
|
| def test_supervised_allowed_for_most_actions(self, db_session: Session): |
| """Test SUPERVISED agent can perform most actions except critical.""" |
| agent = SupervisedAgentFactory(_session=db_session) |
|
|
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| for action in ["search", "stream", "analyze", "update", "submit", "send_email"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["allowed"] == True, f"SUPERVISED should be allowed to {action}" |
|
|
| |
| for action in ["delete", "execute"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["allowed"] == False, f"SUPERVISED should be blocked from {action}" |
|
|
| def test_autonomous_full_execution(self, db_session: Session): |
| """Test AUTONOMOUS agent has full execution权限.""" |
| agent = AutonomousAgentFactory(_session=db_session) |
|
|
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| for action in ["search", "stream", "analyze", "update", "submit", "delete", "execute", "send_email"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["allowed"] == True, \ |
| f"AUTONOMOUS agent should be allowed to {action}, got: {result}" |
|
|
|
|
| class TestGovernanceCaching: |
| """Test governance cache doesn't bypass security.""" |
|
|
| def test_governance_cache_consistent_with_database(self, db_session: Session): |
| """Test governance cache returns consistent decisions.""" |
| from core.governance_cache import get_governance_cache |
|
|
| agent = StudentAgentFactory(_session=db_session) |
|
|
| |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| db_result = governance.can_perform_action( |
| agent_id=agent.id, |
| action_type="delete" |
| ) |
|
|
| |
| cache = get_governance_cache() |
| cached_result = cache.get(agent.id, "delete") |
|
|
| |
| assert cached_result is not None |
| assert cached_result["allowed"] == db_result["allowed"] |
| assert db_result["allowed"] == False |
|
|
| def test_cache_invalidation_on_maturity_change(self, db_session: Session): |
| """Test cache invalidates when agent maturity changes.""" |
| agent = StudentAgentFactory(_session=db_session) |
|
|
| from core.governance_cache import get_governance_cache |
| from core.agent_governance_service import AgentGovernanceService |
|
|
| cache = get_governance_cache() |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| result1 = governance.can_perform_action(agent.id, "delete") |
| assert result1["allowed"] == False |
|
|
| |
| agent.status = AgentStatus.AUTONOMOUS.value |
| agent.confidence_score = 0.95 |
| db_session.commit() |
|
|
| |
| cache.invalidate(agent.id) |
|
|
| |
| result2 = governance.can_perform_action(agent.id, "delete") |
| assert result2["allowed"] == True |
|
|
| def test_cache_hit_returns_same_result(self, db_session: Session): |
| """Test cache hit returns identical result.""" |
| agent = InternAgentFactory(_session=db_session) |
|
|
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| result1 = governance.can_perform_action(agent.id, "stream") |
| assert "action_complexity" in result1 |
|
|
| |
| result2 = governance.can_perform_action(agent.id, "stream") |
|
|
| |
| assert result1["allowed"] == result2["allowed"] |
| assert result1["action_complexity"] == result2["action_complexity"] |
| assert result1["agent_status"] == result2["agent_status"] |
|
|
|
|
| class TestPermissionBoundaries: |
| """Test permission boundaries between maturity levels.""" |
|
|
| def test_maturity_hierarchy_enforced(self, db_session: Session): |
| """Test maturity level hierarchy is properly enforced.""" |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| student = StudentAgentFactory(_session=db_session) |
| intern = InternAgentFactory(_session=db_session) |
| supervised = SupervisedAgentFactory(_session=db_session) |
| autonomous = AutonomousAgentFactory(_session=db_session) |
|
|
| |
| for agent in [student, intern, supervised]: |
| result = governance.can_perform_action(agent.id, "delete") |
| assert result["allowed"] == False, \ |
| f"{agent.status} should not be allowed to delete" |
|
|
| |
| result = governance.can_perform_action(autonomous.id, "delete") |
| assert result["allowed"] == True |
|
|
| def test_confidence_score_consistent_with_maturity(self, db_session: Session): |
| """Test confidence scores match maturity levels.""" |
| student = StudentAgentFactory(_session=db_session) |
| intern = InternAgentFactory(_session=db_session) |
| supervised = SupervisedAgentFactory(_session=db_session) |
| autonomous = AutonomousAgentFactory(_session=db_session) |
|
|
| |
| assert 0.0 <= student.confidence_score < 0.5 |
|
|
| |
| assert 0.5 <= intern.confidence_score < 0.7 |
|
|
| |
| assert 0.7 <= supervised.confidence_score < 0.9 |
|
|
| |
| assert 0.9 <= autonomous.confidence_score <= 1.0 |
|
|
| def test_action_complexity_levels(self, db_session: Session): |
| """Test action complexity is correctly classified.""" |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| agent = AutonomousAgentFactory(_session=db_session) |
|
|
| |
| for action in ["search", "read", "list", "get", "summarize", "present_chart"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["action_complexity"] == 1, \ |
| f"{action} should be complexity 1, got {result['action_complexity']}" |
|
|
| |
| for action in ["analyze", "suggest", "draft", "generate", "stream", "submit"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["action_complexity"] == 2, \ |
| f"{action} should be complexity 2, got {result['action_complexity']}" |
|
|
| |
| for action in ["update", "submit_form", "send_email", "create", "post_message"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["action_complexity"] == 3, \ |
| f"{action} should be complexity 3, got {result['action_complexity']}" |
|
|
| |
| for action in ["delete", "execute", "deploy", "payment"]: |
| result = governance.can_perform_action(agent.id, action) |
| assert result["action_complexity"] == 4, \ |
| f"{action} should be complexity 4, got {result['action_complexity']}" |
|
|
| def test_get_agent_capabilities(self, db_session: Session): |
| """Test getting agent capabilities based on maturity.""" |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| |
| student = StudentAgentFactory(_session=db_session) |
|
|
| capabilities = governance.get_agent_capabilities(student.id) |
|
|
| assert capabilities["maturity_level"] == AgentStatus.STUDENT.value |
| assert capabilities["max_complexity"] == 1 |
| assert len(capabilities["allowed_actions"]) > 0 |
| assert "search" in capabilities["allowed_actions"] |
| assert "delete" not in capabilities["allowed_actions"] |
|
|
| |
| autonomous = AutonomousAgentFactory(_session=db_session) |
|
|
| capabilities = governance.get_agent_capabilities(autonomous.id) |
|
|
| assert capabilities["maturity_level"] == AgentStatus.AUTONOMOUS.value |
| assert capabilities["max_complexity"] == 4 |
| assert "delete" in capabilities["allowed_actions"] |
| assert "execute" in capabilities["allowed_actions"] |
|
|
|
|
| class TestGovernanceEnforcement: |
| """Test governance enforcement at service level.""" |
|
|
| def test_enforce_action_blocks_unauthorized(self, db_session: Session): |
| """Test enforce_action blocks unauthorized agents.""" |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| student = StudentAgentFactory(_session=db_session) |
|
|
| |
| result = governance.enforce_action( |
| agent_id=student.id, |
| action_type="delete", |
| action_details={"target": "test"} |
| ) |
|
|
| assert result["proceed"] == False |
| assert result["status"] in ["BLOCKED", "PENDING_APPROVAL"] |
|
|
| def test_enforce_action_allows_authorized(self, db_session: Session): |
| """Test enforce_action allows authorized agents.""" |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| autonomous = AutonomousAgentFactory(_session=db_session) |
|
|
| |
| result = governance.enforce_action( |
| agent_id=autonomous.id, |
| action_type="delete", |
| action_details={"target": "test"} |
| ) |
|
|
| assert result["proceed"] == True |
| assert result["status"] == "APPROVED" |
|
|
| def test_agent_not_found_returns_error(self, db_session: Session): |
| """Test governance check for non-existent agent.""" |
| from core.agent_governance_service import AgentGovernanceService |
| governance = AgentGovernanceService(db_session) |
|
|
| result = governance.can_perform_action( |
| agent_id="nonexistent-agent-id", |
| action_type="delete" |
| ) |
|
|
| assert result["allowed"] == False |
| assert "not found" in result["reason"].lower() |
|
|
|
|
| class TestTriggerInterceptor: |
| """Test trigger interceptor maturity-based routing.""" |
|
|
| def test_routing_decision_exists(self, db_session: Session): |
| """Test that RoutingDecision enum has expected values.""" |
| from core.trigger_interceptor import RoutingDecision |
|
|
| |
| assert hasattr(RoutingDecision, 'TRAINING') |
| assert hasattr(RoutingDecision, 'PROPOSAL') |
| assert hasattr(RoutingDecision, 'SUPERVISION') |
| assert hasattr(RoutingDecision, 'EXECUTION') |
|
|
| def test_maturity_level_exists(self, db_session: Session): |
| """Test that MaturityLevel enum has expected values.""" |
| from core.trigger_interceptor import MaturityLevel |
|
|
| |
| assert hasattr(MaturityLevel, 'STUDENT') |
| assert hasattr(MaturityLevel, 'INTERN') |
| assert hasattr(MaturityLevel, 'SUPERVISED') |
| assert hasattr(MaturityLevel, 'AUTONOMOUS') |
|
|
| def test_trigger_source_exists(self, db_session: Session): |
| """Test that TriggerSource enum has expected values.""" |
| from core.trigger_interceptor import TriggerSource |
|
|
| |
| assert hasattr(TriggerSource, 'MANUAL') |
| assert hasattr(TriggerSource, 'WORKFLOW_ENGINE') |
| assert hasattr(TriggerSource, 'DATA_SYNC') |
|
|
| def test_interceptor_initialization(self, db_session: Session): |
| """Test TriggerInterceptor can be initialized.""" |
| from core.trigger_interceptor import TriggerInterceptor |
|
|
| interceptor = TriggerInterceptor(db_session, "test_workspace") |
|
|
| assert interceptor is not None |
| assert interceptor.db == db_session |
| assert interceptor.workspace_id == "test_workspace" |
|
|