mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
Previously, we were just relying on the cookie expiration for sessions to expire. We were not cleaning up Session records either. Rework timing to depend on an AURREMEMBER cookie which is now emitted on login during BasicAuthBackend processing. If the SID does still have a session but it's expired, we now delete the session record before returning. Otherwise, we update the session's LastUpdateTS to the current time. In addition, stored the unauthenticated result value in a variable to reduce redundancy. Signed-off-by: Kevin Morris <kevr@0cost.org>
155 lines
4.7 KiB
Python
155 lines
4.7 KiB
Python
from datetime import datetime
|
|
|
|
import fastapi
|
|
import pytest
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
from aurweb import config, db
|
|
from aurweb.auth import AnonymousUser, BasicAuthBackend, account_type_required, auth_required
|
|
from aurweb.models.account_type import USER, USER_ID
|
|
from aurweb.models.session import Session
|
|
from aurweb.models.user import User
|
|
from aurweb.testing.requests import Request
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(db_test):
|
|
return
|
|
|
|
|
|
@pytest.fixture
|
|
def user() -> User:
|
|
with db.begin():
|
|
user = db.create(User, Username="test", Email="test@example.com",
|
|
RealName="Test User", Passwd="testPassword",
|
|
AccountTypeID=USER_ID)
|
|
yield user
|
|
|
|
|
|
@pytest.fixture
|
|
def backend() -> BasicAuthBackend:
|
|
yield BasicAuthBackend()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_backend_missing_sid(backend: BasicAuthBackend):
|
|
# The request has no AURSID cookie, so authentication fails, and
|
|
# AnonymousUser is returned.
|
|
_, result = await backend.authenticate(Request())
|
|
assert not result.is_authenticated()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_backend_invalid_sid(backend: BasicAuthBackend):
|
|
# Provide a fake AURSID that won't be found in the database.
|
|
# This results in our path going down the invalid sid route,
|
|
# which gives us an AnonymousUser.
|
|
request = Request()
|
|
request.cookies["AURSID"] = "fake"
|
|
_, result = await backend.authenticate(request)
|
|
assert not result.is_authenticated()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_backend_invalid_user_id():
|
|
# Create a new session with a fake user id.
|
|
now_ts = datetime.utcnow().timestamp()
|
|
with pytest.raises(IntegrityError):
|
|
Session(UsersID=666, SessionID="realSession",
|
|
LastUpdateTS=now_ts + 5)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_basic_auth_backend(user: User, backend: BasicAuthBackend):
|
|
# This time, everything matches up. We expect the user to
|
|
# equal the real_user.
|
|
now_ts = datetime.utcnow().timestamp()
|
|
with db.begin():
|
|
db.create(Session, UsersID=user.ID, SessionID="realSession",
|
|
LastUpdateTS=now_ts + 5)
|
|
|
|
request = Request()
|
|
request.cookies["AURSID"] = "realSession"
|
|
_, result = await backend.authenticate(request)
|
|
assert result == user
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_expired_session(backend: BasicAuthBackend, user: User):
|
|
""" Login, expire the session manually, then authenticate. """
|
|
# First, build a Request with a logged in user.
|
|
request = Request()
|
|
request.user = user
|
|
sid = request.user.login(Request(), "testPassword")
|
|
request.cookies["AURSID"] = sid
|
|
|
|
# Set Session.LastUpdateTS to 20 seconds expired.
|
|
timeout = config.getint("options", "login_timeout")
|
|
now_ts = int(datetime.utcnow().timestamp())
|
|
with db.begin():
|
|
request.user.session.LastUpdateTS = now_ts - timeout - 20
|
|
|
|
# Run through authentication backend and get the session
|
|
# deleted due to its expiration.
|
|
await backend.authenticate(request)
|
|
session = db.query(Session).filter(Session.SessionID == sid).first()
|
|
assert session is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_required_redirection_bad_referrer():
|
|
# Create a fake route function which can be wrapped by auth_required.
|
|
def bad_referrer_route(request: fastapi.Request):
|
|
pass
|
|
|
|
# Get down to the nitty gritty internal wrapper.
|
|
bad_referrer_route = auth_required()(bad_referrer_route)
|
|
|
|
# Execute the route with a "./blahblahblah" Referer, which does not
|
|
# match aur_location; `./` has been used as a prefix to attempt to
|
|
# ensure we're providing a fake referer.
|
|
with pytest.raises(HTTPException) as exc:
|
|
request = Request(method="POST", headers={"Referer": "./blahblahblah"})
|
|
await bad_referrer_route(request)
|
|
assert exc.detail == "Bad Referer header."
|
|
|
|
|
|
def test_account_type_required():
|
|
""" This test merely asserts that a few different paths
|
|
do not raise exceptions. """
|
|
# This one shouldn't raise.
|
|
account_type_required({USER})
|
|
|
|
# This one also shouldn't raise.
|
|
account_type_required({USER_ID})
|
|
|
|
# But this one should! We have no "FAKE" key.
|
|
with pytest.raises(KeyError):
|
|
account_type_required({'FAKE'})
|
|
|
|
|
|
def test_is_trusted_user():
|
|
user_ = AnonymousUser()
|
|
assert not user_.is_trusted_user()
|
|
|
|
|
|
def test_is_developer():
|
|
user_ = AnonymousUser()
|
|
assert not user_.is_developer()
|
|
|
|
|
|
def test_is_elevated():
|
|
user_ = AnonymousUser()
|
|
assert not user_.is_elevated()
|
|
|
|
|
|
def test_voted_for():
|
|
user_ = AnonymousUser()
|
|
assert not user_.voted_for(None)
|
|
|
|
|
|
def test_notified():
|
|
user_ = AnonymousUser()
|
|
assert not user_.notified(None)
|