From eaa77d363baf47ab836f3932e212b75a41b89e0c Mon Sep 17 00:00:00 2001 From: David Hoppenbrouwers Date: Sun, 9 Oct 2022 13:48:44 +0200 Subject: [PATCH] Implement banning --- db/sqlite.py | 53 ++++++++++++++------ main.py | 99 +++++++++++++++++++++++++++++--------- templates/admin/index.html | 31 +++++++----- templates/base.html | 5 +- templates/comment.html | 6 +-- 5 files changed, 139 insertions(+), 55 deletions(-) diff --git a/db/sqlite.py b/db/sqlite.py index a0acf64..ecce394 100644 --- a/db/sqlite.py +++ b/db/sqlite.py @@ -164,9 +164,9 @@ class DB: ) db.commit() - def get_user_name_role(self, user_id): + def get_user_name_role_banned(self, user_id): return self._db().execute(''' - select name, role + select name, role, banned_until from users where user_id = ? ''', @@ -188,11 +188,15 @@ class DB: c.execute(''' insert into threads (author_id, forum_id, title, text, create_time, modify_time, update_time) - values (?, ?, ?, ?, ?, ?, ?) + select ?, ?, ?, ?, ?, ?, ? + from users + where user_id = ? and banned_until < ? ''', - (author_id, forum_id, title, text, time, time, time) + (author_id, forum_id, title, text, time, time, time, author_id, time) ) rowid = c.lastrowid + if rowid is None: + return None db.commit() return db.execute(''' select thread_id @@ -245,10 +249,10 @@ class DB: c.execute(''' insert into comments(thread_id, author_id, text, create_time, modify_time) select ?, ?, ?, ?, ? - from threads - where thread_id = ? + from threads, users + where thread_id = ? and user_id = ? and banned_until < ? ''', - (thread_id, author_id, text, time, time, thread_id) + (thread_id, author_id, text, time, time, thread_id, author_id, time) ) if c.rowcount > 0: print('SHIT') @@ -269,10 +273,10 @@ class DB: c.execute(''' insert into comments(thread_id, parent_id, author_id, text, create_time, modify_time) select thread_id, ?, ?, ?, ?, ? - from comments - where comment_id = ? + from comments, users + where comment_id = ? and user_id = ? and banned_until < ? ''', - (parent_id, author_id, text, time, time, parent_id) + (parent_id, author_id, text, time, time, parent_id, author_id, time) ) if c.rowcount > 0: c.execute(''' @@ -297,12 +301,17 @@ class DB: update threads set title = ?, text = ?, modify_time = ? where thread_id = ? and ( - author_id = ? + (author_id = ? and (select 1 from users where user_id = ? and banned_until < ?)) -- 1 = moderator, 2 = admin or (select 1 from users where user_id = ? and (role = 1 or role = 2)) ) ''', - (title, text, time, thread_id, user_id, user_id) + ( + title, text, time, + thread_id, + user_id, user_id, time, + user_id, + ) ) if c.rowcount > 0: db.commit() @@ -316,12 +325,17 @@ class DB: update comments set text = ?, modify_time = ? where comment_id = ? and ( - author_id = ? + (author_id = ? and (select 1 from users where user_id = ? and banned_until < ?)) -- 1 = moderator, 2 = admin or (select 1 from users where user_id = ? and (role = 1 or role = 2)) ) ''', - (text, time, comment_id, user_id, user_id) + ( + text, time, + comment_id, + user_id, user_id, time, + user_id, + ) ) if c.rowcount > 0: db.commit() @@ -398,6 +412,15 @@ class DB: (secret_key, captcha_key) ) + def set_user_ban(self, user_id, until): + return self.change_one(''' + update users + set banned_until = ? + where user_id = ? + ''', + (until, user_id) + ) + def change_one(self, query, values): db = self._db() c = db.cursor() @@ -415,4 +438,4 @@ class DB: return rows, c.rowcount def _db(self): - return sqlite3.connect(self.conn) + return sqlite3.connect(self.conn, timeout=5) diff --git a/main.py b/main.py index 6c98f35..78e109e 100644 --- a/main.py +++ b/main.py @@ -153,9 +153,14 @@ def new_thread(forum_id): return redirect(url_for('login')) if request.method == 'POST': - id, = db.add_thread(user_id, forum_id, request.form['title'], trim_text(request.form['text']), time.time_ns()) - flash('Created thread', 'success') - return redirect(url_for('thread', thread_id = id)) + id = db.add_thread(user_id, forum_id, request.form['title'], trim_text(request.form['text']), time.time_ns()) + if id is None: + flash('Failed to create thread', 'error') + return redirect(url_for('forum', forum_id = forum_id)) + else: + id, = id + flash('Created thread', 'success') + return redirect(url_for('thread', thread_id = id)) return render_template( 'new_thread.html', @@ -430,6 +435,42 @@ def admin_new_secrets(): flash(str(e), 'error') return redirect(url_for('admin')) +@app.route('/admin/user//ban/', methods = ['POST']) +def admin_ban_user(user_id): + chk, user = _admin_check() + if not chk: + return user + + d, t = request.form['days'], request.form['time'] + d = 0 if d == '' else int(d) + h, m = (0, 0) if t == '' else map(int, t.split(':')) + until = time.time_ns() + (d * 24 * 60 + h * 60 + m) * (60 * 10**9) + until = min(until, 0xffff_ffff_ffff_ffff) + + try: + if db.set_user_ban(user_id, until): + flash('Banned user', 'success') + else: + flash('Failed to ban user', 'error') + except Exception as e: + flash(str(e), 'error') + return redirect(url_for('admin')) + +@app.route('/admin/user//unban/', methods = ['POST']) +def admin_unban_user(user_id): + chk, user = _admin_check() + if not chk: + return user + + try: + if db.set_user_ban(user_id, None): + flash('Unbanned user', 'success') + else: + flash('Failed to unban user', 'error') + except Exception as e: + flash(str(e), 'error') + return redirect(url_for('admin')) + def _admin_check(): user = get_user() if user is None: @@ -480,10 +521,11 @@ def create_comment_tree(comments): class User: - def __init__(self, id, name, role): + def __init__(self, id, name, role, banned_until): self.id = id self.name = name self.role = role + self.banned_until = banned_until def is_moderator(self): return self.role in (Role.ADMIN, Role.MODERATOR) @@ -491,54 +533,64 @@ class User: def is_admin(self): return self.role == Role.ADMIN + def is_banned(self): + return self.banned_until is not None and self.banned_until > time.time_ns() + def get_user(): id = session.get('user_id') if id is not None: - name, role = db.get_user_name_role(id) - return User(id, name, role) + name, role, banned_until = db.get_user_name_role_banned(id) + return User(id, name, role, banned_until) return None @app.context_processor def utility_processor(): - def format_since(t): - n = time.time_ns() - if n < t: - return 'In a distant future' - + def _format_time_delta(n, t): # Try the sane thing first dt = (n - t) // 10 ** 9 if dt < 1: - return "less than a second ago" + return "less than a second" if dt < 2: - return f"1 second ago" + return f"1 second" if dt < 60: - return f"{dt} seconds ago" + return f"{dt} seconds" if dt < 119: - return f"1 minute ago" + return f"1 minute" if dt < 3600: - return f"{dt // 60} minutes ago" + return f"{dt // 60} minutes" if dt < 3600 * 2: - return f"1 hour ago" + return f"1 hour" if dt < 3600 * 24: - return f"{dt // 3600} hours ago" + return f"{dt // 3600} hours" if dt < 3600 * 24 * 31: - return f"{dt // (3600 * 24)} days ago" + return f"{dt // (3600 * 24)} days" # Try some very rough estimate, whatever f = lambda x: datetime.utcfromtimestamp(x // 10 ** 9) n, t = f(n), f(t) def f(x, y, s): - return f'{y - x} {s}{"s" if y - x > 1 else ""} ago' + return f'{y - x} {s}{"s" if y - x > 1 else ""}' if t.year < n.year: return f(t.year, n.year, "year") if t.month < n.month: return f(t.month, n.month, "month") - # This shouldn't be reachable, but it's still better to return something - return "incredibly long ago" + assert False, 'unreachable' + + def format_since(t): + n = time.time_ns() + if n < t: + return 'in a distant future' + return _format_time_delta(n, t) + ' ago' + + def format_until(t): + n = time.time_ns() + if t <= n: + return 'in a distant past' + return _format_time_delta(t, n) def format_time(t): - return datetime.utcfromtimestamp(t / 10 ** 9) + return datetime.utcfromtimestamp(t / 10 ** 9).replace(microsecond=0) def minimd(text): # Replace angle brackets to prevent XSS @@ -554,6 +606,7 @@ def utility_processor(): return { 'format_since': format_since, 'format_time': format_time, + 'format_until': format_until, 'minimd': minimd, } diff --git a/templates/admin/index.html b/templates/admin/index.html index cfdb147..1ee7284 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -76,30 +76,35 @@ Name Join date Role -Banned until -Actions +Banned {%- for id, name, join_date, role, banned_until in users -%} {{ id }} {{ name }} {{ format_time(join_date) }} -{{ ['user', 'moderator', 'admin'][role] if role >= 0 and role <= 2 else role }} -{{ '' if banned_until is none else format_time(banned_until) }} -
+ + + +
+ + +{% if banned_until is not none %} +
+{{- format_time(banned_until) }} + +
+{% endif %} +
-
- - -
{%- endfor -%} diff --git a/templates/base.html b/templates/base.html index 48528ab..9bfe742 100644 --- a/templates/base.html +++ b/templates/base.html @@ -10,7 +10,10 @@
{%- if user is not none -%} - {{ user.name }} + + {{- user.name }} + {%- if user.is_banned() %} (banned for {{ format_until(user.banned_until) }}){% endif -%} + | {%- if user.is_admin() -%} Admin panel diff --git a/templates/comment.html b/templates/comment.html index f6dbe54..c52cef9 100644 --- a/templates/comment.html +++ b/templates/comment.html @@ -10,7 +10,7 @@ {%- if comment.parent_id is not none -%} parent {%- endif -%} - {%- if user is not none and (comment.author_id == user.id or user.is_moderator()) -%} + {%- if user is not none and (comment.author_id == user.id or user.is_moderator()) and not user.is_banned() -%} edit {%- if can_delete -%} delete @@ -22,7 +22,7 @@ {%- macro thread_author(author_id, name, ctime, mtime) -%}

{{- author(author_id, name, ctime, mtime) -}} - {%- if user is not none and (author_id == user.id or user.is_moderator()) -%} + {%- if user is not none and (author_id == user.id or user.is_moderator()) and not user.is_banned() -%} edit delete {%- endif -%} @@ -49,7 +49,7 @@ {%- endmacro -%} {%- macro reply() -%} -{%- if user is not none -%} +{%- if user is not none and not user.is_banned() -%}