aps-agent/tests/golden/test_auth_tenant_isolation.py

115 lines
4.5 KiB
Python
Raw Normal View History

from __future__ import annotations
import asyncio
import pytest
from fastapi.testclient import TestClient
@pytest.fixture()
def secure_app(tmp_path, monkeypatch):
monkeypatch.setenv("APS_AUTH_PROVIDER", "mock")
monkeypatch.setenv("APS_MOCK_TENANT_UUID", "tenant-a-000000000000000000000001")
monkeypatch.setenv("APS_MOCK_AUTH_SECRET", "test-secret")
monkeypatch.setenv("APS_DB_PATH", str(tmp_path / "tenant.db"))
monkeypatch.setenv("APS_WORLD_PATH", str(tmp_path / "world.json"))
from server.auth import providers
from server.db.database import reset_engine
from server.state import store as world_store
providers._provider = None
providers._provider_mode = None
world_store._stores.clear()
reset_engine()
from server.gateway.app import create_app
app = create_app()
yield app
reset_engine()
world_store._stores.clear()
providers._provider = None
providers._provider_mode = None
def login(client: TestClient, username: str) -> None:
response = client.post("/api/auth/login", json={
"method": "password", "username": username, "password": "test",
})
assert response.status_code == 200
def test_anonymous_business_routes_are_closed(secure_app):
client = TestClient(secure_app)
assert client.get("/api/health").status_code == 200
assert client.get("/api/workspace").status_code == 401
assert client.get("/api/world/summary").status_code == 401
assert client.post("/api/chat", json={"text": "hello"}).status_code == 401
def test_shared_project_is_collaborative_but_personal_chat_is_private(secure_app):
owner = TestClient(secure_app)
editor = TestClient(secure_app)
login(owner, "planner")
login(editor, "collaborator")
owner_workspace = owner.get("/api/workspace").json()
personal_session = next(row for row in owner_workspace["sessions"] if row["scope"] == "personal")
assert owner.put(
f"/api/sessions/{personal_session['id']}/messages",
json={"messages": [{"role": "user", "text": "private"}]},
).status_code == 200
assert editor.get(f"/api/sessions/{personal_session['id']}/messages").status_code == 404
created = owner.post("/api/projects", json={"id": "proj_shared", "name": "协作项目"})
assert created.status_code == 200
project_session = created.json()["session"]
invited = owner.post(
"/api/projects/proj_shared/members",
json={"userId": 1002, "role": "editor"},
)
assert invited.status_code == 200
shared = editor.get("/api/workspace").json()
assert [(row["id"], row["role"]) for row in shared["projects"]] == [("proj_shared", "editor")]
assert any(row["id"] == project_session["id"] for row in shared["sessions"])
assert editor.put(
f"/api/sessions/{project_session['id']}/messages",
json={"messages": [{"role": "user", "text": "shared"}]},
).status_code == 200
messages = owner.get(f"/api/sessions/{project_session['id']}/messages").json()["messages"]
assert messages == [{"role": "user", "text": "shared"}]
def test_viewer_cannot_modify_project(secure_app):
owner = TestClient(secure_app)
viewer = TestClient(secure_app)
login(owner, "planner")
login(viewer, "viewer")
owner.post("/api/projects", json={"id": "proj_readonly", "name": "只读项目"})
owner.post(
"/api/projects/proj_readonly/members",
json={"userId": 1003, "role": "viewer"},
)
assert viewer.get("/api/projects/proj_readonly/members").status_code == 200
assert viewer.patch("/api/projects/proj_readonly", json={"name": "越权修改"}).status_code == 403
assert viewer.post(
"/api/sessions", json={"projectId": "proj_readonly", "title": "越权会话"},
).status_code == 403
def test_cross_tenant_token_cannot_access_known_project_id(secure_app, monkeypatch):
owner = TestClient(secure_app)
login(owner, "planner")
owner.post("/api/projects", json={"id": "proj_tenant_a", "name": "租户 A"})
monkeypatch.setenv("APS_MOCK_TENANT_UUID", "tenant-b-000000000000000000000001")
from server.auth.providers import MockAuthProvider
_, token = asyncio.run(MockAuthProvider().login({"username": "collaborator"}))
tenant_b = TestClient(secure_app)
tenant_b.cookies.set("aps_session", token)
workspace = tenant_b.get("/api/workspace")
assert workspace.status_code == 200
assert workspace.json()["projects"] == []
assert tenant_b.get("/api/projects/proj_tenant_a/members").status_code == 404
assert tenant_b.patch("/api/projects/proj_tenant_a", json={"name": "越权"}).status_code == 403