Merge pull request #15 from DarkflameUniverse/audit-log

Audit log
This commit is contained in:
Aaron Kimbrell 2022-02-11 23:06:45 -06:00 committed by GitHub
commit 9cc3dbb4c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 169 additions and 18 deletions

View File

@ -13,7 +13,7 @@ from flask_apscheduler import APScheduler
from app.luclient import query_cdclient, register_luclient_jinja_helpers
from app.commands import init_db, init_accounts, load_property, gen_image_cache, gen_model_cache
from app.models import Account, AccountInvitation
from app.models import Account, AccountInvitation, AuditLog
import logging
from logging.handlers import RotatingFileHandler
@ -245,3 +245,9 @@ def gm_level(gm_level):
return func(*args, **kwargs)
return wrapper
return decorator
def log_audit(message):
AuditLog(
account_id=current_user.id,
action=message
).save()

View File

@ -6,7 +6,7 @@ import datetime
import time
from app.models import Account, AccountInvitation, db
from app.schemas import AccountSchema
from app import gm_level
from app import gm_level, log_audit
from app.forms import EditGMLevelForm
accounts_blueprint = Blueprint('accounts', __name__)
@ -46,8 +46,10 @@ def edit_gm_level(id):
form = EditGMLevelForm()
if form.validate_on_submit():
log_audit(f"Changed ({account_data.id}){account_data.username}'s GM Level from {account_data.gm_level} to {form.gm_level.data}")
account_data.gm_level = form.gm_level.data
account_data.save()
return redirect(url_for('accounts.view', id=account_data.id))
form.gm_level.data = account_data.gm_level
@ -63,8 +65,10 @@ def lock(id):
account.locked = not account.locked
account.save()
if account.locked:
log_audit(f"Locked ({account.id}){account.username}")
flash("Locked Account", "danger")
else:
log_audit(f"Unlocked ({account.id}){account.username}")
flash("Unlocked account", "success")
return redirect(request.referrer if request.referrer else url_for("main.index"))
@ -77,8 +81,10 @@ def ban(id):
account.banned = not account.banned
account.save()
if account.banned:
log_audit(f"Banned ({account.id}){account.username}")
flash("Banned Account", "danger")
else:
log_audit(f"Unbanned ({account.id}){account.username}")
flash("Unbanned account", "success")
return redirect(request.referrer if request.referrer else url_for("main.index"))
@ -90,10 +96,12 @@ def mute(id, days=0):
account = Account.query.filter(Account.id == id).first()
if days == "0":
account.mute_expire = 0
log_audit(f"Unmuted ({account.id}){account.username}")
flash("Unmuted Account", "success")
else:
muted_intil = datetime.datetime.now() + datetime.timedelta(days=int(days))
account.mute_expire = muted_intil.timestamp()
log_audit(f"Muted ({account.id}){account.username} for {days} days")
flash(f"Muted account for {days} days", "danger")
account.save()

View File

@ -5,7 +5,7 @@ from datatables import ColumnDT, DataTables
import datetime, time
from app.models import CharacterInfo, CharacterXML, Account, db
from app.schemas import CharacterInfoSchema
from app import gm_level
from app import gm_level, log_audit
import xmltodict
character_blueprint = Blueprint('characters', __name__)
@ -26,16 +26,19 @@ def approve_name(id, action):
character = CharacterInfo.query.filter(CharacterInfo.id == id).first()
if action == "approve":
log_audit(f"Approved ({character.id}){character.pending_name} from {character.name}")
flash(
f"Approved ({character.id}){character.pending_name} from {character.name}",
"success"
)
if character.pending_name:
character.name = character.pending_name
character.pending_name = ""
character.needs_rename = False
flash(
f"Approved name {character.name}",
"success"
)
elif action == "rename":
character.needs_rename = True
log_audit(f"Marked character ({character.id}){character.name} (Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename")
flash(
f"Marked character {character.name} (Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename",
"danger"
@ -146,6 +149,9 @@ def restrict(id, bit):
abort(404)
return
log_audit(f"Updated ({character_data.id}){character_data.name}'s permission map to \
{character_data.permission_map ^ (1 << int(bit))} from {character_data.permission_map}")
character_data.permission_map ^= (1 << int(bit))
character_data.save()

View File

@ -3,7 +3,7 @@ from flask_user import login_required, current_user
from app.models import db, Mail, CharacterInfo
from datatables import ColumnDT, DataTables
from app.forms import SendMailForm
from app import gm_level
from app import gm_level, log_audit
from app.luclient import translate_from_locale, query_cdclient
import time
@ -29,6 +29,7 @@ def send():
if form.attachment.data != "0" and form.attachment_count.data == 0:
form.attachment_count.data = 1
if form.recipient.data == "0":
log_audit(f"Sending {form.subject.data}: {form.body.data} to All Characters with {form.attachment_count.data} of item {form.attachment.data}")
for character in CharacterInfo.query.all():
Mail(
sender_id = 0,
@ -42,6 +43,7 @@ def send():
attachment_lot = form.attachment.data,
attachment_count = form.attachment_count.data
).save()
log_audit(f"Sent {form.subject.data}: {form.body.data} to ({character.id}){character.name} with {form.attachment_count.data} of item {form.attachment.data}")
else:
Mail(
sender_id = 0,
@ -55,6 +57,7 @@ def send():
attachment_lot = form.attachment.data,
attachment_count = form.attachment_count.data
).save()
log_audit(f"Sent {form.subject.data}: {form.body.data} to ({form.recipient.data}){CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name} with {form.attachment_count.data} of item {form.attachment.data}")
flash("Sent Mail", "success")
return redirect(url_for('mail.send'))

View File

@ -1,6 +1,6 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_user import UserMixin
from flask_user import UserMixin, current_user
from wtforms import ValidationError
import logging
@ -1010,3 +1010,42 @@ class Reports(db.Model):
def delete(self):
db.session.delete(self)
db.session.commit()
class AuditLog(db.Model):
__tablename__ = 'audit_logs'
id = db.Column(
mysql.INTEGER,
primary_key=True
)
account_id = db.Column(
db.Integer(),
db.ForeignKey(Account.id, ondelete='CASCADE'),
nullable=False,
)
account = db.relationship(
'Account',
backref="audit_logs",
passive_deletes=True
)
action = db.Column(
mysql.TEXT,
nullable=False
)
date = db.Column(
mysql.TIMESTAMP,
nullable=False,
server_default=db.func.now()
)
def save(self):
db.session.add(self)
db.session.commit()
db.session.refresh(self)
def delete(self):
db.session.delete(self)
db.session.commit()

View File

@ -3,7 +3,7 @@ from flask_user import login_required
from app.models import PetNames, db
from datatables import ColumnDT, DataTables
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
from app import gm_level
from app import gm_level, log_audit
moderation_blueprint = Blueprint('moderation', __name__)
@ -23,6 +23,7 @@ def approve_pet(id):
pet_data = PetNames.query.filter(PetNames.id == id).first()
pet_data.approved = 2
log_audit(f"Approved pet name {pet_data.pet_name}")
flash(f"Approved pet name {pet_data.pet_name}", "success")
pet_data.save()
return redirect(request.referrer if request.referrer else url_for("main.index"))
@ -36,6 +37,7 @@ def reject_pet(id):
pet_data = PetNames.query.filter(PetNames.id == id).first()
pet_data.approved = 0
log_audit(f"Rejected pet name {pet_data.pet_name}")
flash(f"Rejected pet name {pet_data.pet_name}", "danger")
pet_data.save()
return redirect(request.referrer if request.referrer else url_for("main.index"))

View File

@ -3,7 +3,7 @@ from flask_user import login_required, current_user
from app.models import Account, AccountInvitation, PlayKey, db
from datatables import ColumnDT, DataTables
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
from app import gm_level
from app import gm_level, log_audit
play_keys_blueprint = Blueprint('play_keys', __name__)
@ -21,6 +21,7 @@ def index():
@gm_level(9)
def create(count=1, uses=1):
PlayKey.create(count=count, uses=uses)
log_audit(f"Created {count} Play Key(s) with {uses} uses!")
flash(f"Created {count} Play Key(s) with {uses} uses!", "success")
return redirect(url_for('play_keys.index'))
@ -32,6 +33,8 @@ def bulk_create():
form = CreatePlayKeyForm()
if form.validate_on_submit():
PlayKey.create(count=form.count.data, uses=form.uses.data)
log_audit(f"Created {form.count.data} Play Key(s) with {form.uses.data} uses!")
flash(f"Created {form.count.data} Play Key(s) with {form.uses.data} uses!", "success")
return redirect(url_for('play_keys.index'))
return render_template('play_keys/bulk.html.j2', form=form)
@ -43,6 +46,7 @@ def bulk_create():
def delete(id):
key = PlayKey.query.filter(PlayKey.id == id).first()
associated_accounts = Account.query.filter(Account.play_key_id==id).all()
log_audit(f"Deleted Play Key {key.key_string}")
flash(f"Deleted Play Key {key.key_string}", "danger")
key.delete()
return redirect(url_for('play_keys.index'))
@ -56,10 +60,16 @@ def edit(id):
form = EditPlayKeyForm()
if form.validate_on_submit():
log_audit(f"Updated Play key {key.id} \
Uses: {key.key_uses}:{form.uses.data} \
Active: {key.active}:{form.active.data} \
Notes: {key.notes}:{form.notes.data} \
")
key.key_uses = form.uses.data
key.active = form.active.data
key.notes = form.notes.data
key.save()
return redirect(url_for('play_keys.index'))
form.uses.data = key.key_uses

View File

@ -16,7 +16,7 @@ from datatables import ColumnDT, DataTables
import time
from app.models import Property, db, UGC, CharacterInfo, PropertyContent, Account
from app.schemas import PropertySchema
from app import gm_level
from app import gm_level, log_audit
from app.luclient import query_cdclient
import zlib
@ -50,25 +50,29 @@ def approve(id):
property_data.rejection_reason = ""
if property_data.mod_approved:
flash(
f"""Approved Property
message = f"""Approved Property
{property_data.name if property_data.name else query_cdclient(
'select DisplayDescription from ZoneTable where zoneID = ?',
[property_data.zone_id],
one=True
)[0]}
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}""",
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}"""
log_audit(message)
flash(
message,
"success"
)
else:
flash(
f"""Unapproved Property
message = f"""Unapproved Property
{property_data.name if property_data.name else query_cdclient(
'select DisplayDescription from ZoneTable where zoneID = ?',
[property_data.zone_id],
one=True
)[0]}
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}""",
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}"""
log_audit(message)
flash(
message,
"danger"
)

View File

@ -0,0 +1,38 @@
"""fix nullables
Revision ID: 3132aaef7413
Revises: bd908969d8fe
Create Date: 2022-02-11 21:51:58.479066
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '3132aaef7413'
down_revision = 'bd908969d8fe'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('audit_logs', 'account_id',
existing_type=mysql.INTEGER(display_width=11),
nullable=False)
op.alter_column('audit_logs', 'action',
existing_type=mysql.TEXT(),
nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('audit_logs', 'action',
existing_type=mysql.TEXT(),
nullable=True)
op.alter_column('audit_logs', 'account_id',
existing_type=mysql.INTEGER(display_width=11),
nullable=True)
# ### end Alembic commands ###

View File

@ -0,0 +1,35 @@
"""Add audit_log table
Revision ID: bd908969d8fe
Revises: aee4c6c24811
Create Date: 2022-02-11 21:48:03.798474
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'bd908969d8fe'
down_revision = 'aee4c6c24811'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('audit_logs',
sa.Column('id', mysql.INTEGER(), nullable=False),
sa.Column('account_id', sa.Integer(), nullable=True),
sa.Column('action', mysql.TEXT(), nullable=True),
sa.Column('date', mysql.TIMESTAMP(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('audit_logs')
# ### end Alembic commands ###