"""Adversarial tests for the shared Bearer-token principal fallback.""" from __future__ import annotations from uuid import UUID, uuid4 import pytest from fastapi import Request from ragcore.api.v1.control.bearer_auth import bearer_principal from ragcore.api.v1.control.dependencies import ControlAPIProblem from ragcore.application.identity.credentials import ServiceAccountCredentialService from ragcore.domain.authorization import Principal, PrincipalType from ragcore.domain.control import Role, Scope, ServiceAccount, Workspace from ragcore.infrastructure.database.identity import InMemoryCredentialRepository PEPPER = "test-credential-pepper-that-is-long-enough" def _token_factory(credential_id: UUID) -> str: return f"rc_sa_{credential_id.hex}_{'a' * 43}" def _request(headers: dict[bytes, bytes]) -> Request: return Request({"type": "http", "headers": list(headers.items())}) def _service_account(scopes: set[Scope]) -> tuple[Workspace, ServiceAccount]: frozen_scopes = frozenset(scopes) workspace = Workspace(uuid4(), "Test", "test") application = workspace.create_application( id=uuid4(), name="n8n", slug="n8n", scopes=frozen_scopes ) account = ServiceAccount( id=uuid4(), workspace_id=workspace.id, application_id=application.id, name="n8n worker", scopes=frozen_scopes, active=True, ) return workspace, account async def _credential_service( account: ServiceAccount | None, ) -> ServiceAccountCredentialService: async def load(account_id: UUID) -> ServiceAccount | None: return account if account is not None and account_id == account.id else None return ServiceAccountCredentialService( InMemoryCredentialRepository(), PEPPER, service_account_loader=load, token_factory=_token_factory, ) @pytest.mark.security async def test_bearer_principal_accepts_a_correctly_scoped_token() -> None: workspace, account = _service_account({Scope.SEARCH}) credential_service = await _credential_service(account) issued = await credential_service.issue(account, scopes={Scope.SEARCH}) dependency = bearer_principal(Scope.SEARCH) request = _request({b"authorization": f"Bearer {issued.token}".encode()}) result = await dependency(request, credential_service) assert result.principal_type is PrincipalType.SERVICE_ACCOUNT assert result.id == account.id assert result.workspace_id == workspace.id assert Role.EDITOR in result.roles @pytest.mark.security async def test_bearer_principal_prefers_an_existing_session_principal() -> None: workspace, account = _service_account({Scope.CONTEXT}) credential_service = await _credential_service(account) issued = await credential_service.issue(account, scopes={Scope.CONTEXT}) session_principal = Principal( id=uuid4(), workspace_id=workspace.id, principal_type=PrincipalType.HUMAN ) request = _request({b"authorization": f"Bearer {issued.token}".encode()}) request.state.principal = session_principal result = await bearer_principal(Scope.CONTEXT)(request, credential_service) assert result is session_principal @pytest.mark.security async def test_bearer_principal_rejects_wrong_scope_missing_header_and_malformed_token() -> None: _workspace, account = _service_account({Scope.SEARCH}) credential_service = await _credential_service(account) issued = await credential_service.issue(account, scopes={Scope.SEARCH}) dependency = bearer_principal(Scope.ANSWER) scoped_request = _request({b"authorization": f"Bearer {issued.token}".encode()}) with pytest.raises(ControlAPIProblem) as wrong_scope: await dependency(scoped_request, credential_service) assert wrong_scope.value.status == 401 with pytest.raises(ControlAPIProblem) as no_header: await dependency(_request({}), credential_service) assert no_header.value.status == 401 with pytest.raises(ControlAPIProblem) as malformed: await dependency( _request({b"authorization": b"Bearer not-a-real-token"}), credential_service ) assert malformed.value.status == 401 @pytest.mark.security async def test_bearer_principal_rejects_deactivated_service_account() -> None: _workspace, account = _service_account({Scope.ANSWER}) repository = InMemoryCredentialRepository() async def load_active(account_id: UUID) -> ServiceAccount | None: return account if account_id == account.id else None issuing_service = ServiceAccountCredentialService( repository, PEPPER, service_account_loader=load_active, token_factory=_token_factory ) issued = await issuing_service.issue(account, scopes={Scope.ANSWER}) inactive_account = ServiceAccount( id=account.id, workspace_id=account.workspace_id, application_id=account.application_id, name=account.name, scopes=account.scopes, active=False, ) async def load_inactive(account_id: UUID) -> ServiceAccount | None: return inactive_account if account_id == account.id else None authenticating_service = ServiceAccountCredentialService( repository, PEPPER, service_account_loader=load_inactive, token_factory=_token_factory ) with pytest.raises(ControlAPIProblem) as error: await bearer_principal(Scope.ANSWER)( _request({b"authorization": f"Bearer {issued.token}".encode()}), authenticating_service, ) assert error.value.status == 401