"""Authentication boundary tests for the ops_run worker API.""" from __future__ import annotations from unittest.mock import MagicMock import pytest from fastapi import FastAPI, HTTPException from fastapi.testclient import TestClient from activity_core.ops_runs_api import ( bind_worker_id, require_worker, require_worker_or_operator, router, ) def _request(headers: dict[str, str] | None = None) -> MagicMock: request = MagicMock() request.headers = headers or {} return request def _client() -> TestClient: app = FastAPI() app.include_router(router) return TestClient(app) def test_worker_token_binds_configured_identity(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("ACTIVITY_CORE_WORKER_TOKEN", "worker-secret") monkeypatch.setenv("ACTIVITY_CORE_WORKER_ID", "rein-aharness@railiance01") authenticated = require_worker( _request(), x_worker_token="worker-secret", ) assert authenticated == "rein-aharness@railiance01" assert bind_worker_id("rein-aharness@railiance01", authenticated) == authenticated def test_spoofed_worker_id_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("ACTIVITY_CORE_WORKER_TOKEN", "worker-secret") monkeypatch.setenv("ACTIVITY_CORE_WORKER_ID", "rein-aharness@railiance01") authenticated = require_worker(_request(), x_worker_token="worker-secret") with pytest.raises(HTTPException) as exc: bind_worker_id("another-worker", authenticated) assert exc.value.status_code == 403 def test_worker_token_without_identity_fails_closed( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("ACTIVITY_CORE_WORKER_TOKEN", "worker-secret") monkeypatch.delenv("ACTIVITY_CORE_WORKER_ID", raising=False) with pytest.raises(HTTPException) as exc: require_worker(_request(), x_worker_token="worker-secret") assert exc.value.status_code == 503 assert "ACTIVITY_CORE_WORKER_ID" in exc.value.detail def test_invalid_worker_token_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("ACTIVITY_CORE_WORKER_TOKEN", "worker-secret") monkeypatch.setenv("ACTIVITY_CORE_WORKER_ID", "rein-aharness@railiance01") with pytest.raises(HTTPException) as exc: require_worker(_request(), x_worker_token="wrong") assert exc.value.status_code == 401 def test_operator_or_sso_is_not_worker_identity(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("ACTIVITY_CORE_WORKER_TOKEN", raising=False) monkeypatch.delenv("ACTIVITY_CORE_WORKER_ID", raising=False) monkeypatch.setenv("ACTIVITY_CORE_OPERATOR_TOKEN", "operator-secret") for request, kwargs in ( (_request({"Remote-User": "alice"}), {}), (_request(), {"authorization": "Bearer operator-secret"}), ): with pytest.raises(HTTPException) as exc: require_worker(request, **kwargs) assert exc.value.status_code == 503 def test_claim_endpoint_rejects_operator_and_spoofed_identity( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("ACTIVITY_CORE_WORKER_TOKEN", "worker-secret") monkeypatch.setenv("ACTIVITY_CORE_WORKER_ID", "rein-aharness@railiance01") monkeypatch.setenv("ACTIVITY_CORE_OPERATOR_TOKEN", "operator-secret") client = _client() body = {"worker_id": "rein-aharness@railiance01", "limit": 1} operator = client.post( "/ops-runs/claim", json=body, headers={"X-Operator-Token": "operator-secret"}, ) spoofed = client.post( "/ops-runs/claim", json={"worker_id": "another-worker", "limit": 1}, headers={"X-Worker-Token": "worker-secret"}, ) assert operator.status_code == 401 assert spoofed.status_code == 403 def test_unauthenticated_dev_worker_requires_explicit_opt_in( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.delenv("ACTIVITY_CORE_WORKER_TOKEN", raising=False) monkeypatch.delenv("ACTIVITY_CORE_WORKER_ID", raising=False) monkeypatch.delenv("ACTIVITY_CORE_OPERATOR_TOKEN", raising=False) monkeypatch.delenv("ACTIVITY_CORE_OPS_ALLOW_UNAUTH_MUTATIONS", raising=False) with pytest.raises(HTTPException) as exc: require_worker(_request()) assert exc.value.status_code == 503 monkeypatch.setenv("ACTIVITY_CORE_OPS_ALLOW_UNAUTH_MUTATIONS", "true") assert require_worker(_request()) == "dev:unauth" assert bind_worker_id("local-worker", "dev:unauth") == "local-worker" def test_read_auth_no_longer_defaults_open(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("ACTIVITY_CORE_WORKER_TOKEN", raising=False) monkeypatch.delenv("ACTIVITY_CORE_WORKER_ID", raising=False) monkeypatch.delenv("ACTIVITY_CORE_OPERATOR_TOKEN", raising=False) monkeypatch.delenv("ACTIVITY_CORE_OPS_ALLOW_UNAUTH_MUTATIONS", raising=False) with pytest.raises(HTTPException) as exc: require_worker_or_operator(_request()) assert exc.value.status_code == 503