| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- """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")
- # Spry's change-own-password revokes the user's auth slots: the
- # seeded handle above dies with the old password, so re-login and
- # use the fresh slot for everything after this point.
- admin = 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")
|