This commit is contained in:
1 parent
4db3f45164
commit
d25c04cdd2
175 files changed
+56243
-15
No files matched your search
@@ -0,0 +1,264 @@
|
||||
import asyncio
|
||||
import sqlite3
|
||||
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
|
||||
|
||||
PASSWORD = "initial-password-fixture"
|
||||
|
||||
|
||||
def test_password_accounts_owned_agent_keys_and_session_lifecycle(tmp_path: Path) -> None:
|
||||
async def exercise() -> None:
|
||||
store = SQLiteIdentityStore(tmp_path / "identity.sqlite3")
|
||||
operator = store.provision(
|
||||
credential_id="operator", principal_id="operator", roles=("admin",)
|
||||
)
|
||||
app = create_app(
|
||||
identity_provider=store,
|
||||
account_identity=store,
|
||||
credential_administration=store,
|
||||
node_reader=CanonicalNodeReader(GitCanonicalRepository(tmp_path / "knowledge.git")),
|
||||
)
|
||||
transport = httpx2.ASGITransport(app=app)
|
||||
operator_headers = {"Authorization": f"Bearer {operator}"}
|
||||
async with httpx2.AsyncClient(transport=transport, base_url="https://gewu.test") as admin:
|
||||
body = {
|
||||
"username": "alice",
|
||||
"password": PASSWORD,
|
||||
"role": "editor",
|
||||
"project_ids": ["pilot"],
|
||||
}
|
||||
assert (await admin.post("/v1/admin/users", json=body)).status_code == 401
|
||||
created = await admin.post("/v1/admin/users", json=body, headers=operator_headers)
|
||||
assert created.status_code == 201 and PASSWORD not in created.text
|
||||
assert (
|
||||
await admin.post("/v1/admin/users", json=body, headers=operator_headers)
|
||||
).status_code == 409
|
||||
for username, role in (("bob", "editor"), ("viewer", "viewer"), ("admin", "admin")):
|
||||
assert (
|
||||
await admin.post(
|
||||
"/v1/admin/users",
|
||||
headers=operator_headers,
|
||||
json={**body, "username": username, "role": role},
|
||||
)
|
||||
).status_code == 201
|
||||
short_secret = "shortsecret"
|
||||
invalid = await admin.post(
|
||||
"/v1/admin/users",
|
||||
headers=operator_headers,
|
||||
json={**body, "username": "invalid", "password": short_secret},
|
||||
)
|
||||
assert invalid.status_code == 422 and short_secret not in invalid.text
|
||||
listing = await admin.get("/v1/admin/users", headers=operator_headers)
|
||||
assert "password_hash" not in listing.text and PASSWORD not in listing.text
|
||||
|
||||
async with httpx2.AsyncClient(
|
||||
transport=transport, base_url="https://gewu.test"
|
||||
) as alice:
|
||||
login = await alice.post(
|
||||
"/v1/users/session",
|
||||
json={"username": "alice", "password": PASSWORD},
|
||||
headers={"Origin": "https://gewu.test"},
|
||||
)
|
||||
assert login.status_code == 200
|
||||
assert "secure" in login.headers["set-cookie"].lower()
|
||||
csrf = {"X-GEWU-CSRF": login.json()["csrf_token"]}
|
||||
assert (await alice.get("/v1/me")).json()["principal_id"] == "user:alice"
|
||||
assert (await alice.get("/v1/admin/users")).status_code == 403
|
||||
assert (
|
||||
await alice.post("/v1/admin/users", headers=csrf, json=body)
|
||||
).status_code == 403
|
||||
agent_body = {"name": "codex-laptop", "project_ids": ["pilot"]}
|
||||
assert (
|
||||
await alice.post("/v1/me/agent-credentials", json=agent_body)
|
||||
).status_code == 403
|
||||
assert (
|
||||
await alice.post(
|
||||
"/v1/me/agent-credentials",
|
||||
headers={**csrf, "Origin": "https://attacker"},
|
||||
json=agent_body,
|
||||
)
|
||||
).status_code == 403
|
||||
assert (
|
||||
await alice.post(
|
||||
"/v1/me/agent-credentials",
|
||||
headers=csrf,
|
||||
json={**agent_body, "project_ids": ["other"]},
|
||||
)
|
||||
).status_code == 403
|
||||
assert (
|
||||
await alice.post(
|
||||
"/v1/me/agent-credentials",
|
||||
headers=csrf,
|
||||
json={**agent_body, "role": "admin"},
|
||||
)
|
||||
).status_code == 422
|
||||
agent = await alice.post("/v1/me/agent-credentials", headers=csrf, json=agent_body)
|
||||
assert agent.status_code == 201
|
||||
key_id, token = agent.json()["credential_id"], agent.json()["token"]
|
||||
agent_principal = store.authenticate(token)
|
||||
assert agent_principal and agent_principal.can_submit_project("pilot")
|
||||
assert not agent_principal.can_submit_project("other")
|
||||
assert (await alice.get("/v1/me/agent-credentials")).json()[0][
|
||||
"credential_id"
|
||||
] == key_id
|
||||
assert token not in (await alice.get("/v1/me/agent-credentials")).text
|
||||
assert (
|
||||
await alice.get("/v1/me", headers={"Authorization": f"Bearer {token}"})
|
||||
).status_code == 403
|
||||
|
||||
async with httpx2.AsyncClient(
|
||||
transport=transport, base_url="https://gewu.test"
|
||||
) as bob:
|
||||
bob_login = await bob.post(
|
||||
"/v1/users/session", json={"username": "bob", "password": PASSWORD}
|
||||
)
|
||||
bob_csrf = {"X-GEWU-CSRF": bob_login.json()["csrf_token"]}
|
||||
assert (await bob.get("/v1/me/agent-credentials")).json() == []
|
||||
assert (
|
||||
await bob.delete(f"/v1/me/agent-credentials/{key_id}", headers=bob_csrf)
|
||||
).status_code == 404
|
||||
assert store.authenticate(token) is not None
|
||||
bob_agent = await bob.post(
|
||||
"/v1/me/agent-credentials", headers=bob_csrf, json=agent_body
|
||||
)
|
||||
rotated = store.rotate(bob_agent.json()["credential_id"], "rotated-bob-key")
|
||||
assert (await bob.get("/v1/me/agent-credentials")).json()[-1][
|
||||
"owner_principal_id"
|
||||
] == "user:bob"
|
||||
assert (
|
||||
await admin.delete(
|
||||
"/v1/admin/credentials/account:bob", headers=operator_headers
|
||||
)
|
||||
).status_code == 200
|
||||
assert store.authenticate(rotated) is None
|
||||
assert (await bob.get("/v1/me")).status_code == 401
|
||||
|
||||
password_body = {
|
||||
"current_password": PASSWORD,
|
||||
"new_password": "changed-password-fixture",
|
||||
}
|
||||
assert (await alice.post("/v1/me/password", json=password_body)).status_code == 403
|
||||
assert (
|
||||
await alice.post(
|
||||
"/v1/me/password",
|
||||
headers=csrf,
|
||||
json={**password_body, "current_password": "wrong"},
|
||||
)
|
||||
).status_code == 403
|
||||
second_session = store.login_account("alice", PASSWORD)
|
||||
assert second_session
|
||||
changed = await alice.post("/v1/me/password", headers=csrf, json=password_body)
|
||||
assert changed.status_code == 200
|
||||
assert store.authenticate_browser_session(second_session.session_token) is None
|
||||
assert (await alice.get("/v1/me")).status_code == 401
|
||||
assert store.login_account("alice", PASSWORD) is None
|
||||
assert store.authenticate(token) is not None
|
||||
relogin = await alice.post(
|
||||
"/v1/users/session",
|
||||
json={"username": "alice", "password": password_body["new_password"]},
|
||||
)
|
||||
csrf = {"X-GEWU-CSRF": relogin.json()["csrf_token"]}
|
||||
assert (
|
||||
await alice.delete(f"/v1/me/agent-credentials/{key_id}", headers=csrf)
|
||||
).status_code == 200
|
||||
assert store.authenticate(token) is None
|
||||
another = await alice.post(
|
||||
"/v1/me/agent-credentials", headers=csrf, json=agent_body
|
||||
)
|
||||
assert (
|
||||
await admin.delete("/v1/admin/users/alice", headers=operator_headers)
|
||||
).status_code == 200
|
||||
assert store.authenticate(another.json()["token"]) is None
|
||||
assert (await alice.get("/v1/me")).status_code == 401
|
||||
assert store.login_account("alice", password_body["new_password"]) is None
|
||||
|
||||
async with httpx2.AsyncClient(
|
||||
transport=transport, base_url="https://gewu.test"
|
||||
) as viewer:
|
||||
login = await viewer.post(
|
||||
"/v1/users/session", json={"username": "viewer", "password": PASSWORD}
|
||||
)
|
||||
csrf = {"X-GEWU-CSRF": login.json()["csrf_token"]}
|
||||
assert (
|
||||
await viewer.post(
|
||||
"/v1/me/agent-credentials",
|
||||
headers=csrf,
|
||||
json={"name": "agent", "project_ids": ["pilot"]},
|
||||
)
|
||||
).status_code == 403
|
||||
async with httpx2.AsyncClient(
|
||||
transport=transport, base_url="https://gewu.test"
|
||||
) as human_admin:
|
||||
login = await human_admin.post(
|
||||
"/v1/users/session", json={"username": "admin", "password": PASSWORD}
|
||||
)
|
||||
csrf = {"X-GEWU-CSRF": login.json()["csrf_token"]}
|
||||
assert (
|
||||
await human_admin.delete("/v1/admin/users/admin", headers=csrf)
|
||||
).status_code == 403
|
||||
with sqlite3.connect(store.database) as db:
|
||||
assert (
|
||||
db.execute("SELECT password_hash FROM user_accounts WHERE username='bob'")
|
||||
.fetchone()[0]
|
||||
.startswith("$argon2id$")
|
||||
)
|
||||
assert (
|
||||
PASSWORD not in (await admin.get("/v1/admin/events", headers=operator_headers)).text
|
||||
)
|
||||
assert len(SQLiteIdentityStore(store.database).list_accounts(limit=50, offset=0)) == 4
|
||||
|
||||
asyncio.run(exercise())
|
||||
|
||||
|
||||
def test_password_login_origin_tls_size_and_throttle(tmp_path: Path) -> None:
|
||||
async def exercise() -> None:
|
||||
store = SQLiteIdentityStore(tmp_path / "identity.sqlite3")
|
||||
store.create_account(
|
||||
username="alice",
|
||||
password=PASSWORD,
|
||||
role="editor",
|
||||
project_ids=("pilot",),
|
||||
actor="operator",
|
||||
)
|
||||
app = create_app(
|
||||
identity_provider=store,
|
||||
account_identity=store,
|
||||
node_reader=CanonicalNodeReader(GitCanonicalRepository(tmp_path / "knowledge.git")),
|
||||
)
|
||||
async with httpx2.AsyncClient(
|
||||
transport=httpx2.ASGITransport(app=app), base_url="http://gewu.test"
|
||||
) as http:
|
||||
body = {"username": "alice", "password": PASSWORD}
|
||||
assert (await http.post("/v1/users/session", json=body)).status_code == 403
|
||||
assert (
|
||||
await http.post(
|
||||
"https://gewu.test/v1/users/session",
|
||||
json=body,
|
||||
headers={"Origin": "https://attacker.invalid"},
|
||||
)
|
||||
).status_code == 403
|
||||
assert (
|
||||
await http.post("https://gewu.test/v1/users/session", content=b" " * 4097)
|
||||
).status_code == 413
|
||||
for _ in range(5):
|
||||
assert (
|
||||
await http.post(
|
||||
"https://gewu.test/v1/users/session", json={**body, "password": "wrong"}
|
||||
)
|
||||
).status_code == 401
|
||||
assert (
|
||||
await http.post("https://gewu.test/v1/users/session", json=body)
|
||||
).status_code == 401
|
||||
with store._connect() as db:
|
||||
db.execute("UPDATE account_login_failures SET failed_at=0")
|
||||
assert (
|
||||
await http.post("https://gewu.test/v1/users/session", json=body)
|
||||
).status_code == 200
|
||||
|
||||
asyncio.run(exercise())
|
||||
Reference in new issue
Block a user