Limit visibility of unapproved packages to maintainers and approvers

Fixes #338
This commit is contained in:
rubenwardy 2022-01-21 21:48:15 +00:00
parent 80d534a53f
commit 727db52c19
6 changed files with 33 additions and 7 deletions

@ -115,6 +115,9 @@ def getReleases(package):
@bp.route("/packages/<author>/<name>/") @bp.route("/packages/<author>/<name>/")
@is_package_page @is_package_page
def view(package): def view(package):
if not package.checkPerm(current_user, Permission.SEE_PACKAGE):
abort(404)
show_similar = not package.approved and ( show_similar = not package.approved and (
current_user in package.maintainers or current_user in package.maintainers or
package.checkPerm(current_user, Permission.APPROVE_NEW)) package.checkPerm(current_user, Permission.APPROVE_NEW))
@ -205,6 +208,9 @@ def shield(package, type):
@bp.route("/packages/<author>/<name>/download/") @bp.route("/packages/<author>/<name>/download/")
@is_package_page @is_package_page
def download(package): def download(package):
if not package.checkPerm(current_user, Permission.SEE_PACKAGE):
abort(404)
release = package.getDownloadRelease() release = package.getDownloadRelease()
if release is None: if release is None:
@ -585,6 +591,9 @@ def alias_create_edit(package: Package, alias_id: int = None):
@login_required @login_required
@is_package_page @is_package_page
def share(package): def share(package):
if not package.checkPerm(current_user, Permission.SEE_PACKAGE):
abort(404)
return render_template("packages/share.html", package=package, return render_template("packages/share.html", package=package,
tabs=get_package_tabs(current_user, package), current_tab="share") tabs=get_package_tabs(current_user, package), current_tab="share")
@ -592,6 +601,9 @@ def share(package):
@bp.route("/packages/<author>/<name>/similar/") @bp.route("/packages/<author>/<name>/similar/")
@is_package_page @is_package_page
def similar(package): def similar(package):
if not package.checkPerm(current_user, Permission.SEE_PACKAGE):
abort(404)
packages_modnames = {} packages_modnames = {}
for metapackage in package.provides: for metapackage in package.provides:
packages_modnames[metapackage] = Package.query.filter(Package.id != package.id, packages_modnames[metapackage] = Package.query.filter(Package.id != package.id,

@ -33,6 +33,9 @@ from . import bp, get_package_tabs
@bp.route("/packages/<author>/<name>/releases/", methods=["GET", "POST"]) @bp.route("/packages/<author>/<name>/releases/", methods=["GET", "POST"])
@is_package_page @is_package_page
def list_releases(package): def list_releases(package):
if not package.checkPerm(current_user, Permission.SEE_PACKAGE):
abort(404)
return render_template("packages/releases_list.html", return render_template("packages/releases_list.html",
package=package, package=package,
tabs=get_package_tabs(current_user, package), current_tab="releases") tabs=get_package_tabs(current_user, package), current_tab="releases")
@ -107,6 +110,9 @@ def create_release(package):
@bp.route("/packages/<author>/<name>/releases/<id>/download/") @bp.route("/packages/<author>/<name>/releases/<id>/download/")
@is_package_page @is_package_page
def download_release(package, id): def download_release(package, id):
if not package.checkPerm(current_user, Permission.SEE_PACKAGE):
abort(404)
release = PackageRelease.query.get(id) release = PackageRelease.query.get(id)
if release is None or release.package != package: if release is None or release.package != package:
abort(404) abort(404)

@ -24,7 +24,8 @@ from flask_login import current_user, login_required
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import * from wtforms import *
from wtforms.validators import * from wtforms.validators import *
from app.models import db, PackageReview, Thread, ThreadReply, NotificationType, PackageReviewVote, Package, UserRank from app.models import db, PackageReview, Thread, ThreadReply, NotificationType, PackageReviewVote, Package, UserRank, \
Permission
from app.utils import is_package_page, addNotification, get_int_or_abort, isYes, is_safe_url, rank_required from app.utils import is_package_page, addNotification, get_int_or_abort, isYes, is_safe_url, rank_required
from app.tasks.webhooktasks import post_discord_webhook from app.tasks.webhooktasks import post_discord_webhook
@ -53,6 +54,9 @@ def review(package):
flash(gettext("You can't review your own package!"), "danger") flash(gettext("You can't review your own package!"), "danger")
return redirect(package.getURL("packages.view")) return redirect(package.getURL("packages.view"))
if not package.checkPerm(current_user, Permission.SEE_PACKAGE):
abort(404)
review = PackageReview.query.filter_by(package=package, author=current_user).first() review = PackageReview.query.filter_by(package=package, author=current_user).first()
form = ReviewForm(formdata=request.form, obj=review) form = ReviewForm(formdata=request.form, obj=review)

@ -607,7 +607,10 @@ class Package(db.Model):
isMaintainer = isOwner or user.rank.atLeast(UserRank.EDITOR) or user in self.maintainers isMaintainer = isOwner or user.rank.atLeast(UserRank.EDITOR) or user in self.maintainers
isApprover = user.rank.atLeast(UserRank.APPROVER) isApprover = user.rank.atLeast(UserRank.APPROVER)
if perm == Permission.CREATE_THREAD: if perm == Permission.SEE_PACKAGE:
return self.state == PackageState.APPROVED or isMaintainer or isApprover
elif perm == Permission.CREATE_THREAD:
return user.rank.atLeast(UserRank.MEMBER) return user.rank.atLeast(UserRank.MEMBER)
# Members can edit their own packages, and editors can edit any packages # Members can edit their own packages, and editors can edit any packages

@ -59,6 +59,7 @@ class UserRank(enum.Enum):
class Permission(enum.Enum): class Permission(enum.Enum):
SEE_PACKAGE = "SEE_PACKAGE"
EDIT_PACKAGE = "EDIT_PACKAGE" EDIT_PACKAGE = "EDIT_PACKAGE"
DELETE_PACKAGE = "DELETE_PACKAGE" DELETE_PACKAGE = "DELETE_PACKAGE"
CHANGE_AUTHOR = "CHANGE_AUTHOR" CHANGE_AUTHOR = "CHANGE_AUTHOR"

@ -18,7 +18,8 @@
from functools import wraps from functools import wraps
from flask import abort, redirect, url_for, request from flask import abort, redirect, url_for, request
from flask_login import current_user from flask_login import current_user
from app.models import User, NotificationType, Package, UserRank, Notification, db, AuditSeverity, AuditLogEntry, ThreadReply, Thread, PackageState, PackageType, PackageAlias from app.models import User, NotificationType, Package, UserRank, Notification, db, AuditSeverity, AuditLogEntry, \
ThreadReply, Thread, PackageState, PackageType, PackageAlias
def getPackageByInfo(author, name): def getPackageByInfo(author, name):
@ -39,14 +40,15 @@ def is_package_page(f):
if not ("author" in kwargs and "name" in kwargs): if not ("author" in kwargs and "name" in kwargs):
abort(400) abort(400)
author = kwargs["author"] author = kwargs.pop("author")
name = kwargs["name"] name = kwargs.pop("name")
package = getPackageByInfo(author, name) package = getPackageByInfo(author, name)
if package is None: if package is None:
package = getPackageByInfo(author, name + "_game") package = getPackageByInfo(author, name + "_game")
if package and package.type == PackageType.GAME: if package and package.type == PackageType.GAME:
args = dict(kwargs) args = dict(kwargs)
args["author"] = author
args["name"] = name + "_game" args["name"] = name + "_game"
return redirect(url_for(request.endpoint, **args)) return redirect(url_for(request.endpoint, **args))
@ -59,8 +61,6 @@ def is_package_page(f):
abort(404) abort(404)
del kwargs["author"]
del kwargs["name"]
return f(package=package, *args, **kwargs) return f(package=package, *args, **kwargs)
return decorated_function return decorated_function