fix(auth): becoming admin through ADMIN_EMAILS drops the password, on any sign-in
The promotion revoke guessed from today's SMTP state whether the password was trustworthy, and ran only when the account was adopted. Both guesses failed: a row verified while SMTP was off stays "verified" after SMTP is configured, and a later sign-in matched by Google identity promoted the account with no revoke at all. Either way a stranger's password and session could become admin. Now one rule, where the promotion happens: when ADMIN_EMAILS turns a non-admin account into an admin and it holds a password, the password goes and every session ends; the owner, signed in through Google at that moment, sets a new one in Settings. An account that already is admin keeps its password. Tests: the activated squat, the SMTP case, the later sign-in (all three red without the rule) and an admin's own sign-in.
This commit is contained in:
@@ -59,7 +59,7 @@ def test_a_no_smtp_squat_of_an_admin_address_loses_its_password(client, db, goog
|
||||
# does. Without SMTP the row is marked verified, and an env-allowed address gets no invite row
|
||||
# to gate it — so the owner's first Google sign-in activates THIS row as admin. The squatter's
|
||||
# password must not survive that.
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: False)
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: False)
|
||||
monkeypatch.setattr(auth_routes, "notify_admins_of_request", lambda *_a, **_k: None)
|
||||
monkeypatch.setattr(settings, "admin_emails", "bob@gmail.com")
|
||||
auth_routes._register_account("b.ob@gmail.com", "the squatter's own")
|
||||
@@ -87,7 +87,7 @@ def test_an_activated_squat_loses_its_password_when_the_sign_in_makes_it_admin(
|
||||
# The same squat after an admin clicked Activate on it: activation vouches for access, not for
|
||||
# who chose the password, and without SMTP nothing else ever proved the mailbox. ADMIN_EMAILS
|
||||
# must not raise the squatter's live session to admin.
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: False)
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: False)
|
||||
monkeypatch.setattr(settings, "admin_emails", "bob@gmail.com")
|
||||
squat = make_user(email="b.ob@gmail.com", password="the squatter's own", is_active=True)
|
||||
squatter = client.__class__(client.app)
|
||||
@@ -107,18 +107,46 @@ def test_an_activated_squat_loses_its_password_when_the_sign_in_makes_it_admin(
|
||||
assert squatter.get("/api/me").json() == {"authenticated": False}
|
||||
|
||||
|
||||
def test_with_smtp_a_verified_account_made_admin_keeps_its_password(
|
||||
def test_with_smtp_too_an_account_made_admin_loses_its_password(
|
||||
client, db, google, make_user, monkeypatch
|
||||
):
|
||||
# With SMTP the verified flag is a proven mailbox, so the promotion takes nothing away.
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: True)
|
||||
# SMTP configured now says nothing about a row verified before it was: the rule does not ask.
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: True)
|
||||
monkeypatch.setattr(settings, "admin_emails", "own@example.com")
|
||||
user = make_user(email="own@example.com", password=_PW, is_active=True)
|
||||
|
||||
_sign_in(client, google, sub="sub-own", email="own@example.com")
|
||||
|
||||
db.refresh(user)
|
||||
assert user.role == "admin" and user.password_hash is not None
|
||||
assert user.role == "admin" and user.password_hash is None
|
||||
# …and the owner, signed in through Google, is not thrown out by the epoch bump.
|
||||
assert client.get("/api/me").json()["id"] == user.id
|
||||
|
||||
|
||||
def test_a_later_sign_in_that_makes_the_account_admin_drops_its_password(
|
||||
client, db, google, make_user, monkeypatch
|
||||
):
|
||||
# Adopted while the address was only allowed (the password kept: nothing promoted it), then
|
||||
# added to ADMIN_EMAILS: the promotion happens on a sign-in matched by Google identity.
|
||||
user = make_user(email="later@example.com", password=_PW, is_active=True, google_sub="sub-l")
|
||||
monkeypatch.setattr(settings, "admin_emails", "later@example.com")
|
||||
|
||||
_sign_in(client, google, sub="sub-l", email="later@example.com")
|
||||
|
||||
db.refresh(user)
|
||||
assert user.role == "admin"
|
||||
assert user.password_hash is None and user.password_revoked_at is not None
|
||||
|
||||
|
||||
def test_an_admin_sign_in_keeps_an_admin_password(client, db, google, make_user, monkeypatch):
|
||||
# Only the promotion drops it: an account that is already admin signs in as before.
|
||||
user = make_user(email="boss@example.com", password=_PW, role="admin", google_sub="sub-b")
|
||||
monkeypatch.setattr(settings, "admin_emails", "boss@example.com")
|
||||
|
||||
_sign_in(client, google, sub="sub-b", email="boss@example.com")
|
||||
|
||||
db.refresh(user)
|
||||
assert user.password_hash is not None
|
||||
|
||||
|
||||
def test_adopting_an_account_already_let_in_keeps_its_password(
|
||||
@@ -126,7 +154,7 @@ def test_adopting_an_account_already_let_in_keeps_its_password(
|
||||
):
|
||||
# The revoke is for passwords nobody vouched for; an active, verified account that this sign-in
|
||||
# does not make admin keeps its own.
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: False)
|
||||
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: False)
|
||||
user = make_user(email="kept@example.com", password=_PW, is_active=True)
|
||||
monkeypatch.setattr(settings, "allowed_emails", "kept@example.com")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user