mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
Merge branch 'fix-improve-auth' into pu
This commit is contained in:
commit
867825491b
19 changed files with 389 additions and 483 deletions
350
aurweb/auth.py
350
aurweb/auth.py
|
@ -1,350 +0,0 @@
|
|||
import functools
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
|
||||
import fastapi
|
||||
|
||||
from fastapi.responses import RedirectResponse
|
||||
from sqlalchemy import and_
|
||||
from starlette.authentication import AuthCredentials, AuthenticationBackend
|
||||
from starlette.requests import HTTPConnection
|
||||
|
||||
import aurweb.config
|
||||
|
||||
from aurweb import db, l10n, util
|
||||
from aurweb.models import Session, User
|
||||
from aurweb.models.account_type import ACCOUNT_TYPE_ID
|
||||
from aurweb.templates import make_variable_context, render_template
|
||||
|
||||
|
||||
class StubQuery:
|
||||
""" Acts as a stubbed version of an orm.Query. Typically used
|
||||
to masquerade fake records for an AnonymousUser. """
|
||||
|
||||
def filter(self, *args):
|
||||
return StubQuery()
|
||||
|
||||
def scalar(self):
|
||||
return 0
|
||||
|
||||
|
||||
class AnonymousUser:
|
||||
""" A stubbed User class used when an unauthenticated User
|
||||
makes a request against FastAPI. """
|
||||
# Stub attributes used to mimic a real user.
|
||||
ID = 0
|
||||
|
||||
class AccountType:
|
||||
""" A stubbed AccountType static class. In here, we use an ID
|
||||
and AccountType which do not exist in our constant records.
|
||||
All records primary keys (AccountType.ID) should be non-zero,
|
||||
so using a zero here means that we'll never match against a
|
||||
real AccountType. """
|
||||
ID = 0
|
||||
AccountType = "Anonymous"
|
||||
|
||||
# AccountTypeID == AccountType.ID; assign a stubbed column.
|
||||
AccountTypeID = AccountType.ID
|
||||
|
||||
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
|
||||
|
||||
# Add stubbed relationship backrefs.
|
||||
notifications = StubQuery()
|
||||
package_votes = StubQuery()
|
||||
|
||||
# A nonce attribute, needed for all browser sessions; set in __init__.
|
||||
nonce = None
|
||||
|
||||
def __init__(self):
|
||||
self.nonce = util.make_nonce()
|
||||
|
||||
@staticmethod
|
||||
def is_authenticated():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_trusted_user():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_developer():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_elevated():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def has_credential(credential, **kwargs):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def voted_for(package):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def notified(package):
|
||||
return False
|
||||
|
||||
|
||||
class BasicAuthBackend(AuthenticationBackend):
|
||||
async def authenticate(self, conn: HTTPConnection):
|
||||
sid = conn.cookies.get("AURSID")
|
||||
if not sid:
|
||||
return (None, AnonymousUser())
|
||||
|
||||
now_ts = datetime.utcnow().timestamp()
|
||||
record = db.query(Session).filter(
|
||||
and_(Session.SessionID == sid,
|
||||
Session.LastUpdateTS >= now_ts)).first()
|
||||
|
||||
# If no session with sid and a LastUpdateTS now or later exists.
|
||||
if not record:
|
||||
return (None, AnonymousUser())
|
||||
|
||||
# At this point, we cannot have an invalid user if the record
|
||||
# exists, due to ForeignKey constraints in the schema upheld
|
||||
# by mysqlclient.
|
||||
user = db.query(User).filter(User.ID == record.UsersID).first()
|
||||
user.nonce = util.make_nonce()
|
||||
user.authenticated = True
|
||||
|
||||
return (AuthCredentials(["authenticated"]), user)
|
||||
|
||||
|
||||
def auth_required(is_required: bool = True,
|
||||
login: bool = True,
|
||||
redirect: str = "/",
|
||||
template: tuple = None,
|
||||
status_code: HTTPStatus = HTTPStatus.UNAUTHORIZED):
|
||||
""" Authentication route decorator.
|
||||
|
||||
If redirect is given, the user will be redirected if the auth state
|
||||
does not match is_required.
|
||||
|
||||
If template is given, it will be rendered with Unauthorized if
|
||||
is_required does not match and take priority over redirect.
|
||||
|
||||
A precondition of this function is that, if template is provided,
|
||||
it **must** match the following format:
|
||||
|
||||
template=("template.html", ["Some Template For", "{}"], ["username"])
|
||||
|
||||
Where `username` is a FastAPI request path parameter, fitting
|
||||
a route like: `/some_route/{username}`.
|
||||
|
||||
If you wish to supply a non-formatted template, just omit any Python
|
||||
format strings (with the '{}' substring). The third tuple element
|
||||
will not be used, and so anything can be supplied.
|
||||
|
||||
template=("template.html", ["Some Page"], None)
|
||||
|
||||
All title shards and format parameters will be translated before
|
||||
applying any format operations.
|
||||
|
||||
:param is_required: A boolean indicating whether the function requires auth
|
||||
:param login: Redirect to `/login`, passing `next=<redirect>`
|
||||
:param redirect: Path to redirect to if is_required isn't True
|
||||
:param template: A three-element template tuple:
|
||||
(path, title_iterable, variable_iterable)
|
||||
:param status_code: An optional status_code for template render.
|
||||
Redirects are always SEE_OTHER.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(request, *args, **kwargs):
|
||||
if request.user.is_authenticated() != is_required:
|
||||
url = "/"
|
||||
|
||||
if redirect:
|
||||
path_params_expr = re.compile(r'\{(\w+)\}')
|
||||
match = re.findall(path_params_expr, redirect)
|
||||
args = {k: request.path_params.get(k) for k in match}
|
||||
url = redirect.format(**args)
|
||||
|
||||
if login:
|
||||
url = "/login?" + util.urlencode({"next": url})
|
||||
|
||||
if template:
|
||||
# template=("template.html",
|
||||
# ["Some Title", "someFormatted {}"],
|
||||
# ["variable"])
|
||||
# => render template.html with title:
|
||||
# "Some Title someFormatted variables"
|
||||
path, title_parts, variables = template
|
||||
_ = l10n.get_translator_for_request(request)
|
||||
|
||||
# Step through title_parts; for each part which contains
|
||||
# a '{}' in it, apply .format(var) where var = the current
|
||||
# iteration of variables.
|
||||
#
|
||||
# This implies that len(variables) is equal to
|
||||
# len([part for part in title_parts if '{}' in part])
|
||||
# and this must always be true.
|
||||
#
|
||||
sanitized = []
|
||||
_variables = iter(variables)
|
||||
for part in title_parts:
|
||||
if "{}" in part: # If this part is formattable.
|
||||
key = next(_variables)
|
||||
var = request.path_params.get(key)
|
||||
sanitized.append(_(part.format(var)))
|
||||
else: # Otherwise, just add the translated part.
|
||||
sanitized.append(_(part))
|
||||
|
||||
# Glue all title parts together, separated by spaces.
|
||||
title = " ".join(sanitized)
|
||||
|
||||
context = await make_variable_context(request, title)
|
||||
return render_template(request, path, context,
|
||||
status_code=status_code)
|
||||
return RedirectResponse(url,
|
||||
status_code=int(HTTPStatus.SEE_OTHER))
|
||||
return await func(request, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def account_type_required(one_of: set):
|
||||
""" A decorator that can be used on FastAPI routes to dictate
|
||||
that a user belongs to one of the types defined in one_of.
|
||||
|
||||
This decorator should be run after an @auth_required(True) is
|
||||
dictated.
|
||||
|
||||
- Example code:
|
||||
|
||||
@router.get('/some_route')
|
||||
@auth_required(True)
|
||||
@account_type_required({"Trusted User", "Trusted User & Developer"})
|
||||
async def some_route(request: fastapi.Request):
|
||||
return Response()
|
||||
|
||||
:param one_of: A set consisting of strings to match against AccountType.
|
||||
:return: Return the FastAPI function this decorator wraps.
|
||||
"""
|
||||
# Convert any account type string constants to their integer IDs.
|
||||
one_of = {
|
||||
ACCOUNT_TYPE_ID[atype]
|
||||
for atype in one_of
|
||||
if isinstance(atype, str)
|
||||
}
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(request: fastapi.Request, *args, **kwargs):
|
||||
if request.user.AccountType.ID not in one_of:
|
||||
return RedirectResponse("/",
|
||||
status_code=int(HTTPStatus.SEE_OTHER))
|
||||
return await func(request, *args, **kwargs)
|
||||
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
|
||||
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
|
192
aurweb/auth/__init__.py
Normal file
192
aurweb/auth/__init__.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
import functools
|
||||
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
|
||||
import fastapi
|
||||
|
||||
from fastapi import HTTPException
|
||||
from fastapi.responses import RedirectResponse
|
||||
from sqlalchemy import and_
|
||||
from starlette.authentication import AuthCredentials, AuthenticationBackend
|
||||
from starlette.requests import HTTPConnection
|
||||
|
||||
import aurweb.config
|
||||
|
||||
from aurweb import db, l10n, util
|
||||
from aurweb.models import Session, User
|
||||
from aurweb.models.account_type import ACCOUNT_TYPE_ID
|
||||
|
||||
|
||||
class StubQuery:
|
||||
""" Acts as a stubbed version of an orm.Query. Typically used
|
||||
to masquerade fake records for an AnonymousUser. """
|
||||
|
||||
def filter(self, *args):
|
||||
return StubQuery()
|
||||
|
||||
def scalar(self):
|
||||
return 0
|
||||
|
||||
|
||||
class AnonymousUser:
|
||||
""" A stubbed User class used when an unauthenticated User
|
||||
makes a request against FastAPI. """
|
||||
# Stub attributes used to mimic a real user.
|
||||
ID = 0
|
||||
|
||||
class AccountType:
|
||||
""" A stubbed AccountType static class. In here, we use an ID
|
||||
and AccountType which do not exist in our constant records.
|
||||
All records primary keys (AccountType.ID) should be non-zero,
|
||||
so using a zero here means that we'll never match against a
|
||||
real AccountType. """
|
||||
ID = 0
|
||||
AccountType = "Anonymous"
|
||||
|
||||
# AccountTypeID == AccountType.ID; assign a stubbed column.
|
||||
AccountTypeID = AccountType.ID
|
||||
|
||||
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
|
||||
|
||||
# Add stubbed relationship backrefs.
|
||||
notifications = StubQuery()
|
||||
package_votes = StubQuery()
|
||||
|
||||
# A nonce attribute, needed for all browser sessions; set in __init__.
|
||||
nonce = None
|
||||
|
||||
def __init__(self):
|
||||
self.nonce = util.make_nonce()
|
||||
|
||||
@staticmethod
|
||||
def is_authenticated():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_trusted_user():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_developer():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_elevated():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def has_credential(credential, **kwargs):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def voted_for(package):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def notified(package):
|
||||
return False
|
||||
|
||||
|
||||
class BasicAuthBackend(AuthenticationBackend):
|
||||
async def authenticate(self, conn: HTTPConnection):
|
||||
sid = conn.cookies.get("AURSID")
|
||||
if not sid:
|
||||
return (None, AnonymousUser())
|
||||
|
||||
now_ts = datetime.utcnow().timestamp()
|
||||
record = db.query(Session).filter(
|
||||
and_(Session.SessionID == sid,
|
||||
Session.LastUpdateTS >= now_ts)).first()
|
||||
|
||||
# If no session with sid and a LastUpdateTS now or later exists.
|
||||
if not record:
|
||||
return (None, AnonymousUser())
|
||||
|
||||
# At this point, we cannot have an invalid user if the record
|
||||
# exists, due to ForeignKey constraints in the schema upheld
|
||||
# by mysqlclient.
|
||||
user = db.query(User).filter(User.ID == record.UsersID).first()
|
||||
user.nonce = util.make_nonce()
|
||||
user.authenticated = True
|
||||
|
||||
return (AuthCredentials(["authenticated"]), user)
|
||||
|
||||
|
||||
def auth_required(is_required: bool = True,
|
||||
template: tuple = None,
|
||||
status_code: HTTPStatus = HTTPStatus.UNAUTHORIZED):
|
||||
""" Authentication route decorator.
|
||||
|
||||
:param is_required: A boolean indicating whether the function requires auth
|
||||
:param status_code: An optional status_code for template render.
|
||||
Redirects are always SEE_OTHER.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(request, *args, **kwargs):
|
||||
if request.user.is_authenticated() != is_required:
|
||||
url = "/"
|
||||
|
||||
if is_required:
|
||||
if request.method == "GET":
|
||||
url = request.url.path
|
||||
elif request.method == "POST" and (referer := request.headers.get("Referer")):
|
||||
aur = aurweb.config.get("options", "aur_location") + "/"
|
||||
if not referer.startswith(aur):
|
||||
_ = l10n.get_translator_for_request(request)
|
||||
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail=_("Bad Referer header."))
|
||||
url = referer[len(aur) - 1:]
|
||||
|
||||
url = "/login?" + util.urlencode({"next": url})
|
||||
return RedirectResponse(url,
|
||||
status_code=int(HTTPStatus.SEE_OTHER))
|
||||
return await func(request, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def account_type_required(one_of: set):
|
||||
""" A decorator that can be used on FastAPI routes to dictate
|
||||
that a user belongs to one of the types defined in one_of.
|
||||
|
||||
This decorator should be run after an @auth_required(True) is
|
||||
dictated.
|
||||
|
||||
- Example code:
|
||||
|
||||
@router.get('/some_route')
|
||||
@auth_required(True)
|
||||
@account_type_required({"Trusted User", "Trusted User & Developer"})
|
||||
async def some_route(request: fastapi.Request):
|
||||
return Response()
|
||||
|
||||
:param one_of: A set consisting of strings to match against AccountType.
|
||||
:return: Return the FastAPI function this decorator wraps.
|
||||
"""
|
||||
# Convert any account type string constants to their integer IDs.
|
||||
one_of = {
|
||||
ACCOUNT_TYPE_ID[atype]
|
||||
for atype in one_of
|
||||
if isinstance(atype, str)
|
||||
}
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(request: fastapi.Request, *args, **kwargs):
|
||||
if request.user.AccountTypeID not in one_of:
|
||||
return RedirectResponse("/",
|
||||
status_code=int(HTTPStatus.SEE_OTHER))
|
||||
return await func(request, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
76
aurweb/auth/creds.py
Normal file
76
aurweb/auth/creds.py
Normal 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]
|
|
@ -1,6 +1,7 @@
|
|||
import hashlib
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Set
|
||||
|
||||
import bcrypt
|
||||
|
||||
|
@ -136,10 +137,10 @@ class User(Base):
|
|||
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 has_credential(self, credential: Set[int],
|
||||
approved: List["User"] = list()):
|
||||
from aurweb.auth.creds import has_credential
|
||||
return has_credential(self, credential, approved)
|
||||
|
||||
def logout(self, request):
|
||||
del request.cookies["AURSID"]
|
||||
|
|
|
@ -10,7 +10,7 @@ from sqlalchemy import and_, or_
|
|||
import aurweb.config
|
||||
|
||||
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.exceptions import ValidationError
|
||||
from aurweb.l10n import get_translator_for_request
|
||||
|
@ -27,14 +27,14 @@ logger = logging.get_logger(__name__)
|
|||
|
||||
|
||||
@router.get("/passreset", response_class=HTMLResponse)
|
||||
@auth_required(False, login=False)
|
||||
@auth_required(False)
|
||||
async def passreset(request: Request):
|
||||
context = await make_variable_context(request, "Password Reset")
|
||||
return render_template(request, "passreset.html", context)
|
||||
|
||||
|
||||
@router.post("/passreset", response_class=HTMLResponse)
|
||||
@auth_required(False, login=False)
|
||||
@auth_required(False)
|
||||
async def passreset_post(request: Request,
|
||||
user: str = Form(...),
|
||||
resetkey: str = Form(default=None),
|
||||
|
@ -176,7 +176,7 @@ def make_account_form_context(context: dict,
|
|||
|
||||
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.TRUSTED_USER_AND_DEV_ID,
|
||||
at.TRUSTED_USER_AND_DEV))
|
||||
|
@ -226,7 +226,7 @@ def make_account_form_context(context: dict,
|
|||
|
||||
|
||||
@router.get("/register", response_class=HTMLResponse)
|
||||
@auth_required(False, login=False)
|
||||
@auth_required(False)
|
||||
async def account_register(request: Request,
|
||||
U: str = Form(default=str()), # Username
|
||||
E: str = Form(default=str()), # Email
|
||||
|
@ -252,7 +252,7 @@ async def account_register(request: Request,
|
|||
|
||||
|
||||
@router.post("/register", response_class=HTMLResponse)
|
||||
@auth_required(False, login=False)
|
||||
@auth_required(False)
|
||||
async def account_register_post(request: Request,
|
||||
U: str = Form(default=str()), # Username
|
||||
E: str = Form(default=str()), # Email
|
||||
|
@ -332,7 +332,7 @@ async def account_register_post(request: Request,
|
|||
def cannot_edit(request, user):
|
||||
""" Return a 401 HTMLResponse if the request user doesn't
|
||||
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])
|
||||
if not has_dev_cred:
|
||||
return HTMLResponse(status_code=HTTPStatus.UNAUTHORIZED)
|
||||
|
@ -340,7 +340,7 @@ def cannot_edit(request, user):
|
|||
|
||||
|
||||
@router.get("/account/{username}/edit", response_class=HTMLResponse)
|
||||
@auth_required(True, redirect="/account/{username}")
|
||||
@auth_required()
|
||||
async def account_edit(request: Request, username: str):
|
||||
user = db.query(models.User, models.User.Username == username).first()
|
||||
|
||||
|
@ -356,7 +356,7 @@ async def account_edit(request: Request, username: str):
|
|||
|
||||
|
||||
@router.post("/account/{username}/edit", response_class=HTMLResponse)
|
||||
@auth_required(True, redirect="/account/{username}")
|
||||
@auth_required()
|
||||
async def account_edit_post(request: Request,
|
||||
username: str,
|
||||
U: str = Form(default=str()), # Username
|
||||
|
@ -424,26 +424,20 @@ async def account_edit_post(request: Request,
|
|||
aurtz=TZ, aurlang=L)
|
||||
|
||||
|
||||
account_template = (
|
||||
"account/show.html",
|
||||
["Account", "{}"],
|
||||
["username"] # Query parameters to replace in the title string.
|
||||
)
|
||||
|
||||
|
||||
@router.get("/account/{username}")
|
||||
@auth_required(True, template=account_template,
|
||||
status_code=HTTPStatus.UNAUTHORIZED)
|
||||
async def account(request: Request, username: str):
|
||||
_ = l10n.get_translator_for_request(request)
|
||||
context = await make_variable_context(
|
||||
request, _("Account") + " " + username)
|
||||
if not request.user.is_authenticated():
|
||||
return render_template(request, "account/show.html", context,
|
||||
status_code=HTTPStatus.UNAUTHORIZED)
|
||||
context["user"] = get_user_by_name(username)
|
||||
return render_template(request, "account/show.html", context)
|
||||
|
||||
|
||||
@router.get("/account/{username}/comments")
|
||||
@auth_required(redirect="/account/{username}/comments")
|
||||
@auth_required()
|
||||
async def account_comments(request: Request, username: str):
|
||||
user = get_user_by_name(username)
|
||||
context = make_context(request, "Accounts")
|
||||
|
@ -454,7 +448,7 @@ async def account_comments(request: Request, username: str):
|
|||
|
||||
|
||||
@router.get("/accounts")
|
||||
@auth_required(True, redirect="/accounts")
|
||||
@auth_required()
|
||||
@account_type_required({at.TRUSTED_USER,
|
||||
at.DEVELOPER,
|
||||
at.TRUSTED_USER_AND_DEV})
|
||||
|
@ -464,7 +458,7 @@ async def accounts(request: Request):
|
|||
|
||||
|
||||
@router.post("/accounts")
|
||||
@auth_required(True, redirect="/accounts")
|
||||
@auth_required()
|
||||
@account_type_required({at.TRUSTED_USER,
|
||||
at.DEVELOPER,
|
||||
at.TRUSTED_USER_AND_DEV})
|
||||
|
@ -548,7 +542,7 @@ def render_terms_of_service(request: Request,
|
|||
|
||||
|
||||
@router.get("/tos")
|
||||
@auth_required(True, redirect="/tos")
|
||||
@auth_required()
|
||||
async def terms_of_service(request: Request):
|
||||
# Query the database for terms that were previously accepted,
|
||||
# but now have a bumped Revision that needs to be accepted.
|
||||
|
@ -572,7 +566,7 @@ async def terms_of_service(request: Request):
|
|||
|
||||
|
||||
@router.post("/tos")
|
||||
@auth_required(True, redirect="/tos")
|
||||
@auth_required()
|
||||
async def terms_of_service_post(request: Request,
|
||||
accept: bool = Form(default=False)):
|
||||
# Query the database for terms that were previously accepted,
|
||||
|
|
|
@ -29,7 +29,7 @@ async def login_get(request: Request, next: str = "/"):
|
|||
|
||||
|
||||
@router.post("/login", response_class=HTMLResponse)
|
||||
@auth_required(False, login=False)
|
||||
@auth_required(False)
|
||||
async def login_post(request: Request,
|
||||
next: str = Form(...),
|
||||
user: str = Form(default=str()),
|
||||
|
|
|
@ -10,7 +10,7 @@ import aurweb.filters
|
|||
import aurweb.packages.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.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID
|
||||
from aurweb.models.relation_type import CONFLICTS_ID, PROVIDES_ID, REPLACES_ID
|
||||
|
@ -295,7 +295,7 @@ async def package_base_voters(request: Request, name: str) -> Response:
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/comments")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comments")
|
||||
@auth_required()
|
||||
async def pkgbase_comments_post(
|
||||
request: Request, name: str,
|
||||
comment: str = Form(default=str()),
|
||||
|
@ -327,7 +327,7 @@ async def pkgbase_comments_post(
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/comments/{id}/form")
|
||||
@auth_required(True, login=False)
|
||||
@auth_required()
|
||||
async def pkgbase_comment_form(request: Request, name: str, id: int,
|
||||
next: str = Query(default=None)):
|
||||
""" Produce a comment form for comment {id}. """
|
||||
|
@ -353,7 +353,7 @@ async def pkgbase_comment_form(request: Request, name: str, id: int,
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/comments/{id}")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comments/{id}")
|
||||
@auth_required()
|
||||
async def pkgbase_comment_post(
|
||||
request: Request, name: str, id: int,
|
||||
comment: str = Form(default=str()),
|
||||
|
@ -392,7 +392,7 @@ async def pkgbase_comment_post(
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/comments/{id}/edit")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comments/{id}/edit")
|
||||
@auth_required()
|
||||
async def pkgbase_comment_edit(request: Request, name: str, id: int,
|
||||
next: str = Form(default=None)):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
|
@ -407,13 +407,13 @@ async def pkgbase_comment_edit(request: Request, name: str, id: int,
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/comments/{id}/delete")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comments/{id}/delete")
|
||||
@auth_required()
|
||||
async def pkgbase_comment_delete(request: Request, name: str, id: int,
|
||||
next: str = Form(default=None)):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
comment = get_pkgbase_comment(pkgbase, id)
|
||||
|
||||
authorized = request.user.has_credential("CRED_COMMENT_DELETE",
|
||||
authorized = request.user.has_credential(creds.COMMENT_DELETE,
|
||||
[comment.User])
|
||||
if not authorized:
|
||||
_ = l10n.get_translator_for_request(request)
|
||||
|
@ -433,13 +433,13 @@ async def pkgbase_comment_delete(request: Request, name: str, id: int,
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/comments/{id}/undelete")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comments/{id}/undelete")
|
||||
@auth_required()
|
||||
async def pkgbase_comment_undelete(request: Request, name: str, id: int,
|
||||
next: str = Form(default=None)):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
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])
|
||||
if not has_cred:
|
||||
_ = l10n.get_translator_for_request(request)
|
||||
|
@ -458,13 +458,13 @@ async def pkgbase_comment_undelete(request: Request, name: str, id: int,
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/comments/{id}/pin")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comments/{id}/pin")
|
||||
@auth_required()
|
||||
async def pkgbase_comment_pin(request: Request, name: str, id: int,
|
||||
next: str = Form(default=None)):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
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])
|
||||
if not has_cred:
|
||||
_ = l10n.get_translator_for_request(request)
|
||||
|
@ -483,13 +483,13 @@ async def pkgbase_comment_pin(request: Request, name: str, id: int,
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/comments/{id}/unpin")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comments/{id}/unpin")
|
||||
@auth_required()
|
||||
async def pkgbase_comment_unpin(request: Request, name: str, id: int,
|
||||
next: str = Form(default=None)):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
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])
|
||||
if not has_cred:
|
||||
_ = l10n.get_translator_for_request(request)
|
||||
|
@ -507,14 +507,14 @@ async def pkgbase_comment_unpin(request: Request, name: str, id: int,
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/comaintainers")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comaintainers")
|
||||
@auth_required()
|
||||
async def package_base_comaintainers(request: Request, name: str) -> Response:
|
||||
# Get the PackageBase.
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
|
||||
# Unauthorized users (Non-TU/Dev and not the pkgbase maintainer)
|
||||
# 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])
|
||||
if not has_creds:
|
||||
return RedirectResponse(f"/pkgbase/{name}",
|
||||
|
@ -532,7 +532,7 @@ async def package_base_comaintainers(request: Request, name: str) -> Response:
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/comaintainers")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/comaintainers")
|
||||
@auth_required()
|
||||
async def package_base_comaintainers_post(
|
||||
request: Request, name: str,
|
||||
users: str = Form(default=str())) -> Response:
|
||||
|
@ -541,7 +541,7 @@ async def package_base_comaintainers_post(
|
|||
|
||||
# Unauthorized users (Non-TU/Dev and not the pkgbase maintainer)
|
||||
# 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])
|
||||
if not has_creds:
|
||||
return RedirectResponse(f"/pkgbase/{name}",
|
||||
|
@ -584,7 +584,7 @@ async def package_base_comaintainers_post(
|
|||
|
||||
|
||||
@router.get("/requests")
|
||||
@auth_required(True, redirect="/requests")
|
||||
@auth_required()
|
||||
async def requests(request: Request,
|
||||
O: int = Query(default=defaults.O),
|
||||
PP: int = Query(default=defaults.PP)):
|
||||
|
@ -618,7 +618,7 @@ async def requests(request: Request,
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/request")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/request")
|
||||
@auth_required()
|
||||
async def package_request(request: Request, name: str):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
context = await make_variable_context(request, "Submit Request")
|
||||
|
@ -627,7 +627,7 @@ async def package_request(request: Request, name: str):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/request")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/request")
|
||||
@auth_required()
|
||||
async def pkgbase_request_post(request: Request, name: str,
|
||||
type: str = Form(...),
|
||||
merge_into: str = Form(default=None),
|
||||
|
@ -699,7 +699,7 @@ async def pkgbase_request_post(request: Request, name: str,
|
|||
|
||||
|
||||
@router.get("/requests/{id}/close")
|
||||
@auth_required(True, redirect="/requests/{id}/close")
|
||||
@auth_required()
|
||||
async def requests_close(request: Request, id: int):
|
||||
pkgreq = get_pkgreq_by_id(id)
|
||||
if not request.user.is_elevated() and request.user != pkgreq.User:
|
||||
|
@ -712,7 +712,7 @@ async def requests_close(request: Request, id: int):
|
|||
|
||||
|
||||
@router.post("/requests/{id}/close")
|
||||
@auth_required(True, redirect="/requests/{id}/close")
|
||||
@auth_required()
|
||||
async def requests_close_post(request: Request, id: int,
|
||||
reason: int = Form(default=0),
|
||||
comments: str = Form(default=str())):
|
||||
|
@ -775,11 +775,11 @@ async def pkgbase_keywords(request: Request, name: str,
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/flag")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/flag")
|
||||
@auth_required()
|
||||
async def pkgbase_flag_get(request: Request, name: str):
|
||||
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:
|
||||
return RedirectResponse(f"/pkgbase/{name}",
|
||||
status_code=HTTPStatus.SEE_OTHER)
|
||||
|
@ -790,7 +790,7 @@ async def pkgbase_flag_get(request: Request, name: str):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/flag")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/flag")
|
||||
@auth_required()
|
||||
async def pkgbase_flag_post(request: Request, name: str,
|
||||
comments: str = Form(default=str())):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
|
@ -803,7 +803,7 @@ async def pkgbase_flag_post(request: Request, name: str,
|
|||
return render_template(request, "packages/flag.html", context,
|
||||
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:
|
||||
now = int(datetime.utcnow().timestamp())
|
||||
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):
|
||||
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:
|
||||
with db.begin():
|
||||
pkgbase.OutOfDateTS = None
|
||||
|
@ -839,7 +839,7 @@ def pkgbase_unflag_instance(request: Request, pkgbase: models.PackageBase):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/unflag")
|
||||
@auth_required(True, redirect="/pkgbase/{name}")
|
||||
@auth_required()
|
||||
async def pkgbase_unflag(request: Request, name: str):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
pkgbase_unflag_instance(request, pkgbase)
|
||||
|
@ -851,7 +851,7 @@ def pkgbase_notify_instance(request: Request, pkgbase: models.PackageBase):
|
|||
notif = db.query(pkgbase.notifications.filter(
|
||||
models.PackageNotification.UserID == request.user.ID
|
||||
).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:
|
||||
with db.begin():
|
||||
db.create(models.PackageNotification,
|
||||
|
@ -860,7 +860,7 @@ def pkgbase_notify_instance(request: Request, pkgbase: models.PackageBase):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/notify")
|
||||
@auth_required(True, redirect="/pkgbase/{name}")
|
||||
@auth_required()
|
||||
async def pkgbase_notify(request: Request, name: str):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
pkgbase_notify_instance(request, pkgbase)
|
||||
|
@ -872,14 +872,14 @@ def pkgbase_unnotify_instance(request: Request, pkgbase: models.PackageBase):
|
|||
notif = pkgbase.notifications.filter(
|
||||
models.PackageNotification.UserID == request.user.ID
|
||||
).first()
|
||||
has_cred = request.user.has_credential("CRED_PKGBASE_NOTIFY")
|
||||
has_cred = request.user.has_credential(creds.PKGBASE_NOTIFY)
|
||||
if has_cred and notif:
|
||||
with db.begin():
|
||||
db.delete(notif)
|
||||
|
||||
|
||||
@router.post("/pkgbase/{name}/unnotify")
|
||||
@auth_required(True, redirect="/pkgbase/{name}")
|
||||
@auth_required()
|
||||
async def pkgbase_unnotify(request: Request, name: str):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
pkgbase_unnotify_instance(request, pkgbase)
|
||||
|
@ -888,14 +888,14 @@ async def pkgbase_unnotify(request: Request, name: str):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/vote")
|
||||
@auth_required(True, redirect="/pkgbase/{name}")
|
||||
@auth_required()
|
||||
async def pkgbase_vote(request: Request, name: str):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
|
||||
vote = pkgbase.package_votes.filter(
|
||||
models.PackageVote.UsersID == request.user.ID
|
||||
).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:
|
||||
now = int(datetime.utcnow().timestamp())
|
||||
with db.begin():
|
||||
|
@ -912,14 +912,14 @@ async def pkgbase_vote(request: Request, name: str):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/unvote")
|
||||
@auth_required(True, redirect="/pkgbase/{name}")
|
||||
@auth_required()
|
||||
async def pkgbase_unvote(request: Request, name: str):
|
||||
pkgbase = get_pkg_or_base(name, models.PackageBase)
|
||||
|
||||
vote = pkgbase.package_votes.filter(
|
||||
models.PackageVote.UsersID == request.user.ID
|
||||
).first()
|
||||
has_cred = request.user.has_credential("CRED_PKGBASE_VOTE")
|
||||
has_cred = request.user.has_credential(creds.PKGBASE_VOTE)
|
||||
if has_cred and vote:
|
||||
with db.begin():
|
||||
db.delete(vote)
|
||||
|
@ -954,11 +954,11 @@ def pkgbase_disown_instance(request: Request, pkgbase: models.PackageBase):
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/disown")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/disown")
|
||||
@auth_required()
|
||||
async def pkgbase_disown_get(request: Request, name: str):
|
||||
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])
|
||||
if not has_cred:
|
||||
return RedirectResponse(f"/pkgbase/{name}",
|
||||
|
@ -970,12 +970,12 @@ async def pkgbase_disown_get(request: Request, name: str):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/disown")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/disown")
|
||||
@auth_required()
|
||||
async def pkgbase_disown_post(request: Request, name: str,
|
||||
confirm: bool = Form(default=False)):
|
||||
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])
|
||||
if not has_cred:
|
||||
return RedirectResponse(f"/pkgbase/{name}",
|
||||
|
@ -1003,11 +1003,11 @@ def pkgbase_adopt_instance(request: Request, pkgbase: models.PackageBase):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/adopt")
|
||||
@auth_required(True, redirect="/pkgbase/{name}")
|
||||
@auth_required()
|
||||
async def pkgbase_adopt_post(request: Request, name: str):
|
||||
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 the user has credentials, they'll adopt the package regardless
|
||||
# of maintainership. Otherwise, we'll promote the user to maintainer
|
||||
|
@ -1019,9 +1019,9 @@ async def pkgbase_adopt_post(request: Request, name: str):
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/delete")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/delete")
|
||||
@auth_required()
|
||||
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}",
|
||||
status_code=HTTPStatus.SEE_OTHER)
|
||||
|
||||
|
@ -1031,12 +1031,12 @@ async def pkgbase_delete_get(request: Request, name: str):
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/delete")
|
||||
@auth_required(True, redirect="/pkgbase/{name}/delete")
|
||||
@auth_required()
|
||||
async def pkgbase_delete_post(request: Request, name: str,
|
||||
confirm: bool = Form(default=False)):
|
||||
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}",
|
||||
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()
|
||||
for pkg in packages:
|
||||
has_cred = request.user.has_credential(
|
||||
"CRED_PKGBASE_UNFLAG", approved=[pkg.PackageBase.Flagger])
|
||||
creds.PKGBASE_UNFLAG, approved=[pkg.PackageBase.Flagger])
|
||||
if not has_cred:
|
||||
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(
|
||||
models.PackageNotification.UserID == request.user.ID
|
||||
).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
|
||||
# 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.
|
||||
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):
|
||||
# TODO: This error needs to be translated.
|
||||
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.
|
||||
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])
|
||||
if not has_cred:
|
||||
# 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, "
|
||||
"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."])
|
||||
|
||||
# A "memo" used to store names of packages that we delete.
|
||||
|
@ -1279,7 +1279,7 @@ PACKAGE_ACTIONS = {
|
|||
|
||||
|
||||
@router.post("/packages")
|
||||
@auth_required(redirect="/packages")
|
||||
@auth_required()
|
||||
async def packages_post(request: Request,
|
||||
IDs: List[int] = Form(default=[]),
|
||||
action: str = Form(default=str()),
|
||||
|
@ -1311,7 +1311,7 @@ async def packages_post(request: Request,
|
|||
|
||||
|
||||
@router.get("/pkgbase/{name}/merge")
|
||||
@auth_required(redirect="/pkgbase/{name}/merge")
|
||||
@auth_required()
|
||||
async def pkgbase_merge_get(request: Request, name: str,
|
||||
into: str = Query(default=str()),
|
||||
next: str = Query(default=str())):
|
||||
|
@ -1329,10 +1329,10 @@ async def pkgbase_merge_get(request: Request, name: str,
|
|||
|
||||
status_code = HTTPStatus.OK
|
||||
# TODO: Lookup errors from credential instead of hardcoding them.
|
||||
# Idea: Something like credential_errors("CRED_PKGBASE_MERGE").
|
||||
# Perhaps additionally: bad_credential_status_code("CRED_PKGBASE_MERGE").
|
||||
# Idea: Something like credential_errors(creds.PKGBASE_MERGE).
|
||||
# Perhaps additionally: bad_credential_status_code(creds.PKGBASE_MERGE).
|
||||
# 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"] = [
|
||||
"Only Trusted Users and Developers can merge packages."]
|
||||
status_code = HTTPStatus.UNAUTHORIZED
|
||||
|
@ -1423,7 +1423,7 @@ def pkgbase_merge_instance(request: Request, pkgbase: models.PackageBase,
|
|||
|
||||
|
||||
@router.post("/pkgbase/{name}/merge")
|
||||
@auth_required(redirect="/pkgbase/{name}/merge")
|
||||
@auth_required()
|
||||
async def pkgbase_merge_post(request: Request, name: str,
|
||||
into: str = Form(default=str()),
|
||||
confirm: bool = Form(default=False),
|
||||
|
@ -1434,7 +1434,7 @@ async def pkgbase_merge_post(request: Request, name: str,
|
|||
context["pkgbase"] = pkgbase
|
||||
|
||||
# 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"] = [
|
||||
"Only Trusted Users and Developers can merge packages."]
|
||||
return render_template(request, "pkgbase/merge.html", context,
|
||||
|
|
|
@ -41,7 +41,7 @@ ADDVOTE_SPECIFICS = {
|
|||
|
||||
|
||||
@router.get("/tu")
|
||||
@auth_required(True, redirect="/tu")
|
||||
@auth_required()
|
||||
@account_type_required(REQUIRED_TYPES)
|
||||
async def trusted_user(request: Request,
|
||||
coff: int = 0, # current offset
|
||||
|
@ -147,7 +147,7 @@ def render_proposal(request: Request,
|
|||
|
||||
|
||||
@router.get("/tu/{proposal}")
|
||||
@auth_required(True, redirect="/tu/{proposal}")
|
||||
@auth_required()
|
||||
@account_type_required(REQUIRED_TYPES)
|
||||
async def trusted_user_proposal(request: Request, proposal: int):
|
||||
context = await make_variable_context(request, "Trusted User")
|
||||
|
@ -176,7 +176,7 @@ async def trusted_user_proposal(request: Request, proposal: int):
|
|||
|
||||
|
||||
@router.post("/tu/{proposal}")
|
||||
@auth_required(True, redirect="/tu/{proposal}")
|
||||
@auth_required()
|
||||
@account_type_required(REQUIRED_TYPES)
|
||||
async def trusted_user_proposal_post(request: Request,
|
||||
proposal: int,
|
||||
|
@ -227,8 +227,8 @@ async def trusted_user_proposal_post(request: Request,
|
|||
|
||||
|
||||
@router.get("/addvote")
|
||||
@auth_required(True, redirect="/addvote")
|
||||
@account_type_required({"Trusted User", "Trusted User & Developer"})
|
||||
@auth_required()
|
||||
@account_type_required({TRUSTED_USER, TRUSTED_USER_AND_DEV})
|
||||
async def trusted_user_addvote(request: Request,
|
||||
user: str = str(),
|
||||
type: str = "add_tu",
|
||||
|
@ -247,7 +247,7 @@ async def trusted_user_addvote(request: Request,
|
|||
|
||||
|
||||
@router.post("/addvote")
|
||||
@auth_required(True, redirect="/addvote")
|
||||
@auth_required()
|
||||
@account_type_required({TRUSTED_USER, TRUSTED_USER_AND_DEV})
|
||||
async def trusted_user_addvote_post(request: Request,
|
||||
user: str = Form(default=str()),
|
||||
|
|
|
@ -16,7 +16,7 @@ from fastapi.responses import HTMLResponse
|
|||
|
||||
import aurweb.config
|
||||
|
||||
from aurweb import captcha, cookies, l10n, time, util
|
||||
from aurweb import auth, captcha, cookies, l10n, time, util
|
||||
|
||||
# Prepare jinja2 objects.
|
||||
_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)),
|
||||
"utcnow": int(datetime.utcnow().timestamp()),
|
||||
"config": aurweb.config,
|
||||
"creds": auth.creds,
|
||||
"next": next if next else request.url.path
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% set header_cls = "%s %s" | format(header_cls, "comment-deleted") %}
|
||||
{% 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) %}
|
||||
<h4 id="comment-{{ comment.ID }}" class="{{ header_cls }}">
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.has_credential("CRED_ACCOUNT_CHANGE_TYPE") %}
|
||||
{% if request.user.has_credential(creds.ACCOUNT_CHANGE_TYPE) %}
|
||||
<p>
|
||||
<label for="id_type">
|
||||
{% trans %}Account Type{% endtrans %}:
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</li>
|
||||
|
||||
{# 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>
|
||||
<a href="/accounts">
|
||||
{% trans %}Accounts{% endtrans %}
|
||||
|
@ -37,7 +37,7 @@
|
|||
</li>
|
||||
|
||||
{# 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>
|
||||
<a href="/tu">{% trans %}Trusted User{% endtrans %}</a>
|
||||
</li>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% set pkgbasename = comment.PackageBase.Name %}
|
||||
|
||||
{% 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"
|
||||
method="post"
|
||||
action="/pkgbase/{{ pkgbasename }}/comments/{{ comment.ID }}/delete"
|
||||
|
@ -22,7 +22,7 @@
|
|||
</form>
|
||||
{% 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 }}"
|
||||
{# /pkgbase/{name}/comments/{id}/edit #}
|
||||
href="/pkgbase/{{ pkgbasename }}/comments/{{ comment.ID }}/edit?{{ {'next': request.url.path} | urlencode }}"
|
||||
|
@ -47,7 +47,7 @@
|
|||
|
||||
{% 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 %}
|
||||
<form class="pin-comment-form"
|
||||
method="post"
|
||||
|
@ -82,7 +82,7 @@
|
|||
</form>
|
||||
{% 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"
|
||||
method="post"
|
||||
action="/pkgbase/{{ comment.PackageBase.Name }}/comments/{{ comment.ID }}/undelete"
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
</form>
|
||||
{% endif %}
|
||||
</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>
|
||||
<a href="/pkgbase/{{ pkgbase.Name }}/comaintainers">
|
||||
{{ "Manage Co-Maintainers" | tr }}
|
||||
|
@ -107,14 +107,14 @@
|
|||
{{ "Submit Request" | tr }}
|
||||
</a>
|
||||
</li>
|
||||
{% if request.user.has_credential("CRED_PKGBASE_DELETE") %}
|
||||
{% if request.user.has_credential(creds.PKGBASE_DELETE) %}
|
||||
<li>
|
||||
<a href="/pkgbase/{{ pkgbase.Name }}/delete">
|
||||
{{ "Delete Package" | tr }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.has_credential("CRED_PKGBASE_MERGE") %}
|
||||
{% if request.user.has_credential(creds.PKGBASE_MERGE) %}
|
||||
<li>
|
||||
<a href="/pkgbase/{{ pkgbase.Name }}/merge">
|
||||
{{ "Merge Package" | tr }}
|
||||
|
@ -130,7 +130,7 @@
|
|||
/>
|
||||
</form>
|
||||
</li>
|
||||
{% elif request.user.has_credential("CRED_PKGBASE_DISOWN", approved=[pkgbase.Maintainer]) %}
|
||||
{% elif request.user.has_credential(creds.PKGBASE_DISOWN, approved=[pkgbase.Maintainer]) %}
|
||||
<li>
|
||||
<a href="/pkgbase/{{ pkgbase.Name }}/disown">
|
||||
{{ "Disown Package" | tr }}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% set article_cls = "%s %s" | format(article_cls, "comment-deleted") %}
|
||||
{% 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 }}">
|
||||
{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}
|
||||
{% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %}
|
||||
|
|
|
@ -33,10 +33,10 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% 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>
|
||||
<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>
|
||||
<form method="post"
|
||||
action="/pkgbase/{{ pkgbase.Name }}/keywords"
|
||||
|
|
|
@ -5,7 +5,7 @@ import pytest
|
|||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
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.session import Session
|
||||
from aurweb.models.user import User
|
||||
|
@ -67,11 +67,6 @@ async def test_basic_auth_backend():
|
|||
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():
|
||||
""" This test merely asserts that a few different paths
|
||||
do not raise exceptions. """
|
||||
|
@ -109,8 +104,3 @@ def test_voted_for():
|
|||
def test_notified():
|
||||
user_ = AnonymousUser()
|
||||
assert not user_.notified(None)
|
||||
|
||||
|
||||
def test_has_credential():
|
||||
user_ = AnonymousUser()
|
||||
assert not user_.has_credential("FAKE_CREDENTIAL")
|
||||
|
|
|
@ -9,7 +9,7 @@ import pytest
|
|||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from aurweb import db, util
|
||||
from aurweb import config, db, util
|
||||
from aurweb.models.account_type import AccountType
|
||||
from aurweb.models.tu_vote import TUVote
|
||||
from aurweb.models.tu_voteinfo import TUVoteInfo
|
||||
|
@ -124,8 +124,9 @@ def proposal(user, tu_user):
|
|||
|
||||
|
||||
def test_tu_index_guest(client):
|
||||
headers = {"referer": config.get("options", "aur_location") + "/tu"}
|
||||
with client as request:
|
||||
response = request.get("/tu", allow_redirects=False)
|
||||
response = request.get("/tu", allow_redirects=False, headers=headers)
|
||||
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
||||
|
||||
params = util.urlencode({"next": "/tu"})
|
||||
|
|
|
@ -10,6 +10,7 @@ import aurweb.auth
|
|||
import aurweb.config
|
||||
|
||||
from aurweb import db
|
||||
from aurweb.auth import creds
|
||||
from aurweb.models.account_type import AccountType
|
||||
from aurweb.models.ban import Ban
|
||||
from aurweb.models.package import Package
|
||||
|
@ -154,7 +155,7 @@ def test_user_minimum_passwd_length():
|
|||
|
||||
|
||||
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():
|
||||
|
@ -169,10 +170,10 @@ def test_user_ssh_pub_key():
|
|||
|
||||
|
||||
def test_user_credential_types():
|
||||
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)
|
||||
assert user.AccountTypeID in creds.user_developer_or_trusted_user
|
||||
assert user.AccountTypeID not in creds.trusted_user
|
||||
assert user.AccountTypeID not in creds.developer
|
||||
assert user.AccountTypeID not in creds.trusted_user_or_dev
|
||||
|
||||
trusted_user_type = db.query(AccountType).filter(
|
||||
AccountType.AccountType == "Trusted User"
|
||||
|
@ -180,16 +181,16 @@ def test_user_credential_types():
|
|||
with db.begin():
|
||||
user.AccountType = trusted_user_type
|
||||
|
||||
assert aurweb.auth.trusted_user(user)
|
||||
assert aurweb.auth.trusted_user_or_dev(user)
|
||||
assert user.AccountTypeID in creds.trusted_user
|
||||
assert user.AccountTypeID in creds.trusted_user_or_dev
|
||||
|
||||
developer_type = db.query(AccountType,
|
||||
AccountType.AccountType == "Developer").first()
|
||||
with db.begin():
|
||||
user.AccountType = developer_type
|
||||
|
||||
assert aurweb.auth.developer(user)
|
||||
assert aurweb.auth.trusted_user_or_dev(user)
|
||||
assert user.AccountTypeID in creds.developer
|
||||
assert user.AccountTypeID in creds.trusted_user_or_dev
|
||||
|
||||
type_str = "Trusted User & Developer"
|
||||
elevated_type = db.query(AccountType,
|
||||
|
@ -197,9 +198,9 @@ def test_user_credential_types():
|
|||
with db.begin():
|
||||
user.AccountType = elevated_type
|
||||
|
||||
assert aurweb.auth.trusted_user(user)
|
||||
assert aurweb.auth.developer(user)
|
||||
assert aurweb.auth.trusted_user_or_dev(user)
|
||||
assert user.AccountTypeID in creds.trusted_user
|
||||
assert user.AccountTypeID in creds.developer
|
||||
assert user.AccountTypeID in creds.trusted_user_or_dev
|
||||
|
||||
# Some model authorization checks.
|
||||
assert user.is_elevated()
|
||||
|
|
Loading…
Add table
Reference in a new issue