68161298a4
- operator_users router now depends on _require_auth_enabled, which raises 404 when OPERATOR_AUTH_ENABLED is false — prevents world-open pre-seeding of a superadmin while the flag is off (the default). Flag is read as a live module attribute (operator_auth.OPERATOR_AUTH_ENABLED) so monkeypatching in tests and a runtime flip both take effect. - operator_gate passes OPTIONS requests through immediately before the exempt- path check, so CORS preflight reaches CORSMiddleware rather than being 303/401'd by the gate. - Two new tests: test_admin_surface_404s_when_flag_off (test_operator_users) and test_options_preflight_passes_through_gate (test_operator_gate). Full suite: 90 passed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
133 lines
6.6 KiB
Python
133 lines
6.6 KiB
Python
# tests/test_operator_users.py
|
|
import uuid
|
|
from tests.conftest import wire_operator_auth
|
|
from backend.operator_auth import create_operator, make_operator_cookie, COOKIE_NAME
|
|
from backend.models import OperatorUser
|
|
|
|
|
|
def _login_as(client, user):
|
|
client.cookies.set(COOKIE_NAME, make_operator_cookie(user.id))
|
|
|
|
|
|
def test_admin_cannot_reach_user_management(client, db_session, monkeypatch):
|
|
admin, _ = create_operator(db_session, "admin@x.com", "Admin", "admin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, admin)
|
|
assert client.get("/admin/users", follow_redirects=False).status_code == 403
|
|
|
|
|
|
def test_superadmin_sees_user_management(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
assert client.get("/admin/users", follow_redirects=False).status_code == 200
|
|
|
|
|
|
def test_superadmin_lists_users_json(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
r = client.get("/api/admin/users")
|
|
assert r.status_code == 200
|
|
emails = [u["email"] for u in r.json()["users"]]
|
|
assert "su@x.com" in emails
|
|
assert all("password_hash" not in u for u in r.json()["users"]) # never leak hashes
|
|
|
|
|
|
def test_create_user_returns_temp_once(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
r = client.post("/api/admin/users",
|
|
json={"email": "dad@x.com", "name": "Dad", "role": "admin"})
|
|
assert r.status_code == 200
|
|
assert len(r.json()["password"]) >= 12
|
|
made = db_session.query(OperatorUser).filter_by(email="dad@x.com").first()
|
|
assert made.must_change_password is True
|
|
|
|
|
|
def test_reset_password_returns_temp_once(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
target, _ = create_operator(db_session, "t@x.com", "T", "admin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
r = client.post(f"/api/admin/users/{target.id}/reset-password")
|
|
assert r.status_code == 200 and len(r.json()["password"]) >= 12
|
|
db_session.refresh(target)
|
|
assert target.must_change_password is True
|
|
|
|
|
|
def test_disable_and_enable(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
target, _ = create_operator(db_session, "t@x.com", "T", "admin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
assert client.post(f"/api/admin/users/{target.id}/disable").status_code == 200
|
|
db_session.refresh(target); assert target.active is False
|
|
assert client.post(f"/api/admin/users/{target.id}/enable").status_code == 200
|
|
db_session.refresh(target); assert target.active is True
|
|
|
|
|
|
def test_change_role(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
target, _ = create_operator(db_session, "t@x.com", "T", "admin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
r = client.post(f"/api/admin/users/{target.id}/role", json={"role": "superadmin"})
|
|
assert r.status_code == 200
|
|
db_session.refresh(target); assert target.role == "superadmin"
|
|
|
|
|
|
def test_admin_cannot_reach_json_endpoints(client, db_session, monkeypatch):
|
|
admin, _ = create_operator(db_session, "a@x.com", "A", "admin", password="pw-123456")
|
|
target, _ = create_operator(db_session, "t@x.com", "T", "admin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, admin)
|
|
assert client.get("/api/admin/users").status_code == 403
|
|
assert client.post("/api/admin/users", json={"email": "x@x.com", "name": "X", "role": "admin"}).status_code == 403
|
|
assert client.post(f"/api/admin/users/{target.id}/reset-password").status_code == 403
|
|
assert client.post(f"/api/admin/users/{target.id}/disable").status_code == 403
|
|
assert client.post(f"/api/admin/users/{target.id}/enable").status_code == 403
|
|
assert client.post(f"/api/admin/users/{target.id}/role", json={"role": "superadmin"}).status_code == 403
|
|
|
|
|
|
def test_cannot_disable_own_account(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
r = client.post(f"/api/admin/users/{su.id}/disable")
|
|
assert r.status_code == 400
|
|
db_session.refresh(su)
|
|
assert su.active is True
|
|
|
|
|
|
def test_cannot_change_own_role(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
r = client.post(f"/api/admin/users/{su.id}/role", json={"role": "admin"})
|
|
assert r.status_code == 400
|
|
db_session.refresh(su)
|
|
assert su.role == "superadmin"
|
|
|
|
|
|
def test_deferred_operator_role_rejected_by_api(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
target, _ = create_operator(db_session, "t@x.com", "T", "admin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=True)
|
|
_login_as(client, su)
|
|
assert client.post("/api/admin/users", json={"email": "op@x.com", "name": "Op", "role": "operator"}).status_code == 400
|
|
assert client.post(f"/api/admin/users/{target.id}/role", json={"role": "operator"}).status_code == 400
|
|
|
|
|
|
def test_admin_surface_404s_when_flag_off(client, db_session, monkeypatch):
|
|
su, _ = create_operator(db_session, "su@x.com", "Su", "superadmin", password="pw-123456")
|
|
wire_operator_auth(monkeypatch, db_session, enabled=False)
|
|
_login_as(client, su)
|
|
# With operator auth OFF, the management surface must not exist (404), even
|
|
# though require_role passes through — otherwise it'd be world-open.
|
|
assert client.get("/admin/users").status_code == 404
|
|
assert client.get("/api/admin/users").status_code == 404
|
|
assert client.post("/api/admin/users",
|
|
json={"email": "x@x.com", "name": "X", "role": "admin"}).status_code == 404
|