diff --git a/aurweb/config.py b/aurweb/config.py index 6284f794..61b60402 100644 --- a/aurweb/config.py +++ b/aurweb/config.py @@ -6,7 +6,7 @@ from typing import Any # Publicly visible version of aurweb. This is used to display # aurweb versioning in the footer and must be maintained. # Todo: Make this dynamic/automated. -AURWEB_VERSION = "v6.0.15" +AURWEB_VERSION = "v6.0.17" _parser = None diff --git a/aurweb/models/user.py b/aurweb/models/user.py index 871ff209..c375fcbc 100644 --- a/aurweb/models/user.py +++ b/aurweb/models/user.py @@ -135,6 +135,10 @@ class User(Base): if last_updated and last_updated < now_ts: self.session.SessionID = generate_unique_sid() self.session.LastUpdateTS = now_ts + + # Unset InactivityTS, we've logged in! + self.InactivityTS = 0 + break except IntegrityError as exc_: exc = exc_ diff --git a/aurweb/packages/search.py b/aurweb/packages/search.py index b0e1e891..4a6eb75f 100644 --- a/aurweb/packages/search.py +++ b/aurweb/packages/search.py @@ -124,6 +124,7 @@ class PackageSearch: def _search_by_keywords(self, keywords: Set[str]) -> orm.Query: self._join_user() self._join_keywords() + keywords = set(k.lower() for k in keywords) self.query = self.query.filter(PackageKeyword.Keyword.in_(keywords)) return self diff --git a/aurweb/packages/util.py b/aurweb/packages/util.py index 0a259e1e..e8569f29 100644 --- a/aurweb/packages/util.py +++ b/aurweb/packages/util.py @@ -138,20 +138,20 @@ def updated_packages(limit: int = 0, # If we already have a cache, deserialize it and return. return orjson.loads(packages) - query = db.query(models.Package).join(models.PackageBase).filter( - models.PackageBase.PackagerUID.isnot(None) - ).order_by( - models.PackageBase.ModifiedTS.desc() - ) + with db.begin(): + query = db.query(models.Package).join(models.PackageBase).filter( + models.PackageBase.PackagerUID.isnot(None) + ).order_by( + models.PackageBase.ModifiedTS.desc() + ) - if limit: - query = query.limit(limit) + if limit: + query = query.limit(limit) packages = [] for pkg in query: # For each Package returned by the query, append a dict # containing Package columns we're interested in. - db.refresh(pkg) packages.append({ "Name": pkg.Name, "Version": pkg.Version, diff --git a/aurweb/routers/auth.py b/aurweb/routers/auth.py index fc5209ce..9f465388 100644 --- a/aurweb/routers/auth.py +++ b/aurweb/routers/auth.py @@ -46,13 +46,19 @@ async def login_post(request: Request, raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=_("Bad Referer header.")) - user = db.query(User).filter( - or_(User.Username == user, User.Email == user) - ).first() + with db.begin(): + user = db.query(User).filter( + or_(User.Username == user, User.Email == user) + ).first() + if not user: return await login_template(request, next, errors=["Bad username or password."]) + if user.Suspended: + return await login_template(request, next, + errors=["Account Suspended"]) + cookie_timeout = cookies.timeout(remember_me) sid = user.login(request, passwd, cookie_timeout) if not sid: diff --git a/aurweb/routers/pkgbase.py b/aurweb/routers/pkgbase.py index 4c0b8a67..2cef5436 100644 --- a/aurweb/routers/pkgbase.py +++ b/aurweb/routers/pkgbase.py @@ -95,24 +95,28 @@ async def pkgbase_flag_comment(request: Request, name: str): async def pkgbase_keywords(request: Request, name: str, keywords: str = Form(default=str())): pkgbase = get_pkg_or_base(name, PackageBase) - keywords = set(keywords.split(" ")) + + # Lowercase all keywords. Our database table is case insensitive, + # and providing CI duplicates of keywords is erroneous. + keywords = set(k.lower() for k in keywords.split(" ")) # Delete all keywords which are not supplied by the user. - other_keywords = pkgbase.keywords.filter( - ~PackageKeyword.Keyword.in_(keywords)) - other_keyword_strings = [kwd.Keyword for kwd in other_keywords] - - existing_keywords = set( - kwd.Keyword for kwd in - pkgbase.keywords.filter( - ~PackageKeyword.Keyword.in_(other_keyword_strings)) - ) with db.begin(): + other_keywords = pkgbase.keywords.filter( + ~PackageKeyword.Keyword.in_(keywords)) + other_keyword_strings = set( + kwd.Keyword.lower() for kwd in other_keywords) + + existing_keywords = set( + kwd.Keyword.lower() for kwd in + pkgbase.keywords.filter( + ~PackageKeyword.Keyword.in_(other_keyword_strings)) + ) + db.delete_all(other_keywords) - for keyword in keywords.difference(existing_keywords): - db.create(PackageKeyword, - PackageBase=pkgbase, - Keyword=keyword) + new_keywords = keywords.difference(existing_keywords) + for keyword in new_keywords: + db.create(PackageKeyword, PackageBase=pkgbase, Keyword=keyword) return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) diff --git a/aurweb/schema.py b/aurweb/schema.py index 39550ff6..d2644541 100644 --- a/aurweb/schema.py +++ b/aurweb/schema.py @@ -269,7 +269,7 @@ PackageVotes = Table( 'PackageVotes', metadata, Column('UsersID', ForeignKey('Users.ID', ondelete='CASCADE'), nullable=False), Column('PackageBaseID', ForeignKey('PackageBases.ID', ondelete='CASCADE'), nullable=False), - Column('VoteTS', BIGINT(unsigned=True)), + Column('VoteTS', BIGINT(unsigned=True), nullable=False), Index('VoteUsersIDPackageID', 'UsersID', 'PackageBaseID', unique=True), Index('VotesPackageBaseID', 'PackageBaseID'), Index('VotesUsersID', 'UsersID'), diff --git a/migrations/versions/d64e5571bc8d_fix_pkgvote_votets.py b/migrations/versions/d64e5571bc8d_fix_pkgvote_votets.py new file mode 100644 index 00000000..a89d97ef --- /dev/null +++ b/migrations/versions/d64e5571bc8d_fix_pkgvote_votets.py @@ -0,0 +1,37 @@ +"""fix pkgvote votets + +Revision ID: d64e5571bc8d +Revises: be7adae47ac3 +Create Date: 2022-02-18 12:47:05.322766 + +""" +from datetime import datetime + +import sqlalchemy as sa + +from alembic import op + +from aurweb import db +from aurweb.models import PackageVote + +# revision identifiers, used by Alembic. +revision = 'd64e5571bc8d' +down_revision = 'be7adae47ac3' +branch_labels = None +depends_on = None + +table = PackageVote.__tablename__ +column = 'VoteTS' +epoch = datetime(1970, 1, 1) + + +def upgrade(): + with db.begin(): + records = db.query(PackageVote).filter(PackageVote.VoteTS.is_(None)) + for record in records: + record.VoteTS = epoch.timestamp() + op.alter_column(table, column, existing_type=sa.BIGINT(), nullable=False) + + +def downgrade(): + op.alter_column(table, column, existing_type=sa.BIGINT(), nullable=True) diff --git a/pyproject.toml b/pyproject.toml index cc6e9d2e..89b149a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ # [tool.poetry] name = "aurweb" -version = "v6.0.15" +version = "v6.0.17" license = "GPL-2.0-only" description = "Source code for the Arch User Repository's website" homepage = "https://aur.archlinux.org" diff --git a/schema/gendummydata.py b/schema/gendummydata.py index 275b3601..aedfda7e 100755 --- a/schema/gendummydata.py +++ b/schema/gendummydata.py @@ -16,6 +16,8 @@ import random import sys import time +from datetime import datetime + import bcrypt LOG_LEVEL = logging.DEBUG # logging level. set to logging.INFO to reduce output @@ -203,7 +205,7 @@ for u in user_keys: log.debug("Number of developers: %d" % len(developers)) log.debug("Number of trusted users: %d" % len(trustedusers)) -log.debug("Number of users: %d" % (MAX_USERS-len(developers)-len(trustedusers))) +log.debug("Number of users: %d" % (MAX_USERS - len(developers) - len(trustedusers))) log.debug("Number of packages: %d" % MAX_PKGS) log.debug("Gathering text from fortune file...") @@ -244,26 +246,27 @@ for p in list(seen_pkgs.keys()): # num_comments = random.randrange(PKG_CMNTS[0], PKG_CMNTS[1]) for i in range(0, num_comments): - now = NOW + random.randrange(400, 86400*3) + now = NOW + random.randrange(400, 86400 * 3) s = ("INSERT INTO PackageComments (PackageBaseID, UsersID," " Comments, RenderedComment, CommentTS) VALUES (%d, %d, '%s', '', %d);\n") s = s % (seen_pkgs[p], genUID(), genFortune(), now) out.write(s) # Cast votes -# +utcnow = int(datetime.utcnow().timestamp()) + track_votes = {} log.debug("Casting votes for packages.") for u in user_keys: - num_votes = random.randrange(int(len(seen_pkgs)*VOTING[0]), - int(len(seen_pkgs)*VOTING[1])) + num_votes = random.randrange(int(len(seen_pkgs) * VOTING[0]), + int(len(seen_pkgs) * VOTING[1])) pkgvote = {} for v in range(num_votes): pkg = random.randrange(1, len(seen_pkgs) + 1) if pkg not in pkgvote: - s = ("INSERT INTO PackageVotes (UsersID, PackageBaseID)" - " VALUES (%d, %d);\n") - s = s % (seen_users[u], pkg) + s = ("INSERT INTO PackageVotes (UsersID, PackageBaseID, VoteTS)" + " VALUES (%d, %d, %d);\n") + s = s % (seen_users[u], pkg, utcnow) pkgvote[pkg] = 1 if pkg not in track_votes: track_votes[pkg] = 0 @@ -318,14 +321,14 @@ for p in seen_pkgs_keys: # log.debug("Creating SQL statements for trusted user proposals.") count = 0 -for t in range(0, OPEN_PROPOSALS+CLOSE_PROPOSALS): +for t in range(0, OPEN_PROPOSALS + CLOSE_PROPOSALS): now = int(time.time()) if count < CLOSE_PROPOSALS: - start = now - random.randrange(3600*24*7, 3600*24*21) - end = now - random.randrange(0, 3600*24*7) + start = now - random.randrange(3600 * 24 * 7, 3600 * 24 * 21) + end = now - random.randrange(0, 3600 * 24 * 7) else: start = now - end = now + random.randrange(3600*24, 3600*24*7) + end = now + random.randrange(3600 * 24, 3600 * 24 * 7) if count % 5 == 0: # Don't make the vote about anyone once in a while user = "" else: diff --git a/test/test_auth_routes.py b/test/test_auth_routes.py index 79b34b6b..8467adea 100644 --- a/test/test_auth_routes.py +++ b/test/test_auth_routes.py @@ -14,6 +14,7 @@ from aurweb.asgi import app from aurweb.models.account_type import USER_ID from aurweb.models.session import Session from aurweb.models.user import User +from aurweb.testing.html import get_errors # Some test global constants. TEST_USERNAME = "test" @@ -79,6 +80,21 @@ def test_login_logout(client: TestClient, user: User): assert "AURSID" not in response.cookies +def test_login_suspended(client: TestClient, user: User): + with db.begin(): + user.Suspended = 1 + + data = { + "user": user.Username, + "passwd": "testPassword", + "next": "/" + } + with client as request: + resp = request.post("/login", data=data) + errors = get_errors(resp.text) + assert errors[0].text.strip() == "Account Suspended" + + def test_login_email(client: TestClient, user: user): post_data = { "user": user.Email,