mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
fix(routers.trusted_user): use creds to determine authorization
Closes #237 Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
9d221604b4
commit
059733cb8c
4 changed files with 71 additions and 34 deletions
|
@ -10,8 +10,7 @@ from fastapi.responses import RedirectResponse, Response
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_, or_
|
||||||
|
|
||||||
from aurweb import db, l10n, logging, models
|
from aurweb import db, l10n, logging, models
|
||||||
from aurweb.auth import account_type_required, requires_auth
|
from aurweb.auth import creds, requires_auth
|
||||||
from aurweb.models.account_type import DEVELOPER, TRUSTED_USER, TRUSTED_USER_AND_DEV
|
|
||||||
from aurweb.templates import make_context, make_variable_context, render_template
|
from aurweb.templates import make_context, make_variable_context, render_template
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
@ -21,13 +20,6 @@ logger = logging.get_logger(__name__)
|
||||||
ITEMS_PER_PAGE = 10 # Paged table size.
|
ITEMS_PER_PAGE = 10 # Paged table size.
|
||||||
MAX_AGENDA_LENGTH = 75 # Agenda table column length.
|
MAX_AGENDA_LENGTH = 75 # Agenda table column length.
|
||||||
|
|
||||||
# A set of account types that will approve a user for TU actions.
|
|
||||||
REQUIRED_TYPES = {
|
|
||||||
TRUSTED_USER,
|
|
||||||
DEVELOPER,
|
|
||||||
TRUSTED_USER_AND_DEV
|
|
||||||
}
|
|
||||||
|
|
||||||
ADDVOTE_SPECIFICS = {
|
ADDVOTE_SPECIFICS = {
|
||||||
# This dict stores a vote duration and quorum for a proposal.
|
# This dict stores a vote duration and quorum for a proposal.
|
||||||
# When a proposal is added, duration is added to the current
|
# When a proposal is added, duration is added to the current
|
||||||
|
@ -42,12 +34,14 @@ ADDVOTE_SPECIFICS = {
|
||||||
|
|
||||||
@router.get("/tu")
|
@router.get("/tu")
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@account_type_required(REQUIRED_TYPES)
|
|
||||||
async def trusted_user(request: Request,
|
async def trusted_user(request: Request,
|
||||||
coff: int = 0, # current offset
|
coff: int = 0, # current offset
|
||||||
cby: str = "desc", # current by
|
cby: str = "desc", # current by
|
||||||
poff: int = 0, # past offset
|
poff: int = 0, # past offset
|
||||||
pby: str = "desc"): # past by
|
pby: str = "desc"): # past by
|
||||||
|
if not request.user.has_credential(creds.TU_LIST_VOTES):
|
||||||
|
return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
context = make_context(request, "Trusted User")
|
context = make_context(request, "Trusted User")
|
||||||
|
|
||||||
current_by, past_by = cby, pby
|
current_by, past_by = cby, pby
|
||||||
|
@ -113,9 +107,7 @@ async def trusted_user(request: Request,
|
||||||
return render_template(request, "tu/index.html", context)
|
return render_template(request, "tu/index.html", context)
|
||||||
|
|
||||||
|
|
||||||
def render_proposal(request: Request,
|
def render_proposal(request: Request, context: dict, proposal: int,
|
||||||
context: dict,
|
|
||||||
proposal: int,
|
|
||||||
voteinfo: models.TUVoteInfo,
|
voteinfo: models.TUVoteInfo,
|
||||||
voters: typing.Iterable[models.User],
|
voters: typing.Iterable[models.User],
|
||||||
vote: models.TUVote,
|
vote: models.TUVote,
|
||||||
|
@ -148,8 +140,10 @@ def render_proposal(request: Request,
|
||||||
|
|
||||||
@router.get("/tu/{proposal}")
|
@router.get("/tu/{proposal}")
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@account_type_required(REQUIRED_TYPES)
|
|
||||||
async def trusted_user_proposal(request: Request, proposal: int):
|
async def trusted_user_proposal(request: Request, proposal: int):
|
||||||
|
if not request.user.has_credential(creds.TU_LIST_VOTES):
|
||||||
|
return RedirectResponse("/tu", status_code=HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
context = await make_variable_context(request, "Trusted User")
|
context = await make_variable_context(request, "Trusted User")
|
||||||
proposal = int(proposal)
|
proposal = int(proposal)
|
||||||
|
|
||||||
|
@ -163,10 +157,9 @@ async def trusted_user_proposal(request: Request, proposal: int):
|
||||||
vote = db.query(models.TUVote).filter(
|
vote = db.query(models.TUVote).filter(
|
||||||
and_(models.TUVote.UserID == request.user.ID,
|
and_(models.TUVote.UserID == request.user.ID,
|
||||||
models.TUVote.VoteID == voteinfo.ID)).first()
|
models.TUVote.VoteID == voteinfo.ID)).first()
|
||||||
|
if not request.user.has_credential(creds.TU_VOTE):
|
||||||
if not request.user.is_trusted_user():
|
|
||||||
context["error"] = "Only Trusted Users are allowed to vote."
|
context["error"] = "Only Trusted Users are allowed to vote."
|
||||||
elif voteinfo.User == request.user.Username:
|
if voteinfo.User == request.user.Username:
|
||||||
context["error"] = "You cannot vote in an proposal about you."
|
context["error"] = "You cannot vote in an proposal about you."
|
||||||
elif vote is not None:
|
elif vote is not None:
|
||||||
context["error"] = "You've already voted for this proposal."
|
context["error"] = "You've already voted for this proposal."
|
||||||
|
@ -177,10 +170,11 @@ async def trusted_user_proposal(request: Request, proposal: int):
|
||||||
|
|
||||||
@router.post("/tu/{proposal}")
|
@router.post("/tu/{proposal}")
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@account_type_required(REQUIRED_TYPES)
|
async def trusted_user_proposal_post(request: Request, proposal: int,
|
||||||
async def trusted_user_proposal_post(request: Request,
|
|
||||||
proposal: int,
|
|
||||||
decision: str = Form(...)):
|
decision: str = Form(...)):
|
||||||
|
if not request.user.has_credential(creds.TU_LIST_VOTES):
|
||||||
|
return RedirectResponse("/tu", status_code=HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
context = await make_variable_context(request, "Trusted User")
|
context = await make_variable_context(request, "Trusted User")
|
||||||
proposal = int(proposal) # Make sure it's an int.
|
proposal = int(proposal) # Make sure it's an int.
|
||||||
|
|
||||||
|
@ -196,7 +190,7 @@ async def trusted_user_proposal_post(request: Request,
|
||||||
models.TUVote.VoteID == voteinfo.ID)).first()
|
models.TUVote.VoteID == voteinfo.ID)).first()
|
||||||
|
|
||||||
status_code = HTTPStatus.OK
|
status_code = HTTPStatus.OK
|
||||||
if not request.user.is_trusted_user():
|
if not request.user.has_credential(creds.TU_VOTE):
|
||||||
context["error"] = "Only Trusted Users are allowed to vote."
|
context["error"] = "Only Trusted Users are allowed to vote."
|
||||||
status_code = HTTPStatus.UNAUTHORIZED
|
status_code = HTTPStatus.UNAUTHORIZED
|
||||||
elif voteinfo.User == request.user.Username:
|
elif voteinfo.User == request.user.Username:
|
||||||
|
@ -228,11 +222,11 @@ async def trusted_user_proposal_post(request: Request,
|
||||||
|
|
||||||
@router.get("/addvote")
|
@router.get("/addvote")
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@account_type_required({TRUSTED_USER, TRUSTED_USER_AND_DEV})
|
async def trusted_user_addvote(request: Request, user: str = str(),
|
||||||
async def trusted_user_addvote(request: Request,
|
type: str = "add_tu", agenda: str = str()):
|
||||||
user: str = str(),
|
if not request.user.has_credential(creds.TU_ADD_VOTE):
|
||||||
type: str = "add_tu",
|
return RedirectResponse("/tu", status_code=HTTPStatus.SEE_OTHER)
|
||||||
agenda: str = str()):
|
|
||||||
context = await make_variable_context(request, "Add Proposal")
|
context = await make_variable_context(request, "Add Proposal")
|
||||||
|
|
||||||
if type not in ADDVOTE_SPECIFICS:
|
if type not in ADDVOTE_SPECIFICS:
|
||||||
|
@ -248,11 +242,13 @@ async def trusted_user_addvote(request: Request,
|
||||||
|
|
||||||
@router.post("/addvote")
|
@router.post("/addvote")
|
||||||
@requires_auth
|
@requires_auth
|
||||||
@account_type_required({TRUSTED_USER, TRUSTED_USER_AND_DEV})
|
|
||||||
async def trusted_user_addvote_post(request: Request,
|
async def trusted_user_addvote_post(request: Request,
|
||||||
user: str = Form(default=str()),
|
user: str = Form(default=str()),
|
||||||
type: str = Form(default=str()),
|
type: str = Form(default=str()),
|
||||||
agenda: str = Form(default=str())):
|
agenda: str = Form(default=str())):
|
||||||
|
if not request.user.has_credential(creds.TU_ADD_VOTE):
|
||||||
|
return RedirectResponse("/tu", status_code=HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
# Build a context.
|
# Build a context.
|
||||||
context = await make_variable_context(request, "Add Proposal")
|
context = await make_variable_context(request, "Add Proposal")
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h2>{% trans %}{{ title }}{% endtrans %}</h2>
|
<h2>{% trans %}{{ title }}{% endtrans %}</h2>
|
||||||
|
|
||||||
{% if title == "Current Votes" %}
|
{% if title == "Current Votes" and request.user.has_credential(creds.TU_ADD_VOTE) %}
|
||||||
<ul class="admin-actions">
|
<ul class="admin-actions">
|
||||||
<li>
|
<li>
|
||||||
<a href="/addvote">
|
<a href="/addvote">
|
||||||
|
|
|
@ -1857,3 +1857,11 @@ def test_account_comments_not_found(client: TestClient, user: User):
|
||||||
with client as request:
|
with client as request:
|
||||||
resp = request.get("/account/non-existent/comments", cookies=cookies)
|
resp = request.get("/account/non-existent/comments", cookies=cookies)
|
||||||
assert resp.status_code == int(HTTPStatus.NOT_FOUND)
|
assert resp.status_code == int(HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
def test_accounts_unauthorized(client: TestClient, user: User):
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.get("/accounts", cookies=cookies, allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
assert resp.headers.get("location") == "/"
|
||||||
|
|
|
@ -11,7 +11,7 @@ import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from aurweb import config, db, util
|
from aurweb import config, db, util
|
||||||
from aurweb.models.account_type import AccountType
|
from aurweb.models.account_type import DEVELOPER_ID, AccountType
|
||||||
from aurweb.models.tu_vote import TUVote
|
from aurweb.models.tu_vote import TUVote
|
||||||
from aurweb.models.tu_voteinfo import TUVoteInfo
|
from aurweb.models.tu_voteinfo import TUVoteInfo
|
||||||
from aurweb.models.user import User
|
from aurweb.models.user import User
|
||||||
|
@ -134,7 +134,7 @@ def test_tu_index_guest(client):
|
||||||
assert response.headers.get("location") == f"/login?{params}"
|
assert response.headers.get("location") == f"/login?{params}"
|
||||||
|
|
||||||
|
|
||||||
def test_tu_index_unauthorized(client, user):
|
def test_tu_index_unauthorized(client: TestClient, user: User):
|
||||||
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
with client as request:
|
with client as request:
|
||||||
# Login as a normal user, not a TU.
|
# Login as a normal user, not a TU.
|
||||||
|
@ -478,6 +478,24 @@ def test_tu_proposal_not_found(client, tu_user):
|
||||||
assert response.status_code == int(HTTPStatus.NOT_FOUND)
|
assert response.status_code == int(HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tu_proposal_unauthorized(client: TestClient, user: User,
|
||||||
|
proposal: Tuple[User, User, TUVoteInfo]):
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
endpoint = f"/tu/{proposal[2].ID}"
|
||||||
|
with client as request:
|
||||||
|
response = request.get(endpoint, cookies=cookies,
|
||||||
|
allow_redirects=False)
|
||||||
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
assert response.headers.get("location") == "/tu"
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post(endpoint, cookies=cookies,
|
||||||
|
data={"decision": False},
|
||||||
|
allow_redirects=False)
|
||||||
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
assert response.headers.get("location") == "/tu"
|
||||||
|
|
||||||
|
|
||||||
def test_tu_running_proposal(client: TestClient,
|
def test_tu_running_proposal(client: TestClient,
|
||||||
proposal: Tuple[User, User, TUVoteInfo]):
|
proposal: Tuple[User, User, TUVoteInfo]):
|
||||||
tu_user, user, voteinfo = proposal
|
tu_user, user, voteinfo = proposal
|
||||||
|
@ -641,13 +659,12 @@ def test_tu_proposal_vote(client, proposal):
|
||||||
assert status == "You've already voted for this proposal."
|
assert status == "You've already voted for this proposal."
|
||||||
|
|
||||||
|
|
||||||
def test_tu_proposal_vote_unauthorized(client, proposal):
|
def test_tu_proposal_vote_unauthorized(
|
||||||
|
client: TestClient, proposal: Tuple[User, User, TUVoteInfo]):
|
||||||
tu_user, user, voteinfo = proposal
|
tu_user, user, voteinfo = proposal
|
||||||
|
|
||||||
dev_type = db.query(AccountType,
|
|
||||||
AccountType.AccountType == "Developer").first()
|
|
||||||
with db.begin():
|
with db.begin():
|
||||||
tu_user.AccountType = dev_type
|
tu_user.AccountTypeID = DEVELOPER_ID
|
||||||
|
|
||||||
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
||||||
with client as request:
|
with client as request:
|
||||||
|
@ -749,6 +766,22 @@ def test_tu_addvote(client: TestClient, tu_user: User):
|
||||||
assert response.status_code == int(HTTPStatus.OK)
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tu_addvote_unauthorized(client: TestClient, user: User,
|
||||||
|
proposal: Tuple[User, User, TUVoteInfo]):
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
response = request.get("/addvote", cookies=cookies,
|
||||||
|
allow_redirects=False)
|
||||||
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
assert response.headers.get("location") == "/tu"
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/addvote", cookies=cookies,
|
||||||
|
allow_redirects=False)
|
||||||
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
assert response.headers.get("location") == "/tu"
|
||||||
|
|
||||||
|
|
||||||
def test_tu_addvote_invalid_type(client: TestClient, tu_user: User):
|
def test_tu_addvote_invalid_type(client: TestClient, tu_user: User):
|
||||||
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
||||||
with client as request:
|
with client as request:
|
||||||
|
|
Loading…
Add table
Reference in a new issue