From 65be8b8e07132a7ef612deca1cd03596c3079884 Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Mon, 25 Oct 2021 21:56:15 -0700 Subject: [PATCH] fix(fastapi): support "Account Type:" input for account edit Signed-off-by: Kevin Morris --- aurweb/models/account_type.py | 3 + aurweb/routers/accounts.py | 39 +++++++++++-- po/aurweb.pot | 12 ++++ test/test_accounts_routes.py | 104 +++++++++++++++++++++++++++++++++- 4 files changed, 153 insertions(+), 5 deletions(-) diff --git a/aurweb/models/account_type.py b/aurweb/models/account_type.py index 2e3dde06..7aa7733c 100644 --- a/aurweb/models/account_type.py +++ b/aurweb/models/account_type.py @@ -47,3 +47,6 @@ ACCOUNT_TYPE_ID = { DEVELOPER: DEVELOPER_ID, TRUSTED_USER_AND_DEV: TRUSTED_USER_AND_DEV_ID } + +# Reversed ACCOUNT_TYPE_ID mapping. +ACCOUNT_TYPE_NAME = {v: k for k, v in ACCOUNT_TYPE_ID.items()} diff --git a/aurweb/routers/accounts.py b/aurweb/routers/accounts.py index a4f6dc56..70be2510 100644 --- a/aurweb/routers/accounts.py +++ b/aurweb/routers/accounts.py @@ -15,6 +15,8 @@ from aurweb.auth import account_type_required, auth_required from aurweb.captcha import get_captcha_answer, get_captcha_salts, get_captcha_token from aurweb.l10n import get_translator_for_request from aurweb.models import account_type +from aurweb.models.account_type import (DEVELOPER, DEVELOPER_ID, TRUSTED_USER, TRUSTED_USER_AND_DEV, TRUSTED_USER_AND_DEV_ID, + TRUSTED_USER_ID, USER_ID) from aurweb.models.ssh_pub_key import get_fingerprint from aurweb.scripts.notify import ResetKeyNotification, WelcomeNotification from aurweb.templates import make_context, make_variable_context, render_template @@ -226,6 +228,29 @@ def process_account_form(request: Request, user: models.User, args: dict): "", fingerprint, "") ]) + T = int(args.get("T", user.AccountTypeID)) + if T != user.AccountTypeID: + if T not in account_type.ACCOUNT_TYPE_NAME: + return (False, + ["Invalid account type provided."]) + elif not request.user.is_elevated(): + return (False, + ["You do not have permission to change account types."]) + + credential_checks = { + DEVELOPER_ID: request.user.is_developer, + TRUSTED_USER_AND_DEV_ID: request.user.is_developer, + TRUSTED_USER_ID: request.user.is_elevated, + USER_ID: request.user.is_elevated + } + credential_check = credential_checks.get(T) + + if not credential_check(): + name = account_type.ACCOUNT_TYPE_NAME.get(T) + error = _("You do not have permission to change " + "this user's account type to %s.") % name + return (False, [error]) + captcha_salt = args.get("captcha_salt", None) if captcha_salt and captcha_salt not in get_captcha_salts(): return (False, ["This CAPTCHA has expired. Please try again."]) @@ -255,15 +280,16 @@ def make_account_form_context(context: dict, context = copy.copy(context) context["account_types"] = [ - (1, "Normal User"), - (2, "Trusted User") + (USER_ID, "Normal User"), + (TRUSTED_USER_ID, TRUSTED_USER) ] user_account_type_id = context.get("account_types")[0][0] if request.user.has_credential("CRED_ACCOUNT_EDIT_DEV"): - context["account_types"].append((3, "Developer")) - context["account_types"].append((4, "Trusted User & Developer")) + context["account_types"].append((DEVELOPER_ID, DEVELOPER)) + context["account_types"].append((TRUSTED_USER_AND_DEV_ID, + TRUSTED_USER_AND_DEV)) if request.user.is_authenticated(): context["username"] = args.get("U", user.Username) @@ -465,6 +491,7 @@ async def account_edit_post(request: Request, CN: bool = Form(default=False), # Comment Notify UN: bool = Form(default=False), # Update Notify ON: bool = Form(default=False), # Owner Notify + T: int = Form(default=None), passwd: str = Form(default=str())): from aurweb.db import session @@ -545,6 +572,10 @@ async def account_edit_post(request: Request, # Else, if the user has a public key already, delete it. session.delete(user.ssh_pub_key) + if T and T != user.AccountTypeID: + with db.begin(): + user.AccountTypeID = T + if P and not user.valid_password(P): # Remove the fields we consumed for passwords. context["P"] = context["C"] = str() diff --git a/po/aurweb.pot b/po/aurweb.pot index 0e30b366..721f874e 100644 --- a/po/aurweb.pot +++ b/po/aurweb.pot @@ -2280,3 +2280,15 @@ msgid "" "Please remember to cast your vote on proposal {id} [1]. The voting period " "ends in less than 48 hours." msgstr "" + +#: aurweb/routers/accounts.py +msgid "Invalid account type provided." +msgstr "" + +#: aurweb/routers/accounts.py +msgid "You do not have permission to change account types." +msgstr "" + +#: aurweb/routers/accounts.py +msgid "You do not have permission to change this user's account type to %s." +msgstr "" diff --git a/test/test_accounts_routes.py b/test/test_accounts_routes.py index d5d92112..6c79cd33 100644 --- a/test/test_accounts_routes.py +++ b/test/test_accounts_routes.py @@ -14,13 +14,14 @@ from aurweb import captcha, db, logging from aurweb.asgi import app from aurweb.db import create, query from aurweb.models.accepted_term import AcceptedTerm -from aurweb.models.account_type import DEVELOPER_ID, TRUSTED_USER_AND_DEV_ID, TRUSTED_USER_ID, AccountType +from aurweb.models.account_type import DEVELOPER_ID, TRUSTED_USER_AND_DEV_ID, TRUSTED_USER_ID, USER_ID, AccountType from aurweb.models.ban import Ban from aurweb.models.session import Session from aurweb.models.ssh_pub_key import SSHPubKey, get_fingerprint from aurweb.models.term import Term from aurweb.models.user import User from aurweb.testing import setup_test_db +from aurweb.testing.html import get_errors from aurweb.testing.requests import Request # Some test global constants. @@ -909,6 +910,107 @@ def test_post_account_edit_password(): assert user.valid_password("newPassword") +def test_post_account_edit_account_types(): + request = Request() + sid = user.login(request, "testPassword") + cookies = {"AURSID": sid} + endpoint = f"/account/{user.Username}/edit" + + # As a normal user, we cannot see the "Account Type:" input. + with client as request: + resp = request.get(endpoint, cookies=cookies) + assert resp.status_code == int(HTTPStatus.OK) + assert "id_type" not in resp.text + + # Invalid account types return an error. + post_data = { + "U": user.Username, + "E": user.Email, + "T": 0, # Invalid type ID + "passwd": "testPassword" + } + with client as request: + resp = request.post(endpoint, data=post_data, cookies=cookies) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + errors = get_errors(resp.text) + expected = "Invalid account type provided." + assert errors[0].text.strip() == expected + + # Nor can we change any account types. + post_data = { + "U": user.Username, + "E": user.Email, + "T": TRUSTED_USER_ID, + "passwd": "testPassword" + } + with client as request: + resp = request.post(endpoint, 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 change account types." + assert errors[0].text.strip() == expected + + # Change user from USER_ID to TRUSTED_USER_ID. + with db.begin(): + user.AccountTypeID = TRUSTED_USER_ID + + # As a trusted user, we can see the "Account Type:" input. + with client as request: + resp = request.get(endpoint, cookies=cookies) + assert resp.status_code == int(HTTPStatus.OK) + assert "id_type" in resp.text + + # As a trusted user, we cannot change account type to DEVELOPER_ID. + post_data = { + "U": user.Username, + "E": user.Email, + "T": DEVELOPER_ID, + "passwd": "testPassword" + } + with client as request: + resp = request.post(endpoint, 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 change " + "this user's account type to Developer.") + assert errors[0].text.strip() == expected + + # However, we can modify our account type to USER_ID. + post_data = { + "U": user.Username, + "E": user.Email, + "T": USER_ID, + "passwd": "testPassword" + } + with client as request: + resp = request.post(endpoint, data=post_data, cookies=cookies) + assert resp.status_code == int(HTTPStatus.OK) + + # No errors should be displayed. + errors = get_errors(resp.text) + assert not errors + + # Make sure it got changed to USER_ID as we intended. + assert user.AccountTypeID == USER_ID + + # Change user to a Developer. + with db.begin(): + user.AccountTypeID = DEVELOPER_ID + + # As a developer, we can absolutely change all account types. + # For example, from DEVELOPER_ID to TRUSTED_USER_AND_DEV_ID: + post_data = { + "U": user.Username, + "E": user.Email, + "T": TRUSTED_USER_AND_DEV_ID, + "passwd": "testPassword" + } + with client as request: + resp = request.post(endpoint, data=post_data, cookies=cookies) + assert resp.status_code == int(HTTPStatus.OK) + assert user.AccountTypeID == TRUSTED_USER_AND_DEV_ID + + def test_get_account(): request = Request() sid = user.login(request, "testPassword")