From 8afe17b984cae3d1e934f92024bbc683d4668c48 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Mon, 28 Jan 2019 19:01:37 +0000 Subject: [PATCH] Add comment ratelimiting, allow any member to open threads --- app/models.py | 32 ++++++++++++++++---------- app/templates/macros/threads.html | 17 ++++++++++---- app/views/threads.py | 38 ++++++++++++++++++++++++------- docker-compose.yml | 2 +- runprodguni.sh | 2 +- 5 files changed, 64 insertions(+), 27 deletions(-) diff --git a/app/models.py b/app/models.py index b1cfbb5..20c3e60 100644 --- a/app/models.py +++ b/app/models.py @@ -20,10 +20,9 @@ from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from urllib.parse import urlparse from app import app, gravatar -from datetime import datetime from sqlalchemy.orm import validates from flask_user import login_required, UserManager, UserMixin, SQLAlchemyAdapter -import enum +import enum, datetime # Initialise database db = SQLAlchemy(app) @@ -129,8 +128,6 @@ class User(db.Model, UserMixin): replies = db.relationship("ThreadReply", backref="author", lazy="dynamic") def __init__(self, username, active=False, email=None, password=None): - import datetime - self.username = username self.confirmed_at = datetime.datetime.now() - datetime.timedelta(days=6000) self.display_name = username @@ -172,6 +169,16 @@ class User(db.Model, UserMixin): else: raise Exception("Permission {} is not related to users".format(perm.name)) + def canCommentRL(self): + hour_ago = datetime.datetime.utcnow() - datetime.timedelta(hours=1) + return ThreadReply.query.filter_by(author=self) \ + .filter(ThreadReply.created_at > hour_ago).count() < 4 + + def canOpenThreadRL(self): + hour_ago = datetime.datetime.utcnow() - datetime.timedelta(hours=1) + return Thread.query.filter_by(author=self) \ + .filter(Thread.created_at > hour_ago).count() < 2 + class UserEmailVerification(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey("user.id")) @@ -347,7 +354,7 @@ class Package(db.Model): shortDesc = db.Column(db.String(200), nullable=False) desc = db.Column(db.Text, nullable=True) type = db.Column(db.Enum(PackageType)) - created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) license_id = db.Column(db.Integer, db.ForeignKey("license.id"), nullable=False, default=1) license = db.relationship("License", foreign_keys=[license_id]) @@ -496,8 +503,11 @@ class Package(db.Model): isOwner = user == self.author + if perm == Permission.CREATE_THREAD: + return user.rank.atLeast(UserRank.MEMBER) + # Members can edit their own packages, and editors can edit any packages - if perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS or perm == Permission.CREATE_THREAD: + if perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS: return isOwner or user.rank.atLeast(UserRank.EDITOR) if perm == Permission.EDIT_PACKAGE or perm == Permission.APPROVE_CHANGES: @@ -522,8 +532,6 @@ class Package(db.Model): raise Exception("Permission {} is not related to packages".format(perm.name)) def recalcScore(self): - import datetime - self.score = 10 if self.forums is not None: @@ -630,7 +638,7 @@ class PackageRelease(db.Model): def __init__(self): - self.releaseDate = datetime.now() + self.releaseDate = datetime.datetime.now() class PackageReview(db.Model): @@ -762,7 +770,7 @@ class Thread(db.Model): title = db.Column(db.String(100), nullable=False) private = db.Column(db.Boolean, server_default="0") - created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) replies = db.relationship("ThreadReply", backref="thread", lazy="dynamic") @@ -800,7 +808,7 @@ class ThreadReply(db.Model): thread_id = db.Column(db.Integer, db.ForeignKey("thread.id"), nullable=False) comment = db.Column(db.String(500), nullable=False) author_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) - created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) REPO_BLACKLIST = [".zip", "mediafire.com", "dropbox.com", "weebly.com", \ @@ -824,7 +832,7 @@ class ForumTopic(db.Model): posts = db.Column(db.Integer, nullable=False) views = db.Column(db.Integer, nullable=False) - created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) def getRepoURL(self): if self.link is None: diff --git a/app/templates/macros/threads.html b/app/templates/macros/threads.html index 65f02f4..fd7b648 100644 --- a/app/templates/macros/threads.html +++ b/app/templates/macros/threads.html @@ -42,11 +42,18 @@ -
- -
- -
+ {% if current_user.canCommentRL() %} +
+ +
+ +
+ {% else %} +
+
+ +
+ {% endif %} diff --git a/app/views/threads.py b/app/views/threads.py index 37ac3d1..c168a23 100644 --- a/app/views/threads.py +++ b/app/views/threads.py @@ -21,6 +21,8 @@ from app import app from app.models import * from app.utils import triggerNotif, clearNotifications +import datetime + from flask_wtf import FlaskForm from wtforms import * from wtforms.validators import * @@ -78,6 +80,13 @@ def thread_page(id): if current_user.is_authenticated and request.method == "POST": comment = request.form["comment"] + if not current_user.canCommentRL(): + flash("Please wait before commenting again", "danger") + if package: + return redirect(package.getDetailsURL()) + else: + return redirect(url_for("home_page")) + if len(comment) <= 500 and len(comment) > 3: reply = ThreadReply() reply.author = current_user @@ -126,15 +135,15 @@ def new_thread_page(): if package is None: flash("Unable to find that package!", "error") - # Don't allow making threads on approved packages for now + # Don't allow making orphan threads on approved packages for now if package is None: abort(403) def_is_private = request.args.get("private") or False - if not package.approved: + if package is None or not package.approved: def_is_private = True - allow_change = package.approved - is_review_thread = package is not None and not package.approved + allow_change = package and package.approved + is_review_thread = package and not package.approved # Check that user can make the thread if not package.checkPerm(current_user, Permission.CREATE_THREAD): @@ -144,8 +153,15 @@ def new_thread_page(): # Only allow creating one thread when not approved elif is_review_thread and package.review_thread is not None: flash("A review thread already exists!", "error") - if request.method == "GET": - return redirect(url_for("thread_page", id=package.review_thread.id)) + return redirect(url_for("thread_page", id=package.review_thread.id)) + + elif not current_user.canOpenThreadRL(): + flash("Please wait before opening another thread", "danger") + + if package: + return redirect(package.getDetailsURL()) + else: + return redirect(url_for("home_page")) # Set default values elif request.method == "GET": @@ -178,9 +194,15 @@ def new_thread_page(): if is_review_thread: package.review_thread = thread + notif_msg = None if package is not None: - triggerNotif(package.author, current_user, - "New thread '{}' on package {}".format(thread.title, package.title), url_for("thread_page", id=thread.id)) + notif_msg = "New thread '{}' on package {}".format(thread.title, package.title) + triggerNotif(package.author, current_user, notif_msg, url_for("thread_page", id=thread.id)) + else: + notif_msg = "New thread '{}'".format(thread.title) + + for user in User.query.filter(User.rank >= UserRank.EDITOR).all(): + triggerNotif(user, current_user, notif_msg, url_for("thread_page", id=thread.id)) db.session.commit() diff --git a/docker-compose.yml b/docker-compose.yml index 4c2752e..93e86bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,6 @@ version: '3' services: db: image: "postgres:9.6.5" - restart: always volumes: - "./data/db:/var/lib/postgresql/data" env_file: @@ -21,6 +20,7 @@ services: - 5123:5123 volumes: - "./data/uploads:/home/cdb/app/public/uploads" + - "./app:/home/cdb/app" depends_on: - db - redis diff --git a/runprodguni.sh b/runprodguni.sh index fca01c0..c7e8bb8 100644 --- a/runprodguni.sh +++ b/runprodguni.sh @@ -1,3 +1,3 @@ #!/bin/bash -gunicorn -w 4 -b :5123 -e FLASK_APP=app/__init__.py -e FLASK_CONFIG=../config.prod.cfg -e FLASK_DEBUG=0 app:app +gunicorn -w 4 -b :5123 -e FLASK_APP=app/__init__.py -e FLASK_CONFIG=../config.prod.cfg -e FLASK_DEBUG=1 app:app