fix(FastAPI): reorganize credential checkin into dedicated file

Signed-off-by: Steven Guikal <void@fluix.one>
This commit is contained in:
Steven Guikal 2021-11-30 15:44:18 -05:00
parent 125b244f44
commit a10f8663fd
15 changed files with 143 additions and 172 deletions

View file

@ -250,101 +250,3 @@ def account_type_required(one_of: set):
return await func(request, *args, **kwargs) return await func(request, *args, **kwargs)
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
CRED_PKGBASE_MERGE = 29
def has_any(user, *account_types):
return str(user.AccountType) in set(account_types)
def user_developer_or_trusted_user(user):
return True
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_or_dev,
CRED_TU_VOTE: trusted_user,
CRED_ACCOUNT_EDIT_DEV: developer,
CRED_PKGBASE_MERGE: trusted_user_or_dev,
}
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

76
aurweb/auth/creds.py Normal file
View file

@ -0,0 +1,76 @@
from aurweb.models.account_type import DEVELOPER_ID, TRUSTED_USER_AND_DEV_ID, TRUSTED_USER_ID, USER_ID
from aurweb.models.user import User
ACCOUNT_CHANGE_TYPE = 1
ACCOUNT_EDIT = 2
ACCOUNT_EDIT_DEV = 3
ACCOUNT_LAST_LOGIN = 4
ACCOUNT_SEARCH = 5
ACCOUNT_LIST_COMMENTS = 28
COMMENT_DELETE = 6
COMMENT_UNDELETE = 27
COMMENT_VIEW_DELETED = 22
COMMENT_EDIT = 25
COMMENT_PIN = 26
PKGBASE_ADOPT = 7
PKGBASE_SET_KEYWORDS = 8
PKGBASE_DELETE = 9
PKGBASE_DISOWN = 10
PKGBASE_EDIT_COMAINTAINERS = 24
PKGBASE_FLAG = 11
PKGBASE_LIST_VOTERS = 12
PKGBASE_NOTIFY = 13
PKGBASE_UNFLAG = 15
PKGBASE_VOTE = 16
PKGREQ_FILE = 23
PKGREQ_CLOSE = 17
PKGREQ_LIST = 18
TU_ADD_VOTE = 19
TU_LIST_VOTES = 20
TU_VOTE = 21
PKGBASE_MERGE = 29
user_developer_or_trusted_user = set([USER_ID, TRUSTED_USER_ID, DEVELOPER_ID, TRUSTED_USER_AND_DEV_ID])
trusted_user_or_dev = set([TRUSTED_USER_ID, DEVELOPER_ID, TRUSTED_USER_AND_DEV_ID])
developer = set([DEVELOPER_ID, TRUSTED_USER_AND_DEV_ID])
trusted_user = set([TRUSTED_USER_ID, TRUSTED_USER_AND_DEV_ID])
cred_filters = {
PKGBASE_FLAG: user_developer_or_trusted_user,
PKGBASE_NOTIFY: user_developer_or_trusted_user,
PKGBASE_VOTE: user_developer_or_trusted_user,
PKGREQ_FILE: user_developer_or_trusted_user,
ACCOUNT_CHANGE_TYPE: trusted_user_or_dev,
ACCOUNT_EDIT: trusted_user_or_dev,
ACCOUNT_LAST_LOGIN: trusted_user_or_dev,
ACCOUNT_LIST_COMMENTS: trusted_user_or_dev,
ACCOUNT_SEARCH: trusted_user_or_dev,
COMMENT_DELETE: trusted_user_or_dev,
COMMENT_UNDELETE: trusted_user_or_dev,
COMMENT_VIEW_DELETED: trusted_user_or_dev,
COMMENT_EDIT: trusted_user_or_dev,
COMMENT_PIN: trusted_user_or_dev,
PKGBASE_ADOPT: trusted_user_or_dev,
PKGBASE_SET_KEYWORDS: trusted_user_or_dev,
PKGBASE_DELETE: trusted_user_or_dev,
PKGBASE_EDIT_COMAINTAINERS: trusted_user_or_dev,
PKGBASE_DISOWN: trusted_user_or_dev,
PKGBASE_LIST_VOTERS: trusted_user_or_dev,
PKGBASE_UNFLAG: trusted_user_or_dev,
PKGREQ_CLOSE: trusted_user_or_dev,
PKGREQ_LIST: trusted_user_or_dev,
TU_ADD_VOTE: trusted_user,
TU_LIST_VOTES: trusted_user_or_dev,
TU_VOTE: trusted_user,
ACCOUNT_EDIT_DEV: developer,
PKGBASE_MERGE: trusted_user_or_dev,
}
def has_credential(user: User,
credential: int,
approved_users: list = tuple()):
if user in approved_users:
return True
return user.AccountTypeID in cred_filters[credential]

View file

@ -1,6 +1,7 @@
import hashlib import hashlib
from datetime import datetime from datetime import datetime
from typing import List, Set
import bcrypt import bcrypt
@ -136,10 +137,10 @@ class User(Base):
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()): def has_credential(self, credential: Set[int],
import aurweb.auth approved: List["User"] = list()):
cred = getattr(aurweb.auth, credential) from aurweb.auth.creds import has_credential
return aurweb.auth.has_credential(self, cred, approved) return has_credential(self, credential, approved)
def logout(self, request): def logout(self, request):
del request.cookies["AURSID"] del request.cookies["AURSID"]

View file

@ -10,7 +10,7 @@ from sqlalchemy import and_, or_
import aurweb.config import aurweb.config
from aurweb import cookies, db, l10n, logging, models, util from aurweb import cookies, db, l10n, logging, models, util
from aurweb.auth import account_type_required, auth_required from aurweb.auth import account_type_required, auth_required, creds
from aurweb.captcha import get_captcha_salts from aurweb.captcha import get_captcha_salts
from aurweb.exceptions import ValidationError from aurweb.exceptions import ValidationError
from aurweb.l10n import get_translator_for_request from aurweb.l10n import get_translator_for_request
@ -176,7 +176,7 @@ def make_account_form_context(context: dict,
user_account_type_id = context.get("account_types")[0][0] user_account_type_id = context.get("account_types")[0][0]
if request.user.has_credential("CRED_ACCOUNT_EDIT_DEV"): if request.user.has_credential(creds.ACCOUNT_EDIT_DEV):
context["account_types"].append((at.DEVELOPER_ID, at.DEVELOPER)) context["account_types"].append((at.DEVELOPER_ID, at.DEVELOPER))
context["account_types"].append((at.TRUSTED_USER_AND_DEV_ID, context["account_types"].append((at.TRUSTED_USER_AND_DEV_ID,
at.TRUSTED_USER_AND_DEV)) at.TRUSTED_USER_AND_DEV))
@ -332,7 +332,7 @@ async def account_register_post(request: Request,
def cannot_edit(request, user): def cannot_edit(request, user):
""" Return a 401 HTMLResponse if the request user doesn't """ Return a 401 HTMLResponse if the request user doesn't
have authorization, otherwise None. """ have authorization, otherwise None. """
has_dev_cred = request.user.has_credential("CRED_ACCOUNT_EDIT_DEV", has_dev_cred = request.user.has_credential(creds.ACCOUNT_EDIT_DEV,
approved=[user]) approved=[user])
if not has_dev_cred: if not has_dev_cred:
return HTMLResponse(status_code=HTTPStatus.UNAUTHORIZED) return HTMLResponse(status_code=HTTPStatus.UNAUTHORIZED)

View file

@ -10,7 +10,7 @@ import aurweb.filters
import aurweb.packages.util import aurweb.packages.util
from aurweb import db, defaults, l10n, logging, models, util from aurweb import db, defaults, l10n, logging, models, util
from aurweb.auth import auth_required from aurweb.auth import auth_required, creds
from aurweb.exceptions import ValidationError from aurweb.exceptions import ValidationError
from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID
from aurweb.models.relation_type import CONFLICTS_ID, PROVIDES_ID, REPLACES_ID from aurweb.models.relation_type import CONFLICTS_ID, PROVIDES_ID, REPLACES_ID
@ -413,7 +413,7 @@ async def pkgbase_comment_delete(request: Request, name: str, id: int,
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
comment = get_pkgbase_comment(pkgbase, id) comment = get_pkgbase_comment(pkgbase, id)
authorized = request.user.has_credential("CRED_COMMENT_DELETE", authorized = request.user.has_credential(creds.COMMENT_DELETE,
[comment.User]) [comment.User])
if not authorized: if not authorized:
_ = l10n.get_translator_for_request(request) _ = l10n.get_translator_for_request(request)
@ -439,7 +439,7 @@ async def pkgbase_comment_undelete(request: Request, name: str, id: int,
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
comment = get_pkgbase_comment(pkgbase, id) comment = get_pkgbase_comment(pkgbase, id)
has_cred = request.user.has_credential("CRED_COMMENT_UNDELETE", has_cred = request.user.has_credential(creds.COMMENT_UNDELETE,
approved=[comment.User]) approved=[comment.User])
if not has_cred: if not has_cred:
_ = l10n.get_translator_for_request(request) _ = l10n.get_translator_for_request(request)
@ -464,7 +464,7 @@ async def pkgbase_comment_pin(request: Request, name: str, id: int,
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
comment = get_pkgbase_comment(pkgbase, id) comment = get_pkgbase_comment(pkgbase, id)
has_cred = request.user.has_credential("CRED_COMMENT_PIN", has_cred = request.user.has_credential(creds.COMMENT_PIN,
approved=[pkgbase.Maintainer]) approved=[pkgbase.Maintainer])
if not has_cred: if not has_cred:
_ = l10n.get_translator_for_request(request) _ = l10n.get_translator_for_request(request)
@ -489,7 +489,7 @@ async def pkgbase_comment_unpin(request: Request, name: str, id: int,
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
comment = get_pkgbase_comment(pkgbase, id) comment = get_pkgbase_comment(pkgbase, id)
has_cred = request.user.has_credential("CRED_COMMENT_PIN", has_cred = request.user.has_credential(creds.COMMENT_PIN,
approved=[pkgbase.Maintainer]) approved=[pkgbase.Maintainer])
if not has_cred: if not has_cred:
_ = l10n.get_translator_for_request(request) _ = l10n.get_translator_for_request(request)
@ -514,7 +514,7 @@ async def package_base_comaintainers(request: Request, name: str) -> Response:
# Unauthorized users (Non-TU/Dev and not the pkgbase maintainer) # Unauthorized users (Non-TU/Dev and not the pkgbase maintainer)
# get redirected to the package base's page. # get redirected to the package base's page.
has_creds = request.user.has_credential("CRED_PKGBASE_EDIT_COMAINTAINERS", has_creds = request.user.has_credential(creds.PKGBASE_EDIT_COMAINTAINERS,
approved=[pkgbase.Maintainer]) approved=[pkgbase.Maintainer])
if not has_creds: if not has_creds:
return RedirectResponse(f"/pkgbase/{name}", return RedirectResponse(f"/pkgbase/{name}",
@ -541,7 +541,7 @@ async def package_base_comaintainers_post(
# Unauthorized users (Non-TU/Dev and not the pkgbase maintainer) # Unauthorized users (Non-TU/Dev and not the pkgbase maintainer)
# get redirected to the package base's page. # get redirected to the package base's page.
has_creds = request.user.has_credential("CRED_PKGBASE_EDIT_COMAINTAINERS", has_creds = request.user.has_credential(creds.PKGBASE_EDIT_COMAINTAINERS,
approved=[pkgbase.Maintainer]) approved=[pkgbase.Maintainer])
if not has_creds: if not has_creds:
return RedirectResponse(f"/pkgbase/{name}", return RedirectResponse(f"/pkgbase/{name}",
@ -779,7 +779,7 @@ async def pkgbase_keywords(request: Request, name: str,
async def pkgbase_flag_get(request: Request, name: str): async def pkgbase_flag_get(request: Request, name: str):
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
has_cred = request.user.has_credential("CRED_PKGBASE_FLAG") has_cred = request.user.has_credential(creds.PKGBASE_FLAG)
if not has_cred or pkgbase.Flagger is not None: if not has_cred or pkgbase.Flagger is not None:
return RedirectResponse(f"/pkgbase/{name}", return RedirectResponse(f"/pkgbase/{name}",
status_code=HTTPStatus.SEE_OTHER) status_code=HTTPStatus.SEE_OTHER)
@ -803,7 +803,7 @@ async def pkgbase_flag_post(request: Request, name: str,
return render_template(request, "packages/flag.html", context, return render_template(request, "packages/flag.html", context,
status_code=HTTPStatus.BAD_REQUEST) status_code=HTTPStatus.BAD_REQUEST)
has_cred = request.user.has_credential("CRED_PKGBASE_FLAG") has_cred = request.user.has_credential(creds.PKGBASE_FLAG)
if has_cred and not pkgbase.Flagger: if has_cred and not pkgbase.Flagger:
now = int(datetime.utcnow().timestamp()) now = int(datetime.utcnow().timestamp())
with db.begin(): with db.begin():
@ -830,7 +830,7 @@ async def pkgbase_flag_comment(request: Request, name: str):
def pkgbase_unflag_instance(request: Request, pkgbase: models.PackageBase): def pkgbase_unflag_instance(request: Request, pkgbase: models.PackageBase):
has_cred = request.user.has_credential( has_cred = request.user.has_credential(
"CRED_PKGBASE_UNFLAG", approved=[pkgbase.Flagger, pkgbase.Maintainer]) creds.PKGBASE_UNFLAG, approved=[pkgbase.Flagger, pkgbase.Maintainer])
if has_cred: if has_cred:
with db.begin(): with db.begin():
pkgbase.OutOfDateTS = None pkgbase.OutOfDateTS = None
@ -851,7 +851,7 @@ def pkgbase_notify_instance(request: Request, pkgbase: models.PackageBase):
notif = db.query(pkgbase.notifications.filter( notif = db.query(pkgbase.notifications.filter(
models.PackageNotification.UserID == request.user.ID models.PackageNotification.UserID == request.user.ID
).exists()).scalar() ).exists()).scalar()
has_cred = request.user.has_credential("CRED_PKGBASE_NOTIFY") has_cred = request.user.has_credential(creds.PKGBASE_NOTIFY)
if has_cred and not notif: if has_cred and not notif:
with db.begin(): with db.begin():
db.create(models.PackageNotification, db.create(models.PackageNotification,
@ -872,7 +872,7 @@ def pkgbase_unnotify_instance(request: Request, pkgbase: models.PackageBase):
notif = pkgbase.notifications.filter( notif = pkgbase.notifications.filter(
models.PackageNotification.UserID == request.user.ID models.PackageNotification.UserID == request.user.ID
).first() ).first()
has_cred = request.user.has_credential("CRED_PKGBASE_NOTIFY") has_cred = request.user.has_credential(creds.PKGBASE_NOTIFY)
if has_cred and notif: if has_cred and notif:
with db.begin(): with db.begin():
db.delete(notif) db.delete(notif)
@ -895,7 +895,7 @@ async def pkgbase_vote(request: Request, name: str):
vote = pkgbase.package_votes.filter( vote = pkgbase.package_votes.filter(
models.PackageVote.UsersID == request.user.ID models.PackageVote.UsersID == request.user.ID
).first() ).first()
has_cred = request.user.has_credential("CRED_PKGBASE_VOTE") has_cred = request.user.has_credential(creds.PKGBASE_VOTE)
if has_cred and not vote: if has_cred and not vote:
now = int(datetime.utcnow().timestamp()) now = int(datetime.utcnow().timestamp())
with db.begin(): with db.begin():
@ -919,7 +919,7 @@ async def pkgbase_unvote(request: Request, name: str):
vote = pkgbase.package_votes.filter( vote = pkgbase.package_votes.filter(
models.PackageVote.UsersID == request.user.ID models.PackageVote.UsersID == request.user.ID
).first() ).first()
has_cred = request.user.has_credential("CRED_PKGBASE_VOTE") has_cred = request.user.has_credential(creds.PKGBASE_VOTE)
if has_cred and vote: if has_cred and vote:
with db.begin(): with db.begin():
db.delete(vote) db.delete(vote)
@ -958,7 +958,7 @@ def pkgbase_disown_instance(request: Request, pkgbase: models.PackageBase):
async def pkgbase_disown_get(request: Request, name: str): async def pkgbase_disown_get(request: Request, name: str):
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
has_cred = request.user.has_credential("CRED_PKGBASE_DISOWN", has_cred = request.user.has_credential(creds.PKGBASE_DISOWN,
approved=[pkgbase.Maintainer]) approved=[pkgbase.Maintainer])
if not has_cred: if not has_cred:
return RedirectResponse(f"/pkgbase/{name}", return RedirectResponse(f"/pkgbase/{name}",
@ -975,7 +975,7 @@ async def pkgbase_disown_post(request: Request, name: str,
confirm: bool = Form(default=False)): confirm: bool = Form(default=False)):
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
has_cred = request.user.has_credential("CRED_PKGBASE_DISOWN", has_cred = request.user.has_credential(creds.PKGBASE_DISOWN,
approved=[pkgbase.Maintainer]) approved=[pkgbase.Maintainer])
if not has_cred: if not has_cred:
return RedirectResponse(f"/pkgbase/{name}", return RedirectResponse(f"/pkgbase/{name}",
@ -1007,7 +1007,7 @@ def pkgbase_adopt_instance(request: Request, pkgbase: models.PackageBase):
async def pkgbase_adopt_post(request: Request, name: str): async def pkgbase_adopt_post(request: Request, name: str):
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
has_cred = request.user.has_credential("CRED_PKGBASE_ADOPT") has_cred = request.user.has_credential(creds.PKGBASE_ADOPT)
if has_cred or not pkgbase.Maintainer: if has_cred or not pkgbase.Maintainer:
# If the user has credentials, they'll adopt the package regardless # If the user has credentials, they'll adopt the package regardless
# of maintainership. Otherwise, we'll promote the user to maintainer # of maintainership. Otherwise, we'll promote the user to maintainer
@ -1021,7 +1021,7 @@ async def pkgbase_adopt_post(request: Request, name: str):
@router.get("/pkgbase/{name}/delete") @router.get("/pkgbase/{name}/delete")
@auth_required(True, redirect="/pkgbase/{name}/delete") @auth_required(True, redirect="/pkgbase/{name}/delete")
async def pkgbase_delete_get(request: Request, name: str): async def pkgbase_delete_get(request: Request, name: str):
if not request.user.has_credential("CRED_PKGBASE_DELETE"): if not request.user.has_credential(creds.PKGBASE_DELETE):
return RedirectResponse(f"/pkgbase/{name}", return RedirectResponse(f"/pkgbase/{name}",
status_code=HTTPStatus.SEE_OTHER) status_code=HTTPStatus.SEE_OTHER)
@ -1036,7 +1036,7 @@ async def pkgbase_delete_post(request: Request, name: str,
confirm: bool = Form(default=False)): confirm: bool = Form(default=False)):
pkgbase = get_pkg_or_base(name, models.PackageBase) pkgbase = get_pkg_or_base(name, models.PackageBase)
if not request.user.has_credential("CRED_PKGBASE_DELETE"): if not request.user.has_credential(creds.PKGBASE_DELETE):
return RedirectResponse(f"/pkgbase/{name}", return RedirectResponse(f"/pkgbase/{name}",
status_code=HTTPStatus.SEE_OTHER) status_code=HTTPStatus.SEE_OTHER)
@ -1070,7 +1070,7 @@ async def packages_unflag(request: Request, package_ids: List[int] = [],
models.Package.ID.in_(package_ids)).all() models.Package.ID.in_(package_ids)).all()
for pkg in packages: for pkg in packages:
has_cred = request.user.has_credential( has_cred = request.user.has_credential(
"CRED_PKGBASE_UNFLAG", approved=[pkg.PackageBase.Flagger]) creds.PKGBASE_UNFLAG, approved=[pkg.PackageBase.Flagger])
if not has_cred: if not has_cred:
return (False, ["You did not select any packages to unflag."]) return (False, ["You did not select any packages to unflag."])
@ -1106,7 +1106,7 @@ async def packages_notify(request: Request, package_ids: List[int] = [],
notif = db.query(pkgbase.notifications.filter( notif = db.query(pkgbase.notifications.filter(
models.PackageNotification.UserID == request.user.ID models.PackageNotification.UserID == request.user.ID
).exists()).scalar() ).exists()).scalar()
has_cred = request.user.has_credential("CRED_PKGBASE_NOTIFY") has_cred = request.user.has_credential(creds.PKGBASE_NOTIFY)
# If the request user either does not have credentials # If the request user either does not have credentials
# or the notification already exists: # or the notification already exists:
@ -1178,7 +1178,7 @@ async def packages_adopt(request: Request, package_ids: List[int] = [],
# Check that the user has credentials for every package they selected. # Check that the user has credentials for every package they selected.
for pkgbase in bases: for pkgbase in bases:
has_cred = request.user.has_credential("CRED_PKGBASE_ADOPT") has_cred = request.user.has_credential(creds.PKGBASE_ADOPT)
if not (has_cred or not pkgbase.Maintainer): if not (has_cred or not pkgbase.Maintainer):
# TODO: This error needs to be translated. # TODO: This error needs to be translated.
return (False, ["You are not allowed to adopt one of the " return (False, ["You are not allowed to adopt one of the "
@ -1211,7 +1211,7 @@ async def packages_disown(request: Request, package_ids: List[int] = [],
# Check that the user has credentials for every package they selected. # Check that the user has credentials for every package they selected.
for pkgbase in bases: for pkgbase in bases:
has_cred = request.user.has_credential("CRED_PKGBASE_DISOWN", has_cred = request.user.has_credential(creds.PKGBASE_DISOWN,
approved=[pkgbase.Maintainer]) approved=[pkgbase.Maintainer])
if not has_cred: if not has_cred:
# TODO: This error needs to be translated. # TODO: This error needs to be translated.
@ -1235,7 +1235,7 @@ async def packages_delete(request: Request, package_ids: List[int] = [],
return (False, ["The selected packages have not been deleted, " return (False, ["The selected packages have not been deleted, "
"check the confirmation checkbox."]) "check the confirmation checkbox."])
if not request.user.has_credential("CRED_PKGBASE_DELETE"): if not request.user.has_credential(creds.PKGBASE_DELETE):
return (False, ["You do not have permission to delete packages."]) return (False, ["You do not have permission to delete packages."])
# A "memo" used to store names of packages that we delete. # A "memo" used to store names of packages that we delete.
@ -1329,10 +1329,10 @@ async def pkgbase_merge_get(request: Request, name: str,
status_code = HTTPStatus.OK status_code = HTTPStatus.OK
# TODO: Lookup errors from credential instead of hardcoding them. # TODO: Lookup errors from credential instead of hardcoding them.
# Idea: Something like credential_errors("CRED_PKGBASE_MERGE"). # Idea: Something like credential_errors(creds.PKGBASE_MERGE).
# Perhaps additionally: bad_credential_status_code("CRED_PKGBASE_MERGE"). # Perhaps additionally: bad_credential_status_code(creds.PKGBASE_MERGE).
# Don't take these examples verbatim. We should find good naming. # Don't take these examples verbatim. We should find good naming.
if not request.user.has_credential("CRED_PKGBASE_MERGE"): if not request.user.has_credential(creds.PKGBASE_MERGE):
context["errors"] = [ context["errors"] = [
"Only Trusted Users and Developers can merge packages."] "Only Trusted Users and Developers can merge packages."]
status_code = HTTPStatus.UNAUTHORIZED status_code = HTTPStatus.UNAUTHORIZED
@ -1434,7 +1434,7 @@ async def pkgbase_merge_post(request: Request, name: str,
context["pkgbase"] = pkgbase context["pkgbase"] = pkgbase
# TODO: Lookup errors from credential instead of hardcoding them. # TODO: Lookup errors from credential instead of hardcoding them.
if not request.user.has_credential("CRED_PKGBASE_MERGE"): if not request.user.has_credential(creds.PKGBASE_MERGE):
context["errors"] = [ context["errors"] = [
"Only Trusted Users and Developers can merge packages."] "Only Trusted Users and Developers can merge packages."]
return render_template(request, "pkgbase/merge.html", context, return render_template(request, "pkgbase/merge.html", context,

View file

@ -16,7 +16,7 @@ from fastapi.responses import HTMLResponse
import aurweb.config import aurweb.config
from aurweb import captcha, cookies, l10n, time, util from aurweb import auth, captcha, cookies, l10n, time, util
# Prepare jinja2 objects. # Prepare jinja2 objects.
_loader = jinja2.FileSystemLoader(os.path.join( _loader = jinja2.FileSystemLoader(os.path.join(
@ -107,6 +107,7 @@ def make_context(request: Request, title: str, next: str = None):
"now": datetime.now(tz=zoneinfo.ZoneInfo(timezone)), "now": datetime.now(tz=zoneinfo.ZoneInfo(timezone)),
"utcnow": int(datetime.utcnow().timestamp()), "utcnow": int(datetime.utcnow().timestamp()),
"config": aurweb.config, "config": aurweb.config,
"creds": auth.creds,
"next": next if next else request.url.path "next": next if next else request.url.path
} }

View file

@ -3,7 +3,7 @@
{% set header_cls = "%s %s" | format(header_cls, "comment-deleted") %} {% set header_cls = "%s %s" | format(header_cls, "comment-deleted") %}
{% endif %} {% endif %}
{% if not comment.Deleter or request.user.has_credential("CRED_COMMENT_VIEW_DELETED", approved=[comment.Deleter]) %} {% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %}
{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %} {% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}
<h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}"> <h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}">

View file

@ -53,7 +53,7 @@
</p> </p>
{% endif %} {% endif %}
{% if request.user.has_credential("CRED_ACCOUNT_CHANGE_TYPE") %} {% if request.user.has_credential(creds.ACCOUNT_CHANGE_TYPE) %}
<p> <p>
<label for="id_type"> <label for="id_type">
{% trans %}Account Type{% endtrans %}: {% trans %}Account Type{% endtrans %}:

View file

@ -21,7 +21,7 @@
</li> </li>
{# Only CRED_ACCOUNT_SEARCH privileged users see Accounts #} {# Only CRED_ACCOUNT_SEARCH privileged users see Accounts #}
{% if request.user.has_credential("CRED_ACCOUNT_SEARCH") %} {% if request.user.has_credential(creds.ACCOUNT_SEARCH) %}
<li> <li>
<a href="/accounts"> <a href="/accounts">
{% trans %}Accounts{% endtrans %} {% trans %}Accounts{% endtrans %}
@ -37,7 +37,7 @@
</li> </li>
{# Only CRED_TU_LIST_VOTES privileged users see Trusted User #} {# Only CRED_TU_LIST_VOTES privileged users see Trusted User #}
{% if request.user.has_credential("CRED_TU_LIST_VOTES") %} {% if request.user.has_credential(creds.TU_LIST_VOTES) %}
<li> <li>
<a href="/tu">{% trans %}Trusted User{% endtrans %}</a> <a href="/tu">{% trans %}Trusted User{% endtrans %}</a>
</li> </li>

View file

@ -1,7 +1,7 @@
{% set pkgbasename = comment.PackageBase.Name %} {% set pkgbasename = comment.PackageBase.Name %}
{% if not comment.Deleter %} {% if not comment.Deleter %}
{% if request.user.has_credential('CRED_COMMENT_DELETE', approved=[comment.User]) %} {% if request.user.has_credential(creds.COMMENT_DELETE, approved=[comment.User]) %}
<form class="delete-comment-form" <form class="delete-comment-form"
method="post" method="post"
action="/pkgbase/{{ pkgbasename }}/comments/{{ comment.ID }}/delete" action="/pkgbase/{{ pkgbasename }}/comments/{{ comment.ID }}/delete"
@ -22,7 +22,7 @@
</form> </form>
{% endif %} {% endif %}
{% if request.user.has_credential('CRED_COMMENT_EDIT', approved=[comment.User]) %} {% if request.user.has_credential(creds.COMMENT_EDIT, approved=[comment.User]) %}
<a id="comment-edit-link-{{ comment.ID }}" <a id="comment-edit-link-{{ comment.ID }}"
{# /pkgbase/{name}/comments/{id}/edit #} {# /pkgbase/{name}/comments/{id}/edit #}
href="/pkgbase/{{ pkgbasename }}/comments/{{ comment.ID }}/edit?{{ {'next': request.url.path} | urlencode }}" href="/pkgbase/{{ pkgbasename }}/comments/{{ comment.ID }}/edit?{{ {'next': request.url.path} | urlencode }}"
@ -47,7 +47,7 @@
{% endif %} {% endif %}
{% if request.user.has_credential("CRED_COMMENT_PIN", approved=[comment.PackageBase.Maintainer]) %} {% if request.user.has_credential(creds.COMMENT_PIN, approved=[comment.PackageBase.Maintainer]) %}
{% if comment.PinnedTS %} {% if comment.PinnedTS %}
<form class="pin-comment-form" <form class="pin-comment-form"
method="post" method="post"
@ -82,7 +82,7 @@
</form> </form>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% elif request.user.has_credential("CRED_COMMENT_UNDELETE", approved=[comment.User]) %} {% elif request.user.has_credential(creds.COMMENT_UNDELETE, approved=[comment.User]) %}
<form class="undelete-comment-form" <form class="undelete-comment-form"
method="post" method="post"
action="/pkgbase/{{ comment.PackageBase.Name }}/comments/{{ comment.ID }}/undelete" action="/pkgbase/{{ comment.PackageBase.Name }}/comments/{{ comment.ID }}/undelete"

View file

@ -88,7 +88,7 @@
</form> </form>
{% endif %} {% endif %}
</li> </li>
{% if request.user.has_credential('CRED_PKGBASE_EDIT_COMAINTAINERS', approved=[pkgbase.Maintainer]) %} {% if request.user.has_credential(creds.PKGBASE_EDIT_COMAINTAINERS, approved=[pkgbase.Maintainer]) %}
<li> <li>
<a href="/pkgbase/{{ pkgbase.Name }}/comaintainers"> <a href="/pkgbase/{{ pkgbase.Name }}/comaintainers">
{{ "Manage Co-Maintainers" | tr }} {{ "Manage Co-Maintainers" | tr }}
@ -107,14 +107,14 @@
{{ "Submit Request" | tr }} {{ "Submit Request" | tr }}
</a> </a>
</li> </li>
{% if request.user.has_credential("CRED_PKGBASE_DELETE") %} {% if request.user.has_credential(creds.PKGBASE_DELETE) %}
<li> <li>
<a href="/pkgbase/{{ pkgbase.Name }}/delete"> <a href="/pkgbase/{{ pkgbase.Name }}/delete">
{{ "Delete Package" | tr }} {{ "Delete Package" | tr }}
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% if request.user.has_credential("CRED_PKGBASE_MERGE") %} {% if request.user.has_credential(creds.PKGBASE_MERGE) %}
<li> <li>
<a href="/pkgbase/{{ pkgbase.Name }}/merge"> <a href="/pkgbase/{{ pkgbase.Name }}/merge">
{{ "Merge Package" | tr }} {{ "Merge Package" | tr }}
@ -130,7 +130,7 @@
/> />
</form> </form>
</li> </li>
{% elif request.user.has_credential("CRED_PKGBASE_DISOWN", approved=[pkgbase.Maintainer]) %} {% elif request.user.has_credential(creds.PKGBASE_DISOWN, approved=[pkgbase.Maintainer]) %}
<li> <li>
<a href="/pkgbase/{{ pkgbase.Name }}/disown"> <a href="/pkgbase/{{ pkgbase.Name }}/disown">
{{ "Disown Package" | tr }} {{ "Disown Package" | tr }}

View file

@ -5,7 +5,7 @@
{% set article_cls = "%s %s" | format(article_cls, "comment-deleted") %} {% set article_cls = "%s %s" | format(article_cls, "comment-deleted") %}
{% endif %} {% endif %}
{% if not comment.Deleter or request.user.has_credential("CRED_COMMENT_VIEW_DELETED", approved=[comment.Deleter]) %} {% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %}
<h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}"> <h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}">
{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %} {% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}
{% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %} {% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %}

View file

@ -33,10 +33,10 @@
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if pkgbase.keywords.count() or request.user.has_credential("CRED_PKGBASE_SET_KEYWORDS", approved=[pkgbase.Maintainer]) %} {% if pkgbase.keywords.count() or request.user.has_credential(creds.PKGBASE_SET_KEYWORDS, approved=[pkgbase.Maintainer]) %}
<tr> <tr>
<th>{{ "Keywords" | tr }}:</th> <th>{{ "Keywords" | tr }}:</th>
{% if request.user.has_credential("CRED_PKGBASE_SET_KEYWORDS", approved=[pkgbase.Maintainer]) %} {% if request.user.has_credential(creds.PKGBASE_SET_KEYWORDS, approved=[pkgbase.Maintainer]) %}
<td> <td>
<form method="post" <form method="post"
action="/pkgbase/{{ pkgbase.Name }}/keywords" action="/pkgbase/{{ pkgbase.Name }}/keywords"

View file

@ -5,7 +5,7 @@ import pytest
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from aurweb import db from aurweb import db
from aurweb.auth import AnonymousUser, BasicAuthBackend, account_type_required, has_credential from aurweb.auth import AnonymousUser, BasicAuthBackend, account_type_required
from aurweb.models.account_type import USER, USER_ID from aurweb.models.account_type import USER, USER_ID
from aurweb.models.session import Session from aurweb.models.session import Session
from aurweb.models.user import User from aurweb.models.user import User
@ -67,11 +67,6 @@ async def test_basic_auth_backend():
assert result == user assert result == user
def test_has_fake_credential_fails():
# Fake credential 666 does not exist.
assert not has_credential(user, 666)
def test_account_type_required(): def test_account_type_required():
""" This test merely asserts that a few different paths """ This test merely asserts that a few different paths
do not raise exceptions. """ do not raise exceptions. """
@ -109,8 +104,3 @@ def test_voted_for():
def test_notified(): def test_notified():
user_ = AnonymousUser() user_ = AnonymousUser()
assert not user_.notified(None) assert not user_.notified(None)
def test_has_credential():
user_ = AnonymousUser()
assert not user_.has_credential("FAKE_CREDENTIAL")

View file

@ -10,6 +10,7 @@ import aurweb.auth
import aurweb.config import aurweb.config
from aurweb import db from aurweb import db
from aurweb.auth import creds
from aurweb.models.account_type import AccountType from aurweb.models.account_type import AccountType
from aurweb.models.ban import Ban from aurweb.models.ban import Ban
from aurweb.models.package import Package from aurweb.models.package import Package
@ -154,7 +155,7 @@ def test_user_minimum_passwd_length():
def test_user_has_credential(): def test_user_has_credential():
assert not user.has_credential("CRED_ACCOUNT_CHANGE_TYPE") assert not user.has_credential(aurweb.auth.creds.ACCOUNT_CHANGE_TYPE)
def test_user_ssh_pub_key(): def test_user_ssh_pub_key():
@ -169,10 +170,10 @@ def test_user_ssh_pub_key():
def test_user_credential_types(): def test_user_credential_types():
assert aurweb.auth.user_developer_or_trusted_user(user) assert user.AccountTypeID in creds.user_developer_or_trusted_user
assert not aurweb.auth.trusted_user(user) assert user.AccountTypeID not in creds.trusted_user
assert not aurweb.auth.developer(user) assert user.AccountTypeID not in creds.developer
assert not aurweb.auth.trusted_user_or_dev(user) assert user.AccountTypeID not in creds.trusted_user_or_dev
trusted_user_type = db.query(AccountType).filter( trusted_user_type = db.query(AccountType).filter(
AccountType.AccountType == "Trusted User" AccountType.AccountType == "Trusted User"
@ -180,16 +181,16 @@ def test_user_credential_types():
with db.begin(): with db.begin():
user.AccountType = trusted_user_type user.AccountType = trusted_user_type
assert aurweb.auth.trusted_user(user) assert user.AccountTypeID in creds.trusted_user
assert aurweb.auth.trusted_user_or_dev(user) assert user.AccountTypeID in creds.trusted_user_or_dev
developer_type = db.query(AccountType, developer_type = db.query(AccountType,
AccountType.AccountType == "Developer").first() AccountType.AccountType == "Developer").first()
with db.begin(): with db.begin():
user.AccountType = developer_type user.AccountType = developer_type
assert aurweb.auth.developer(user) assert user.AccountTypeID in creds.developer
assert aurweb.auth.trusted_user_or_dev(user) assert user.AccountTypeID in creds.trusted_user_or_dev
type_str = "Trusted User & Developer" type_str = "Trusted User & Developer"
elevated_type = db.query(AccountType, elevated_type = db.query(AccountType,
@ -197,9 +198,9 @@ def test_user_credential_types():
with db.begin(): with db.begin():
user.AccountType = elevated_type user.AccountType = elevated_type
assert aurweb.auth.trusted_user(user) assert user.AccountTypeID in creds.trusted_user
assert aurweb.auth.developer(user) assert user.AccountTypeID in creds.developer
assert aurweb.auth.trusted_user_or_dev(user) assert user.AccountTypeID in creds.trusted_user_or_dev
# Some model authorization checks. # Some model authorization checks.
assert user.is_elevated() assert user.is_elevated()