Files
GEWU/tests/api/test_credential_admin.py
T

103 lines
5.0 KiB
Python
Raw Normal View History

import asyncio
from pathlib import Path
import httpx2
from gewu.adapters.git_repository import GitCanonicalRepository
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
from gewu.application.reading import CanonicalNodeReader
from gewu.bootstrap.app import create_app
def test_admin_credentials_sessions_and_secret_free_audit(tmp_path: Path) -> None:
async def exercise() -> None:
store = SQLiteIdentityStore(tmp_path / "identity.sqlite3")
admin = store.provision(credential_id="admin", principal_id="operator", roles=("admin",))
viewer = store.provision(credential_id="viewer", principal_id="reader", roles=("viewer",))
mixed = store.provision(
credential_id="mixed", principal_id="agent", roles=("agent", "admin")
)
app = create_app(
identity_provider=store,
credential_administration=store,
node_reader=CanonicalNodeReader(GitCanonicalRepository(tmp_path / "knowledge.git")),
)
async with httpx2.AsyncClient(
transport=httpx2.ASGITransport(app=app), base_url="http://test"
) as http:
assert (await http.get("/v1/admin/credentials")).status_code == 401
for token in (viewer, mixed):
headers = {"Authorization": f"Bearer {token}"}
assert (await http.get("/v1/admin/credentials", headers=headers)).status_code == 403
assert (await http.get("/v1/admin/events", headers=headers)).status_code == 403
assert (
await http.post(
"/v1/admin/credentials",
headers=headers,
json={
"credential_id": "denied",
"principal_id": "denied",
"role": "admin",
},
)
).status_code == 403
assert (
await http.delete("/v1/admin/credentials/viewer", headers=headers)
).status_code == 403
session = await http.post("/v1/session", headers={"Authorization": f"Bearer {admin}"})
csrf = {"X-GEWU-CSRF": session.json()["csrf_token"]}
body = {
"credential_id": "new",
"principal_id": "researcher",
"role": "agent",
"project_ids": ["pilot"],
}
assert (await http.post("/v1/admin/credentials", json=body)).status_code == 403
assert (
await http.post(
"/v1/admin/credentials", json=body, headers={**csrf, "Origin": "https://other"}
)
).status_code == 403
assert (
await http.post("/v1/admin/credentials", headers=csrf, content=b" " * 4097)
).status_code == 413
assert (
await http.post(
"/v1/admin/credentials", headers=csrf, json={**body, "project_ids": []}
)
).status_code == 422
created = await http.post("/v1/admin/credentials", headers=csrf, json=body)
assert created.status_code == 201
token = created.json()["token"]
principal = store.authenticate(token)
assert principal and principal.project_ids == frozenset({"pilot"})
assert (
await http.post("/v1/admin/credentials", headers=csrf, json=body)
).status_code == 409
listing = await http.get("/v1/admin/credentials")
assert listing.headers["cache-control"] == "private, no-store"
assert token not in listing.text and "token_digest" not in listing.text
assert (
await http.delete("/v1/admin/credentials/admin", headers=csrf)
).status_code == 409
assert (await http.delete("/v1/admin/credentials/new")).status_code == 403
assert (await http.delete("/v1/admin/credentials/new", headers=csrf)).status_code == 200
assert store.authenticate(token) is None
assert (await http.delete("/v1/admin/credentials/new", headers=csrf)).status_code == 404
# Revocation invalidates already-issued browser sessions as well as bearer keys.
viewer_session = store.create_browser_session(viewer)
assert (
await http.delete("/v1/admin/credentials/viewer", headers=csrf)
).status_code == 200
assert store.authenticate_browser_session(viewer_session.session_token) is None
events = await http.get("/v1/admin/events")
assert [e["action"] for e in events.json()] == [
"credential.revoked",
"credential.revoked",
"credential.created",
]
assert token not in events.text and admin not in events.text
reopened = SQLiteIdentityStore(store.database)
assert len(reopened.list_admin_events(limit=50)) == 3
asyncio.run(exercise())