"""The users bundle: the user-management page, roles and the profile page (the roles decision: normal users manage projects, tasks, goals and rules; admins additionally manage users, saddles and capabilities).""" from harness import * # noqa: F401,F403 def run(ctx): admin = ctx.auth check(True, "login yields an auth slot") print("== users page: admin CRUD + newest-first ==") users_state = slot_public(admin, "/users", "users") check(users_state["create"] is not None, "admin sees the create action") seeded = [u for u in users_state["users"] if u["username"] == "admin"] check(len(seeded) == 1 and seeded[0]["is_admin"] and seeded[0]["is_me"], "seeded admin row carries the admin badge and is_me") result = act(admin, users_state["create"], { "username": "bob", "email": "bob@example.com", "forename": "Bob", "surname": "Builder", "date_of_birth": "1990-05-05", "password": "bobpass1", "confirm_password": "bobpass1"}) check("created" in (action_notify(result) or ""), "create bob") result = act(admin, users_state["create"], { "username": "carol", "email": "carol@example.com", "forename": "Carol", "surname": "Coda", "date_of_birth": "1985-03-03", "password": "carolpass", "confirm_password": "carolpass", "admin": "1"}) check("created" in (action_notify(result) or ""), "create carol as admin") users_state = slot_public(admin, "/users", "users") order = [u["username"] for u in users_state["users"]] check(order == ["carol", "bob", "admin"], "users table is newest-first: %s" % order) bob = [u for u in users_state["users"] if u["username"] == "bob"][0] carol = [u for u in users_state["users"] if u["username"] == "carol"][0] me_row = [u for u in users_state["users"] if u["is_me"]][0] check(bob["is_admin"] is False, "bob has the user badge") check(carol["is_admin"] is True, "carol has the admin badge") # The serialiser drops null properties: a row carries exactly one # of grant/revoke, whichever applies to its current role. check(bob.get("grant_admin") is not None and "revoke_admin" not in bob, "non-admin row carries grant (not revoke)") check(carol.get("revoke_admin") is not None and "grant_admin" not in carol, "admin row carries revoke (not grant)") check(me_row["revoke_admin"] is not None, "own row carries revoke (server refuses it)") print("== roles: non-admin refused on /users and the admin mutations ==") bob_auth = login("bob", "bobpass1") frames = directives("/users", bob_auth) navs = [d.get("uri") for d in frames if d.get("type") == "navigate"] check(navs == ["/login"], "non-admin /users entrypoint navigates to /login") # The saddles page itself stays OPEN to the non-admin; only its # mutations are admin-gated (the roles decision). saddles_state = slot_public(bob_auth, "/saddles", "saddles") check(saddles_state["is_admin"] is False, "saddles page open, controls hidden for non-admin") result = act(bob_auth, saddles_state["register"], {"name": "x", "host": "h:1"}) check(any(d.get("type") == "navigate" for d in result), "non-admin register saddle refused") result = act(bob_auth, saddles_state["create_capability"], {"name": "nope"}) check(any(d.get("type") == "navigate" for d in result), "non-admin create capability refused") # The admin path stays green after the gates. saddles_state = slot_public(admin, "/saddles", "saddles") result = act(admin, saddles_state["create_capability"], {"name": "gpu"}) check("created" in (action_notify(result) or ""), "admin creates a capability") print("== self-protection guards ==") result = act(admin, me_row["delete"], {}) check("own account" in (action_notify(result) or ""), "self-delete refused") result = act(admin, me_row["set_enabled"], {}) check("own account" in (action_notify(result) or ""), "self-disable refused") result = act(admin, me_row["revoke_admin"], {}) check("own admin" in (action_notify(result) or ""), "self-demote refused") result = act(admin, me_row["alter"], { "username": "admin", "email": "admin@example.com", "forename": "Admin", "surname": "User", "date_of_birth": "1980-01-01", "enabled": "1"}) check("own account" in (action_notify(result) or ""), "self-edit refused (belongs on /profile)") print("== admin edits another user: rename + reset password ==") result = act(admin, bob["alter"], { "username": "bob", "email": "bob2@example.com", "forename": "Bob", "surname": "B", "date_of_birth": "1990-05-05", "enabled": "1", "new_password": "bobnewpass"}) check("saved" in (action_notify(result) or ""), "edit bob (rename + reset password)") login("bob", "bobnewpass") check(True, "bob logs in with the reset password") # disable: the session dies on the next guarded request and the # login is refused. users_state = slot_public(admin, "/users", "users") bob = [u for u in users_state["users"] if u["username"] == "bob"][0] result = act(admin, bob["set_enabled"], {}) check("disabled" in (action_notify(result) or ""), "disable bob") frames = directives("/projects", bob_auth) navs = [d.get("uri") for d in frames if d.get("type") == "navigate"] check(navs == ["/login"], "disabled bob's entrypoints bounce to /login") result = act(admin, bob["delete"], {}) check("deleted" in (action_notify(result) or ""), "delete bob") print("== role toggles on another user ==") result = act(admin, carol["revoke_admin"], {}) check("revoked" in (action_notify(result) or ""), "demote carol (admin remains)") users_state = slot_public(admin, "/users", "users") carol = [u for u in users_state["users"] if u["username"] == "carol"][0] check(carol["is_admin"] is False, "carol's badge flips to user") result = act(admin, carol["grant_admin"], {}) check("granted" in (action_notify(result) or ""), "re-grant carol admin") print("== profile: every user changes their own password ==") profile = slot_public(admin, "/profile", "profile") check(profile["username"] == "admin" and profile["is_admin"] is True, "profile shows the own row with the role") result = act(admin, profile["change_password"], { "current_password": "WRONG", "new_password": "newpass9", "confirm_password": "newpass9"}) check("incorrect" in (action_notify(result) or ""), "wrong current password refused") result = act(admin, profile["change_password"], { "current_password": "adminpass123", "new_password": "newpass9", "confirm_password": "newpass9"}) check("changed" in (action_notify(result) or ""), "password changed") login("admin", "newpass9") check(True, "admin logs in with the new password") carol_auth = login("carol", "carolpass") carol_profile = slot_public(carol_auth, "/profile", "profile") check(carol_profile["username"] == "carol", "carol's profile shows her row") result = act(carol_auth, carol_profile["change_password"], { "current_password": "carolpass", "new_password": "carolnew1", "confirm_password": "carolnew1"}) check("changed" in (action_notify(result) or ""), "carol changes her password") login("carol", "carolnew1") check(True, "carol logs in with the new password") # The seeded admin password has changed mid-bundle; later login # helpers (if any re-login) use the new one. print("== cleanup: single admin remains ==") users_state = slot_public(admin, "/users", "users") carol = [u for u in users_state["users"] if u["username"] == "carol"][0] result = act(admin, carol["revoke_admin"], {}) check("revoked" in (action_notify(result) or ""), "demote carol before delete") result = act(admin, carol["delete"], {}) check("deleted" in (action_notify(result) or ""), "delete carol") check([u["username"] for u in slot_public(admin, "/users", "users")["users"]] == ["admin"], "the admin remains the sole user")