From 3094205362aa8c7e9c4ce62d1a327aa7c83c5b9b Mon Sep 17 00:00:00 2001 From: lza_menace Date: Wed, 31 Aug 2022 23:28:48 -0700 Subject: [PATCH] add audit logs --- suchwow/cli.py | 4 ++-- suchwow/models.py | 9 +++++++++ suchwow/routes/mod.py | 16 ++++++++++++---- suchwow/routes/post.py | 12 +++++------- suchwow/templates/mod/logs.html | 29 +++++++++++++++++++++++++++++ suchwow/templates/mod/main.html | 2 +- suchwow/utils/helpers.py | 16 +++++++++++++--- 7 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 suchwow/templates/mod/logs.html diff --git a/suchwow/cli.py b/suchwow/cli.py index b1ce203..2ea280d 100644 --- a/suchwow/cli.py +++ b/suchwow/cli.py @@ -3,7 +3,7 @@ from os import makedirs import click from flask import Blueprint, url_for, current_app -from suchwow.models import Post, Profile, Comment, Notification, db, Moderator, Ban +from suchwow.models import Post, Profile, Comment, Notification, db, Moderator, Ban, AuditEvent from suchwow.utils.helpers import get_latest_tipped_posts from suchwow.utils.helpers import get_top_posters, get_top_posts from suchwow.reddit import make_post @@ -20,7 +20,7 @@ def init(): makedirs(f"{config.DATA_FOLDER}/{i}", exist_ok=True) # init db - db.create_tables([Post, Profile, Comment, Notification, Moderator, Ban]) + db.create_tables([Post, Profile, Comment, Notification, Moderator, Ban, AuditEvent]) @bp.cli.command("post_reddit") diff --git a/suchwow/models.py b/suchwow/models.py index 295eaa7..6296dfa 100644 --- a/suchwow/models.py +++ b/suchwow/models.py @@ -143,3 +143,12 @@ class Ban(Model): class Meta: database = db + +class AuditEvent(Model): + id = AutoField() + user = ForeignKeyField(Profile) + timestamp = DateTimeField(default=datetime.now) + action = CharField() + + class Meta: + database = db diff --git a/suchwow/routes/mod.py b/suchwow/routes/mod.py index 31bf95b..85d84aa 100644 --- a/suchwow/routes/mod.py +++ b/suchwow/routes/mod.py @@ -1,8 +1,8 @@ from flask import Blueprint, render_template, redirect, url_for, flash, request -from suchwow.models import Post, Profile, Moderator, Ban, get_ban_reason +from suchwow.models import AuditEvent, Post, Profile, Moderator, Ban, get_ban_reason from suchwow.utils.decorators import moderator_required -from suchwow.utils.helpers import get_session_user +from suchwow.utils.helpers import get_session_user, audit_event from suchwow import config @@ -48,6 +48,7 @@ def manage_mods(): flash('Cannot delete super admin you son-of-a-bitch.', 'is-danger') else: m.delete_instance() + audit_event(f'Deleted {to_delete} from mods') flash(f'Removed {to_delete} from mods!', 'is-success') return redirect(url_for('mod.manage_mods')) if request.method == 'POST': @@ -61,6 +62,7 @@ def manage_mods(): else: m = Moderator(username=to_add) m.save() + audit_event(f'Added {to_add} to mods') flash(f'Added {to_add} to mods!', 'is-success') mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username)) return render_template('mod/manage.html', mods=mods) @@ -80,6 +82,7 @@ def manage_bans(): flash('Cannot ban super admin you son-of-a-bitch.', 'is-danger') else: ban.delete_instance() + audit_event(f'Removed ban on {to_delete}') flash(f'Unbanned {to_delete}!', 'is-success') return redirect(url_for('mod.manage_bans')) if request.method == 'POST': @@ -98,9 +101,14 @@ def manage_bans(): reason = get_ban_reason() ban = Ban(user=u, reason=reason) ban.save() + audit_event(f'Banned {to_add} ({reason})') flash(f'Banned {to_add}!', 'is-success') bans = Ban.select() return render_template('mod/bans.html', bans=bans) -# audit trail of activity -# view wallet rpc logs \ No newline at end of file + +@bp.route('/mods/logs') +@moderator_required +def view_logs(): + events = AuditEvent.select().order_by(AuditEvent.timestamp.desc()).limit(50) + return render_template('mod/logs.html', logs=events) \ No newline at end of file diff --git a/suchwow/routes/post.py b/suchwow/routes/post.py index 8a0e1f7..0f24a61 100644 --- a/suchwow/routes/post.py +++ b/suchwow/routes/post.py @@ -2,6 +2,7 @@ from datetime import datetime, timedelta from os import path, remove from io import BytesIO from base64 import b64encode +from sys import audit from qrcode import make as qrcode_make from flask import render_template, Blueprint, request, session, flash from flask import send_from_directory, redirect, url_for, current_app @@ -12,8 +13,7 @@ from suchwow import config from suchwow.models import Post, Profile, Comment, Ban from suchwow.utils.decorators import login_required, profile_required, moderator_required from suchwow.utils.helpers import allowed_file, is_moderator, get_session_user -from suchwow.utils.helpers import rw_cache, post_webhook -from suchwow.reddit import make_post +from suchwow.utils.helpers import post_webhook, audit_event from suchwow.discord import post_discord_webhook @@ -57,7 +57,6 @@ def create(): submitter = get_session_user() u = Profile.filter(username=submitter) banned = Ban.filter(user=u).first() - print(banned) if banned: flash(f"You can't post: {banned.reason}", "is-danger") return redirect("/") @@ -105,7 +104,7 @@ def create(): post.save() post.save_thumbnail() url = url_for('post.read', id=post.id, _external=True) - post_webhook(f"New post :doge2: [{post.id}]({url}) by `{submitter}` :neckbeard:") + audit_event(f'Created new post {post.id}') flash("New post created and pending approval!", "is-success") return redirect(url_for("main.index")) return render_template("post/create.html") @@ -119,11 +118,10 @@ def approve(id): if not post.approved: post.approved = True post.save() - post_webhook(f"Post [{post.id}]({url}) approved :white_check_mark: by `{get_session_user()}` :fieri_parrot:") flash("Approved", "is-success") + audit_event(f'Approved post {post.id}') if current_app.config["DEBUG"] is False: post_discord_webhook(post) - post_webhook(f"Post [{post.id}]({url}) submitted :dab_parrot: to Discord.") return redirect(url_for("mod.pending_posts")) else: flash("You can't approve this", "is-success") @@ -141,8 +139,8 @@ def delete(id): save_path_base = path.join(current_app.config["DATA_FOLDER"], "uploads") save_path = path.join(save_path_base, post.image_name) remove(save_path) + audit_event(f'Deleted post {post.id}') post.delete_instance() - post_webhook(f"Post {post.id} deleted :dumpsterfire: by `{user}` :godmode:") flash("Deleted that shit, brah!", "is-success") if is_mod: return redirect(url_for("mod.pending_posts")) diff --git a/suchwow/templates/mod/logs.html b/suchwow/templates/mod/logs.html new file mode 100644 index 0000000..01d1e0c --- /dev/null +++ b/suchwow/templates/mod/logs.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} + +
+

Manage Mods

+ +
+ {% for log in logs %} +
+
+

{{ log.user.username }} - {{ log.timestamp | humanize }}

+
+
+ {{ log.action }} +
+
+ {% endfor %} +
+
+ +{% endblock %} + +{% block footer %}{% endblock %} diff --git a/suchwow/templates/mod/main.html b/suchwow/templates/mod/main.html index e97c303..cf5cc30 100644 --- a/suchwow/templates/mod/main.html +++ b/suchwow/templates/mod/main.html @@ -52,7 +52,7 @@
  • - + View Logs diff --git a/suchwow/utils/helpers.py b/suchwow/utils/helpers.py index 0b91717..3846db1 100644 --- a/suchwow/utils/helpers.py +++ b/suchwow/utils/helpers.py @@ -1,10 +1,12 @@ import pickle -from os import path, remove +from os import path from datetime import datetime, timedelta -from requests import post as r_post from json import dumps + +from requests import post as r_post from flask import session, current_app -from suchwow.models import Moderator, Post + +from suchwow.models import Moderator, Post, AuditEvent, Profile from suchwow.wownero import Wallet, from_atomic from suchwow import config @@ -20,6 +22,14 @@ def is_moderator(username): else: return False +def get_profile(): + p = Profile.filter(username=get_session_user()).first() + return p + +def audit_event(event): + e = AuditEvent(user=get_profile(), action=event) + e.save() + def get_session_user(): if "auth" not in session or not session["auth"]: return None