Some checks failed
Deploy Development / deploy (push) Failing after 42s
Test Suite / pytest-backend (push) Failing after 1s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
Agenten authentifizieren per X-Actor-Token; /api/operational/ Fassade mit context, next-action, status, evidence; Token-Verwaltung unter /api/actors/{id}/service-tokens.
Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
3.6 KiB
Python
125 lines
3.6 KiB
Python
"""AP1.7 — Operational API and Actor Service Tokens."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
|
|
from auth import AUTH_HEADER
|
|
from services import actors as actor_service
|
|
from services.actor_service_tokens import (
|
|
TOKEN_PREFIX,
|
|
_hash_token,
|
|
create_service_token,
|
|
validate_service_token,
|
|
)
|
|
from tests.factories import provision_user_in_tenant
|
|
from tests.test_initiatives_actions import _auth, _create_initiative, _login
|
|
|
|
|
|
def test_token_hash_roundtrip():
|
|
raw = f"{TOKEN_PREFIX}abc123_secretpart"
|
|
assert _hash_token(raw) == hashlib.sha256(raw.encode()).hexdigest()
|
|
|
|
|
|
def test_validate_service_token_rejects_garbage():
|
|
assert validate_service_token("not-a-token") is None
|
|
assert validate_service_token("") is None
|
|
|
|
|
|
def test_operational_api_with_service_token(client):
|
|
admin = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, admin)
|
|
|
|
agent = actor_service.create_actor(
|
|
tenant_id=admin["tenant_id"],
|
|
actor_type="agent",
|
|
name="Cursor Agent",
|
|
)
|
|
created = create_service_token(
|
|
tenant_id=admin["tenant_id"],
|
|
actor_id=agent["id"],
|
|
label="Test Agent",
|
|
created_by_user_id=admin["id"],
|
|
)
|
|
actor_token = created["token"]
|
|
|
|
me = client.get(
|
|
"/api/operational/me",
|
|
headers={"X-Actor-Token": actor_token},
|
|
)
|
|
assert me.status_code == 200
|
|
body = me.json()
|
|
assert body["ok"] is True
|
|
assert body["actor_id"] == agent["id"]
|
|
assert body["data"]["auth_source"] == "service_token"
|
|
|
|
initiative = _create_initiative(client, token, title="Agent Test Initiative")
|
|
initiative_id = initiative.json()["id"]
|
|
|
|
ctx = client.get(
|
|
f"/api/operational/initiatives/{initiative_id}/context",
|
|
headers={"X-Actor-Token": actor_token},
|
|
)
|
|
assert ctx.status_code == 200
|
|
assert ctx.json()["data"]["title"] == "Agent Test Initiative"
|
|
|
|
next_action = client.get(
|
|
f"/api/operational/next-action?initiative_id={initiative_id}&limit=3",
|
|
headers={"X-Actor-Token": actor_token},
|
|
)
|
|
assert next_action.status_code == 200
|
|
assert isinstance(next_action.json()["data"], list)
|
|
|
|
|
|
def test_operational_token_missing_capability(client):
|
|
admin = provision_user_in_tenant(tenant_role="admin")
|
|
agent = actor_service.create_actor(
|
|
tenant_id=admin["tenant_id"],
|
|
actor_type="agent",
|
|
name="Limited Agent",
|
|
)
|
|
created = create_service_token(
|
|
tenant_id=admin["tenant_id"],
|
|
actor_id=agent["id"],
|
|
label="Read-only",
|
|
created_by_user_id=admin["id"],
|
|
capabilities=["kairo.action.read"],
|
|
)
|
|
|
|
res = client.post(
|
|
"/api/operational/evidence",
|
|
json={
|
|
"initiative_id": "00000000-0000-0000-0000-000000000001",
|
|
"title": "Should fail",
|
|
},
|
|
headers={"X-Actor-Token": created["token"]},
|
|
)
|
|
assert res.status_code == 403
|
|
|
|
|
|
def test_create_service_token_via_api(client):
|
|
admin = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, admin)
|
|
agent = actor_service.create_actor(
|
|
tenant_id=admin["tenant_id"],
|
|
actor_type="agent",
|
|
name="API Token Agent",
|
|
)
|
|
|
|
res = client.post(
|
|
f"/api/actors/{agent['id']}/service-tokens",
|
|
json={"label": "Cursor"},
|
|
headers=_auth(token),
|
|
)
|
|
assert res.status_code == 201
|
|
body = res.json()
|
|
assert body["token"].startswith(TOKEN_PREFIX)
|
|
assert body["actor_id"] == agent["id"]
|
|
|
|
listed = client.get(
|
|
f"/api/actors/{agent['id']}/service-tokens",
|
|
headers=_auth(token),
|
|
)
|
|
assert listed.status_code == 200
|
|
assert len(listed.json()) >= 1
|