From a9698d362f9b860148d5c2b64511c38babcff2f2 Mon Sep 17 00:00:00 2001 From: azivner Date: Thu, 28 Sep 2017 23:16:36 -0400 Subject: [PATCH] basic implementation of audit logging --- setup.py | 13 +++++-------- src/audit_category.py | 8 ++++++++ src/change_password.py | 5 ++++- src/notes_api.py | 11 ++++++++++- src/notes_move_api.py | 12 +++++++++++- src/settings_api.py | 4 ++++ src/sql.py | 14 ++++++++++++++ src/tree_api.py | 4 ++++ static/js/tree.js | 5 +++++ 9 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 src/audit_category.py diff --git a/setup.py b/setup.py index 525340595..da0fcb4c8 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,15 @@ #!/usr/bin/python -import binascii -import getpass -import os import base64 +import getpass +import hashlib +import os +from Crypto.Cipher import AES +from Crypto.Util import Counter from builtins import input -import src.config_provider -import src.sql import src.my_scrypt -from Crypto.Cipher import AES -from Crypto.Util import Counter -import hashlib config = src.config_provider.getConfig() src.sql.connect(config['Document']['documentPath']) diff --git a/src/audit_category.py b/src/audit_category.py new file mode 100644 index 000000000..d23704d1b --- /dev/null +++ b/src/audit_category.py @@ -0,0 +1,8 @@ +UPDATE_CONTENT = 'CONTENT' +CHANGE_POSITION = 'POSITION' +CREATE_NOTE = 'CREATE' +DELETE_NOTE = 'DELETE' +CHANGE_PARENT = 'PARENT' +ENCRYPTION = 'ENCRYPTION' +CHANGE_PASSWORD = 'PASSWORD' +SETTINGS = 'SETTINGS' \ No newline at end of file diff --git a/src/change_password.py b/src/change_password.py index 735381651..31c5e6258 100644 --- a/src/change_password.py +++ b/src/change_password.py @@ -5,9 +5,10 @@ from Crypto.Util import Counter import sql import my_scrypt +import audit_category -def change_password(current_password, new_password): +def change_password(current_password, new_password, request = None): current_password_hash = base64.b64encode(my_scrypt.getVerificationHash(current_password)) if current_password_hash != sql.getOption('password_verification_hash'): @@ -49,6 +50,8 @@ def change_password(current_password, new_password): sql.setOption('password_verification_hash', new_password_verification_key) + sql.addAudit(audit_category.CHANGE_PASSWORD, request) + sql.commit() return { diff --git a/src/notes_api.py b/src/notes_api.py index 51d635603..6b1fb94aa 100644 --- a/src/notes_api.py +++ b/src/notes_api.py @@ -10,7 +10,9 @@ from flask_login import login_required from sql import delete from sql import execute, insert, commit -from sql import getResults, getSingleResult, getOption +from sql import getResults, getSingleResult, getOption, addAudit + +import audit_category notes_api = Blueprint('notes_api', __name__) @@ -66,6 +68,9 @@ def updateNote(note_id): now ]) + if note['detail']['encryption'] != detail['encryption']: + addAudit(audit_category.ENCRYPTION, request, note_id, detail['encryption'], note['detail']['encryption']) + execute("update notes set note_title = ?, note_text = ?, encryption = ?, date_modified = ? where note_id = ?", [ note['detail']['note_title'], note['detail']['note_text'], @@ -105,6 +110,8 @@ def deleteNote(note_id): delete("notes_tree", note_id) delete("notes", note_id) + addAudit(audit_category.DELETE_NOTE, request, note_id) + commit() return jsonify({}) @@ -137,6 +144,8 @@ def createChild(parent_note_id): else: raise Exception('Unknown target: ' + note['target']) + addAudit(audit_category.CREATE_NOTE, request, noteId) + now = math.floor(time.time()) insert("notes", { diff --git a/src/notes_move_api.py b/src/notes_move_api.py index d8489c94e..ede63a8c0 100644 --- a/src/notes_move_api.py +++ b/src/notes_move_api.py @@ -1,7 +1,9 @@ from flask import Blueprint, jsonify +from flask import request from flask_login import login_required -from sql import execute, commit +import audit_category +from sql import execute, commit, addAudit from sql import getSingleResult notes_move_api = Blueprint('notes_move_api', __name__) @@ -20,6 +22,8 @@ def moveToNote(note_id, parent_id): execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [parent_id, new_note_pos, note_id]) + addAudit(audit_category.CHANGE_PARENT, request, note_id) + commit() return jsonify({}) @@ -32,6 +36,8 @@ def moveBeforeNote(note_id, before_note_id): execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [before_note['note_pid'], before_note['note_pos'], note_id]) + addAudit(audit_category.CHANGE_POSITION, request, note_id) + commit() return jsonify({}) @@ -45,6 +51,8 @@ def moveAfterNote(note_id, after_note_id): execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [after_note['note_pid'], after_note['note_pos'] + 1, note_id]) + addAudit(audit_category.CHANGE_POSITION, request, note_id) + commit() return jsonify({}) @@ -53,5 +61,7 @@ def moveAfterNote(note_id, after_note_id): def setExpandedNote(note_id, expanded): execute("update notes_tree set is_expanded = ? where note_id = ?", [expanded, note_id]) + # no audit here, not really important + commit() return jsonify({}) \ No newline at end of file diff --git a/src/settings_api.py b/src/settings_api.py index 184e2f83a..4d30bdbe2 100644 --- a/src/settings_api.py +++ b/src/settings_api.py @@ -2,6 +2,7 @@ from flask import Blueprint, jsonify, request from flask_login import login_required import sql +import audit_category settings_api = Blueprint('settings_api', __name__) @@ -25,7 +26,10 @@ def set_settings(): req = request.get_json(force=True) if req['name'] in allowed_options: + sql.addAudit(audit_category.SETTINGS, request, None, sql.getOption(req['name']), req['value'], req['name']) + sql.setOption(req['name'], req['value']) + sql.commit() return jsonify({}) diff --git a/src/sql.py b/src/sql.py index 1a59a0af7..d877a65c3 100644 --- a/src/sql.py +++ b/src/sql.py @@ -1,6 +1,9 @@ import base64 import sqlite3 +import math +import time + conn = None def dict_factory(cursor, row): @@ -32,6 +35,17 @@ def setOption(name, value): def getOption(name): return getSingleResult("SELECT opt_value FROM options WHERE opt_name = ?", [name])['opt_value'] +def addAudit(category, request = None, note_id = None, change_from = None, change_to = None, comment = None): + now = math.floor(time.time()) + + browser_id = None + + if request: + browser_id = request.headers['x-browser-id'] + + execute("INSERT INTO audit_log (date_modified, category, browser_id, note_id, change_from, change_to, comment)" + " VALUES (?, ?, ?, ?, ?, ?, ?)", [now, category, browser_id, note_id, change_from, change_to, comment]) + def delete(tablename, note_id): execute("DELETE FROM " + tablename + " WHERE note_id = ?", [note_id]) diff --git a/src/tree_api.py b/src/tree_api.py index ac31f5a8d..d14aae38a 100644 --- a/src/tree_api.py +++ b/src/tree_api.py @@ -1,3 +1,6 @@ +import base64 +import os + from flask import Blueprint, jsonify from flask_login import login_required @@ -44,5 +47,6 @@ def getTree(): retObject['password_derived_key_salt'] = getOption('password_derived_key_salt') retObject['encrypted_data_key'] = getOption('encrypted_data_key') retObject['encryption_session_timeout'] = getOption('encryption_session_timeout') + retObject['browser_id'] = base64.b64encode(os.urandom(8)) return jsonify(retObject) \ No newline at end of file diff --git a/static/js/tree.js b/static/js/tree.js index 7b61988c4..b3716ae36 100644 --- a/static/js/tree.js +++ b/static/js/tree.js @@ -95,6 +95,11 @@ $(function(){ globalEncryptionSessionTimeout = resp.encryption_session_timeout; globalEncryptedDataKey = resp.encrypted_data_key; + // add browser ID header to all AJAX requests + $.ajaxSetup({ + headers: { 'x-browser-id': resp.browser_id } + }); + if (document.location.hash) { startNoteId = document.location.hash.substr(1); // strip initial # }