From 85ebc72e8af542d73909b6f58f9bfb3b4f40ccd3 Mon Sep 17 00:00:00 2001
From: Kevin Morris
Date: Mon, 8 Nov 2021 18:18:41 -0800
Subject: [PATCH] fix(fastapi): only elevated users are allowed to suspend
accounts
Signed-off-by: Kevin Morris
---
aurweb/auth.py | 3 ++
aurweb/routers/accounts.py | 7 +++-
po/aurweb.pot | 4 +++
templates/partials/account_form.html | 18 +++++-----
test/test_accounts_routes.py | 49 ++++++++++++++++++++++++++++
5 files changed, 72 insertions(+), 9 deletions(-)
diff --git a/aurweb/auth.py b/aurweb/auth.py
index 5e45ee83..38754db0 100644
--- a/aurweb/auth.py
+++ b/aurweb/auth.py
@@ -51,6 +51,9 @@ class AnonymousUser:
LangPreference = aurweb.config.get("options", "default_lang")
Timezone = aurweb.config.get("options", "default_timezone")
+ Suspended = 0
+ InactivityTS = 0
+
# A stub ssh_pub_key relationship.
ssh_pub_key = None
diff --git a/aurweb/routers/accounts.py b/aurweb/routers/accounts.py
index 152b0a15..498568ad 100644
--- a/aurweb/routers/accounts.py
+++ b/aurweb/routers/accounts.py
@@ -143,6 +143,10 @@ def process_account_form(request: Request, user: models.User, args: dict):
if not email or not username:
return (False, ["Missing a required field."])
+ inactive = args.get("J", False)
+ if not request.user.is_elevated() and inactive != bool(user.InactivityTS):
+ return (False, ["You do not have permission to suspend accounts."])
+
username_min_len = aurweb.config.getint("options", "username_min_len")
username_max_len = aurweb.config.getint("options", "username_max_len")
if not util.valid_username(args.get("U")):
@@ -528,7 +532,8 @@ async def account_edit_post(request: Request,
user.Homepage = HP or user.Homepage
user.IRCNick = I or user.IRCNick
user.PGPKey = K or user.PGPKey
- user.InactivityTS = datetime.utcnow().timestamp() if J else 0
+ user.Suspended = J
+ user.InactivityTS = int(datetime.utcnow().timestamp()) * int(J)
# If we update the language, update the cookie as well.
if L and L != user.LangPreference:
diff --git a/po/aurweb.pot b/po/aurweb.pot
index 721f874e..f4deee70 100644
--- a/po/aurweb.pot
+++ b/po/aurweb.pot
@@ -879,6 +879,10 @@ msgstr ""
msgid "Account suspended"
msgstr ""
+#: aurweb/routers/accounts.py
+msgid "You do not have permission to suspend accounts."
+msgstr ""
+
#: lib/acctfuncs.inc.php
#, php-format
msgid ""
diff --git a/templates/partials/account_form.html b/templates/partials/account_form.html
index f166c230..2e47a932 100644
--- a/templates/partials/account_form.html
+++ b/templates/partials/account_form.html
@@ -42,14 +42,16 @@
"account is inactive." | tr }}
-
-
-
-
+ {% if request.user.is_elevated() %}
+
+
+
+
+ {% endif %}
{% if request.user.has_credential("CRED_ACCOUNT_CHANGE_TYPE") %}
diff --git a/test/test_accounts_routes.py b/test/test_accounts_routes.py
index 188f7048..5e855daf 100644
--- a/test/test_accounts_routes.py
+++ b/test/test_accounts_routes.py
@@ -780,6 +780,55 @@ def test_post_account_edit_error_invalid_password():
assert "Invalid password." in content
+def test_post_account_edit_inactivity_unauthorized():
+ cookies = {"AURSID": user.login(Request(), "testPassword")}
+ post_data = {
+ "U": "test",
+ "E": "test@example.org",
+ "J": True,
+ "passwd": "testPassword"
+ }
+ with client as request:
+ resp = request.post(f"/account/{user.Username}/edit", data=post_data,
+ cookies=cookies)
+ assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
+
+ errors = get_errors(resp.text)
+ expected = "You do not have permission to suspend accounts."
+ assert errors[0].text.strip() == expected
+
+
+def test_post_account_edit_inactivity():
+ with db.begin():
+ user.AccountTypeID = TRUSTED_USER_ID
+ assert not user.Suspended
+
+ cookies = {"AURSID": user.login(Request(), "testPassword")}
+ post_data = {
+ "U": "test",
+ "E": "test@example.org",
+ "J": True,
+ "passwd": "testPassword"
+ }
+ with client as request:
+ resp = request.post(f"/account/{user.Username}/edit", data=post_data,
+ cookies=cookies)
+ assert resp.status_code == int(HTTPStatus.OK)
+
+ # Make sure the user record got updated correctly.
+ assert user.Suspended
+ assert user.InactivityTS > 0
+
+ post_data.update({"J": False})
+ with client as request:
+ resp = request.post(f"/account/{user.Username}/edit", data=post_data,
+ cookies=cookies)
+ assert resp.status_code == int(HTTPStatus.OK)
+
+ assert not user.Suspended
+ assert user.InactivityTS == 0
+
+
def test_post_account_edit_error_unauthorized():
request = Request()
sid = user.login(request, "testPassword")