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 <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-01-25 16:30:47 -08:00
parent 670f711b59
commit 07d5907ecd
4 changed files with 154 additions and 1 deletions

View file

@ -17,6 +17,10 @@ class AnonymousUser:
def is_authenticated(): def is_authenticated():
return False return False
@staticmethod
def has_credential(credential):
return False
class BasicAuthBackend(AuthenticationBackend): class BasicAuthBackend(AuthenticationBackend):
async def authenticate(self, conn: HTTPConnection): async def authenticate(self, conn: HTTPConnection):
@ -75,3 +79,100 @@ def auth_required(is_required: bool = True,
return wrapper return wrapper
return decorator 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

View file

@ -141,6 +141,11 @@ class User:
request.cookies["AURSID"] = self.session.SessionID request.cookies["AURSID"] = self.session.SessionID
return 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): def logout(self, request):
from aurweb.db import session from aurweb.db import session

View file

@ -4,8 +4,8 @@ import pytest
from starlette.authentication import AuthenticationError from starlette.authentication import AuthenticationError
from aurweb.auth import BasicAuthBackend, has_credential
from aurweb.db import query from aurweb.db import query
from aurweb.auth import BasicAuthBackend
from aurweb.models.account_type import AccountType from aurweb.models.account_type import AccountType
from aurweb.testing import setup_test_db from aurweb.testing import setup_test_db
from aurweb.testing.models import make_session, make_user from aurweb.testing.models import make_session, make_user
@ -78,3 +78,8 @@ async def test_basic_auth_backend():
LastUpdateTS=now_ts + 5) LastUpdateTS=now_ts + 5)
_, result = await backend.authenticate(request) _, result = await backend.authenticate(request)
assert result == user assert result == user
def test_has_fake_credential_fails():
# Fake credential 666 does not exist.
assert not has_credential(user, 666)

View file

@ -163,6 +163,11 @@ def test_user_minimum_passwd_length():
assert User.minimum_passwd_length() == passwd_min_len 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(): def test_user_ssh_pub_key():
from aurweb.db import session from aurweb.db import session
@ -178,3 +183,40 @@ def test_user_ssh_pub_key():
session.delete(ssh_pub_key) session.delete(ssh_pub_key)
session.commit() 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)