416 lines
13 KiB
Python
416 lines
13 KiB
Python
"""Regression tests for tenant-scoped administration."""
|
|
from __future__ import annotations
|
|
|
|
|
|
def auth_headers(token: str) -> dict[str, str]:
|
|
return {"Authorization": f"Bearer {token}"}
|
|
|
|
|
|
def _create_org_admin(user_store, *, org_id: str, username: str, password: str):
|
|
return user_store.create_user(
|
|
org_id=org_id,
|
|
username=username,
|
|
password=password,
|
|
name=username,
|
|
role="admin",
|
|
email=f"{username}@example.com",
|
|
must_setup=False,
|
|
)
|
|
|
|
|
|
def test_tenant_admin_cannot_provision_into_another_org(
|
|
client, user_store, login
|
|
):
|
|
org_b = user_store.create_org("Organization B", org_id="org-b")
|
|
admin_a = _create_org_admin(
|
|
user_store, org_id="org-default", username="admin-a", password="admin-a-password"
|
|
)
|
|
token = login(admin_a["username"], "admin-a-password")["token"]
|
|
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "cross-org-user",
|
|
"name": "Cross Org User",
|
|
"password": "cross-org-password",
|
|
"role": "user",
|
|
"org_id": org_b["id"],
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 403, response.get_json()
|
|
assert user_store.get_user_or_none("cross-org-user") is None
|
|
|
|
|
|
def test_tenant_admin_can_provision_without_org_id_into_own_org(
|
|
client, user_store, login
|
|
):
|
|
admin_a = _create_org_admin(
|
|
user_store, org_id="org-default", username="admin-own", password="admin-own-password"
|
|
)
|
|
token = login(admin_a["username"], "admin-own-password")["token"]
|
|
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "own-org-user",
|
|
"name": "Own Org User",
|
|
"password": "own-org-password",
|
|
"role": "user",
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 201, response.get_json()
|
|
assert response.get_json()["org_id"] == "org-default"
|
|
assert user_store.get_user("own-org-user")["org_id"] == "org-default"
|
|
|
|
|
|
def test_tenant_admin_cannot_update_user_in_another_org(
|
|
client, user_store, login
|
|
):
|
|
org_b = user_store.create_org("Organization B", org_id="org-b")
|
|
admin_a = _create_org_admin(
|
|
user_store, org_id="org-default", username="admin-update", password="admin-update-password"
|
|
)
|
|
victim = user_store.create_user(
|
|
org_id=org_b["id"],
|
|
username="org-b-victim",
|
|
password="victim-password",
|
|
name="Org B Victim",
|
|
role="user",
|
|
email="org-b-victim@example.com",
|
|
must_setup=False,
|
|
)
|
|
before = dict(victim)
|
|
token = login(admin_a["username"], "admin-update-password")["token"]
|
|
|
|
response = client.put(
|
|
"/api/admin/users/org-b-victim",
|
|
json={"password": "attacker-password", "email": "attacker@example.com"},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code in (403, 404), response.get_json()
|
|
assert user_store.get_user("org-b-victim") == before
|
|
|
|
|
|
def test_tenant_admin_cannot_update_super_admin(
|
|
client, user_store, login
|
|
):
|
|
admin_a = _create_org_admin(
|
|
user_store, org_id="org-default", username="admin-no-root", password="admin-no-root-password"
|
|
)
|
|
super_admin_before = dict(user_store.get_user("admin"))
|
|
token = login(admin_a["username"], "admin-no-root-password")["token"]
|
|
|
|
response = client.put(
|
|
"/api/admin/users/admin",
|
|
json={"password": "attacker-password", "email": "attacker@example.com"},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code in (403, 404), response.get_json()
|
|
assert user_store.get_user("admin") == super_admin_before
|
|
|
|
|
|
def test_tenant_admin_cannot_change_role_or_active_state(
|
|
client, user_store, login
|
|
):
|
|
admin_a = _create_org_admin(
|
|
user_store, org_id="org-default", username="admin-role", password="admin-role-password"
|
|
)
|
|
target = user_store.create_user(
|
|
org_id="org-default",
|
|
username="same-org-user",
|
|
password="same-org-password",
|
|
name="Same Org User",
|
|
role="user",
|
|
must_setup=False,
|
|
)
|
|
before = dict(target)
|
|
token = login(admin_a["username"], "admin-role-password")["token"]
|
|
|
|
role_response = client.put(
|
|
"/api/admin/users/same-org-user",
|
|
json={"role": "admin"},
|
|
headers=auth_headers(token),
|
|
)
|
|
active_response = client.put(
|
|
"/api/admin/users/same-org-user",
|
|
json={"active": False},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert role_response.status_code == 403, role_response.get_json()
|
|
assert active_response.status_code == 403, active_response.get_json()
|
|
assert user_store.get_user("same-org-user") == before
|
|
|
|
|
|
def test_admin_password_update_rejects_weak_password_without_mutation(
|
|
client, user_store, login
|
|
):
|
|
admin = _create_org_admin(
|
|
user_store, org_id="org-default", username="admin-password-policy", password="admin-password-policy"
|
|
)
|
|
target = user_store.create_user(
|
|
org_id="org-default",
|
|
username="password-policy-target",
|
|
password="target-original-password",
|
|
name="Password Policy Target",
|
|
role="user",
|
|
must_setup=False,
|
|
)
|
|
before = user_store.get_user(target["username"])
|
|
token = login(admin["username"], "admin-password-policy")["token"]
|
|
|
|
response = client.put(
|
|
"/api/admin/users/password-policy-target",
|
|
json={"password": "12345678901"},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 400, response.get_json()
|
|
assert user_store.get_user(target["username"]) == before
|
|
|
|
|
|
def test_admin_profile_update_invalid_email_does_not_partially_change_password(
|
|
client, user_store, login
|
|
):
|
|
admin = _create_org_admin(
|
|
user_store, org_id="org-default", username="admin-profile-policy", password="admin-profile-policy"
|
|
)
|
|
target = user_store.create_user(
|
|
org_id="org-default",
|
|
username="profile-policy-target",
|
|
password="target-original-password",
|
|
name="Profile Policy Target",
|
|
role="user",
|
|
must_setup=False,
|
|
)
|
|
before = user_store.get_user(target["username"])
|
|
token = login(admin["username"], "admin-profile-policy")["token"]
|
|
|
|
response = client.put(
|
|
"/api/admin/users/profile-policy-target",
|
|
json={"password": "target-updated-password", "email": "invalid-email"},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 400, response.get_json()
|
|
assert user_store.get_user(target["username"]) == before
|
|
|
|
|
|
def test_new_org_weak_password_does_not_create_orphan_org(
|
|
client, user_store, login
|
|
):
|
|
user_store.complete_setup(
|
|
"admin",
|
|
"bootstrap@example.com",
|
|
"bootstrap-admin-password",
|
|
accepted_terms=True,
|
|
accepted_terms_at="2026-08-13T00:00:00Z",
|
|
)
|
|
token = login("admin", "bootstrap-admin-password")["token"]
|
|
org_ids_before = {org["id"] for org in user_store.orgs.all()}
|
|
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "weak-new-org-admin",
|
|
"password": "12345678901",
|
|
"role": "admin",
|
|
"new_org": True,
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 400, response.get_json()
|
|
assert user_store.get_user_or_none("weak-new-org-admin") is None
|
|
assert {org["id"] for org in user_store.orgs.all()} == org_ids_before
|
|
|
|
|
|
def test_new_org_invalid_email_does_not_create_orphan_org(
|
|
client, user_store, login
|
|
):
|
|
user_store.complete_setup(
|
|
"admin",
|
|
"bootstrap-email@example.com",
|
|
"bootstrap-admin-password",
|
|
accepted_terms=True,
|
|
accepted_terms_at="2026-08-13T00:00:00Z",
|
|
)
|
|
token = login("admin", "bootstrap-admin-password")["token"]
|
|
org_ids_before = {org["id"] for org in user_store.orgs.all()}
|
|
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "invalid-email-new-org-admin",
|
|
"email": "not-an-email",
|
|
"password": "valid-new-org-password",
|
|
"role": "admin",
|
|
"new_org": True,
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 400, response.get_json()
|
|
assert user_store.get_user_or_none("invalid-email-new-org-admin") is None
|
|
assert {org["id"] for org in user_store.orgs.all()} == org_ids_before
|
|
|
|
|
|
def test_new_org_non_string_email_does_not_create_orphan_org(
|
|
client, user_store, login
|
|
):
|
|
user_store.complete_setup(
|
|
"admin",
|
|
"bootstrap-type@example.com",
|
|
"bootstrap-admin-password",
|
|
accepted_terms=True,
|
|
accepted_terms_at="2026-08-13T00:00:00Z",
|
|
)
|
|
token = login("admin", "bootstrap-admin-password")["token"]
|
|
org_ids_before = {org["id"] for org in user_store.orgs.all()}
|
|
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "type-email-new-org-admin",
|
|
"email": 123,
|
|
"password": "valid-new-org-password",
|
|
"role": "admin",
|
|
"new_org": True,
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 400, response.get_json()
|
|
assert user_store.get_user_or_none("type-email-new-org-admin") is None
|
|
assert {org["id"] for org in user_store.orgs.all()} == org_ids_before
|
|
|
|
|
|
def test_admin_create_rejects_non_string_name_without_mutation(
|
|
client, user_store, login
|
|
):
|
|
user_store.complete_setup(
|
|
"admin",
|
|
"bootstrap-name@example.com",
|
|
"bootstrap-admin-password",
|
|
accepted_terms=True,
|
|
accepted_terms_at="2026-08-13T00:00:00Z",
|
|
)
|
|
token = login("admin", "bootstrap-admin-password")["token"]
|
|
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "non-string-name-user",
|
|
"name": 123,
|
|
"password": "valid-created-password",
|
|
"role": "user",
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 400, response.get_json()
|
|
assert user_store.get_user_or_none("non-string-name-user") is None
|
|
|
|
|
|
def test_new_org_unexpected_user_write_failure_rolls_back_org(
|
|
client, user_store, login, monkeypatch
|
|
):
|
|
user_store.complete_setup(
|
|
"admin",
|
|
"bootstrap-write@example.com",
|
|
"bootstrap-admin-password",
|
|
accepted_terms=True,
|
|
accepted_terms_at="2026-08-13T00:00:00Z",
|
|
)
|
|
token = login("admin", "bootstrap-admin-password")["token"]
|
|
org_ids_before = {org["id"] for org in user_store.orgs.all()}
|
|
|
|
def fail_user_write(**_kwargs):
|
|
raise RuntimeError("simulated persistence failure")
|
|
|
|
monkeypatch.setattr(user_store, "create_user", fail_user_write)
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "failed-new-org-admin",
|
|
"password": "valid-new-org-password",
|
|
"role": "admin",
|
|
"new_org": True,
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 500, response.get_json()
|
|
assert {org["id"] for org in user_store.orgs.all()} == org_ids_before
|
|
|
|
|
|
def test_super_admin_can_provision_into_existing_org(
|
|
client, user_store, login
|
|
):
|
|
org_b = user_store.create_org("Organization B", org_id="org-b")
|
|
user_store.complete_setup(
|
|
"admin",
|
|
"admin@example.com",
|
|
"bootstrap-password",
|
|
accepted_terms=True,
|
|
accepted_terms_at="2026-08-13T00:00:00Z",
|
|
)
|
|
token = login("admin", "bootstrap-password")["token"]
|
|
|
|
response = client.post(
|
|
"/api/admin/users",
|
|
json={
|
|
"username": "platform-created",
|
|
"name": "Platform Created",
|
|
"password": "platform-password",
|
|
"role": "user",
|
|
"org_id": org_b["id"],
|
|
},
|
|
headers=auth_headers(token),
|
|
)
|
|
|
|
assert response.status_code == 201, response.get_json()
|
|
assert user_store.get_user("platform-created")["org_id"] == org_b["id"]
|
|
|
|
|
|
def test_super_admin_group_listing_spans_all_orgs(client, user_store, login):
|
|
user_store.complete_setup(
|
|
"admin",
|
|
"platform@example.com",
|
|
"platform-admin-password",
|
|
accepted_terms=True,
|
|
accepted_terms_at="2026-08-13T00:00:00Z",
|
|
)
|
|
super_token = login("admin", "platform-admin-password")["token"]
|
|
org_b = user_store.create_org("Organization B", org_id="org-b")
|
|
org_b_admin = user_store.create_user(
|
|
org_id=org_b["id"],
|
|
username="org-b-group-admin",
|
|
password="org-b-group-password",
|
|
name="Org B Group Admin",
|
|
role="admin",
|
|
must_setup=False,
|
|
)
|
|
org_b_token = login(
|
|
org_b_admin["username"], "org-b-group-password"
|
|
)["token"]
|
|
|
|
created = client.post(
|
|
"/api/groups",
|
|
json={"product": "Org B product", "segment": "Org B segment"},
|
|
headers=auth_headers(org_b_token),
|
|
)
|
|
assert created.status_code == 201, created.get_json()
|
|
gid = created.get_json()["group"]["id"]
|
|
|
|
response = client.get("/api/groups", headers=auth_headers(super_token))
|
|
|
|
assert response.status_code == 200, response.get_json()
|
|
assert gid in {group["id"] for group in response.get_json()["groups"]}
|