From 07d5907ecda5e93ebe44bd591a7f0ce87fb73cc2 Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Mon, 25 Jan 2021 16:30:47 -0800 Subject: [PATCH] aurweb.auth: add user credentials and matcher functions This clones the behavior already present in the PHP implementation, but it uses a global dict with credential constant keys to validation functions to determine if a given user has a credential. Signed-off-by: Kevin Morris --- aurweb/auth.py | 101 ++++++++++++++++++++++++++++++++++++++++++ aurweb/models/user.py | 5 +++ test/test_auth.py | 7 ++- test/test_user.py | 42 ++++++++++++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) diff --git a/aurweb/auth.py b/aurweb/auth.py index 8608a82a..53c853de 100644 --- a/aurweb/auth.py +++ b/aurweb/auth.py @@ -17,6 +17,10 @@ class AnonymousUser: def is_authenticated(): return False + @staticmethod + def has_credential(credential): + return False + class BasicAuthBackend(AuthenticationBackend): async def authenticate(self, conn: HTTPConnection): @@ -75,3 +79,100 @@ def auth_required(is_required: bool = True, return wrapper return decorator + + +CRED_ACCOUNT_CHANGE_TYPE = 1 +CRED_ACCOUNT_EDIT = 2 +CRED_ACCOUNT_EDIT_DEV = 3 +CRED_ACCOUNT_LAST_LOGIN = 4 +CRED_ACCOUNT_SEARCH = 5 +CRED_ACCOUNT_LIST_COMMENTS = 28 +CRED_COMMENT_DELETE = 6 +CRED_COMMENT_UNDELETE = 27 +CRED_COMMENT_VIEW_DELETED = 22 +CRED_COMMENT_EDIT = 25 +CRED_COMMENT_PIN = 26 +CRED_PKGBASE_ADOPT = 7 +CRED_PKGBASE_SET_KEYWORDS = 8 +CRED_PKGBASE_DELETE = 9 +CRED_PKGBASE_DISOWN = 10 +CRED_PKGBASE_EDIT_COMAINTAINERS = 24 +CRED_PKGBASE_FLAG = 11 +CRED_PKGBASE_LIST_VOTERS = 12 +CRED_PKGBASE_NOTIFY = 13 +CRED_PKGBASE_UNFLAG = 15 +CRED_PKGBASE_VOTE = 16 +CRED_PKGREQ_FILE = 23 +CRED_PKGREQ_CLOSE = 17 +CRED_PKGREQ_LIST = 18 +CRED_TU_ADD_VOTE = 19 +CRED_TU_LIST_VOTES = 20 +CRED_TU_VOTE = 21 + + +def has_any(user, *account_types): + return str(user.AccountType) in set(account_types) + + +def user_developer_or_trusted_user(user): + return has_any(user, "User", "Trusted User", "Developer", + "Trusted User & Developer") + + +def trusted_user(user): + return has_any(user, "Trusted User", "Trusted User & Developer") + + +def developer(user): + return has_any(user, "Developer", "Trusted User & Developer") + + +def trusted_user_or_dev(user): + return has_any(user, "Trusted User", "Developer", + "Trusted User & Developer") + + +# A mapping of functions that users must pass to have credentials. +cred_filters = { + CRED_PKGBASE_FLAG: user_developer_or_trusted_user, + CRED_PKGBASE_NOTIFY: user_developer_or_trusted_user, + CRED_PKGBASE_VOTE: user_developer_or_trusted_user, + CRED_PKGREQ_FILE: user_developer_or_trusted_user, + CRED_ACCOUNT_CHANGE_TYPE: trusted_user_or_dev, + CRED_ACCOUNT_EDIT: trusted_user_or_dev, + CRED_ACCOUNT_LAST_LOGIN: trusted_user_or_dev, + CRED_ACCOUNT_LIST_COMMENTS: trusted_user_or_dev, + CRED_ACCOUNT_SEARCH: trusted_user_or_dev, + CRED_COMMENT_DELETE: trusted_user_or_dev, + CRED_COMMENT_UNDELETE: trusted_user_or_dev, + CRED_COMMENT_VIEW_DELETED: trusted_user_or_dev, + CRED_COMMENT_EDIT: trusted_user_or_dev, + CRED_COMMENT_PIN: trusted_user_or_dev, + CRED_PKGBASE_ADOPT: trusted_user_or_dev, + CRED_PKGBASE_SET_KEYWORDS: trusted_user_or_dev, + CRED_PKGBASE_DELETE: trusted_user_or_dev, + CRED_PKGBASE_EDIT_COMAINTAINERS: trusted_user_or_dev, + CRED_PKGBASE_DISOWN: trusted_user_or_dev, + CRED_PKGBASE_LIST_VOTERS: trusted_user_or_dev, + CRED_PKGBASE_UNFLAG: trusted_user_or_dev, + CRED_PKGREQ_CLOSE: trusted_user_or_dev, + CRED_PKGREQ_LIST: trusted_user_or_dev, + CRED_TU_ADD_VOTE: trusted_user, + CRED_TU_LIST_VOTES: trusted_user, + CRED_TU_VOTE: trusted_user, + CRED_ACCOUNT_EDIT_DEV: developer, +} + + +def has_credential(user: User, + credential: int, + approved_users: list = tuple()): + + if user in approved_users: + return True + + if credential in cred_filters: + cred_filter = cred_filters.get(credential) + return cred_filter(user) + + return False diff --git a/aurweb/models/user.py b/aurweb/models/user.py index aff4ce6b..3983e098 100644 --- a/aurweb/models/user.py +++ b/aurweb/models/user.py @@ -141,6 +141,11 @@ class User: request.cookies["AURSID"] = self.session.SessionID return self.session.SessionID + def has_credential(self, credential: str, approved: list = tuple()): + import aurweb.auth + cred = getattr(aurweb.auth, credential) + return aurweb.auth.has_credential(self, cred, approved) + def logout(self, request): from aurweb.db import session diff --git a/test/test_auth.py b/test/test_auth.py index d2251de4..d43459cd 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -4,8 +4,8 @@ import pytest from starlette.authentication import AuthenticationError +from aurweb.auth import BasicAuthBackend, has_credential from aurweb.db import query -from aurweb.auth import BasicAuthBackend from aurweb.models.account_type import AccountType from aurweb.testing import setup_test_db from aurweb.testing.models import make_session, make_user @@ -78,3 +78,8 @@ async def test_basic_auth_backend(): LastUpdateTS=now_ts + 5) _, result = await backend.authenticate(request) assert result == user + + +def test_has_fake_credential_fails(): + # Fake credential 666 does not exist. + assert not has_credential(user, 666) diff --git a/test/test_user.py b/test/test_user.py index 473b035a..e8056681 100644 --- a/test/test_user.py +++ b/test/test_user.py @@ -163,6 +163,11 @@ def test_user_minimum_passwd_length(): assert User.minimum_passwd_length() == passwd_min_len +def test_user_has_credential(): + assert user.has_credential("CRED_PKGBASE_FLAG") + assert not user.has_credential("CRED_ACCOUNT_CHANGE_TYPE") + + def test_user_ssh_pub_key(): from aurweb.db import session @@ -178,3 +183,40 @@ def test_user_ssh_pub_key(): session.delete(ssh_pub_key) session.commit() + + +def test_user_credential_types(): + from aurweb.db import session + + assert aurweb.auth.user_developer_or_trusted_user(user) + assert not aurweb.auth.trusted_user(user) + assert not aurweb.auth.developer(user) + assert not aurweb.auth.trusted_user_or_dev(user) + + trusted_user_type = query(AccountType, + AccountType.AccountType == "Trusted User")\ + .first() + user.AccountType = trusted_user_type + session.commit() + + assert aurweb.auth.trusted_user(user) + assert aurweb.auth.trusted_user_or_dev(user) + + developer_type = query(AccountType, + AccountType.AccountType == "Developer")\ + .first() + user.AccountType = developer_type + session.commit() + + assert aurweb.auth.developer(user) + assert aurweb.auth.trusted_user_or_dev(user) + + type_str = "Trusted User & Developer" + elevated_type = query(AccountType, + AccountType.AccountType == type_str).first() + user.AccountType = elevated_type + session.commit() + + assert aurweb.auth.trusted_user(user) + assert aurweb.auth.developer(user) + assert aurweb.auth.trusted_user_or_dev(user)