diff --git a/database/myems_user_db.sql b/database/myems_user_db.sql index ac1b26a6..8fe493f8 100644 --- a/database/myems_user_db.sql +++ b/database/myems_user_db.sql @@ -66,18 +66,18 @@ DROP TABLE IF EXISTS `myems_user_db`.`tbl_logs` ; CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_logs` ( `id` BIGINT NOT NULL AUTO_INCREMENT, - `user_id` BIGINT NOT NULL, + `user_uuid` CHAR(36) NOT NULL, `request_datetime_utc` DATETIME NOT NULL, `request_method` VARCHAR(256) NOT NULL, `resource_type` VARCHAR(256) NOT NULL, `resource_id` BIGINT NULL, `request_body` JSON NULL, PRIMARY KEY (`id`)); -CREATE INDEX `tbl_logs_index_1` ON `myems_user_db`.`tbl_logs` (`user_id`, `request_datetime_utc`, `request_method`); +CREATE INDEX `tbl_logs_index_1` ON `myems_user_db`.`tbl_logs` (`user_uuid`, `request_datetime_utc`, `request_method`); --- ---------------------------------------------------------------------------------- +-- --------------------------------------------------------------------------------------------------------------------- -- Table `myems_user_db`.`tbl_notifications` --- ---------------------------------------------------------------------------------- +-- --------------------------------------------------------------------------------------------------------------------- DROP TABLE IF EXISTS `myems_user_db`.`tbl_notifications` ; CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_notifications` ( diff --git a/database/upgrade/upgrade1.2.2.sql b/database/upgrade/upgrade1.2.2.sql index e93bdd63..1e1240ca 100644 --- a/database/upgrade/upgrade1.2.2.sql +++ b/database/upgrade/upgrade1.2.2.sql @@ -2,14 +2,14 @@ DROP TABLE IF EXISTS `myems_user_db`.`tbl_logs` ; CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_logs` ( `id` BIGINT NOT NULL AUTO_INCREMENT, - `user_id` BIGINT NOT NULL, + `user_uuid` CHAR(36) NOT NULL, `request_datetime_utc` DATETIME NOT NULL, `request_method` VARCHAR(256) NOT NULL, `resource_type` VARCHAR(256) NOT NULL, `resource_id` BIGINT NULL, `request_body` JSON NULL, PRIMARY KEY (`id`)); -CREATE INDEX `tbl_logs_index_1` ON `myems_user_db`.`tbl_logs` (`user_id`, `request_datetime_utc`, `request_method`); +CREATE INDEX `tbl_logs_index_1` ON `myems_user_db`.`tbl_logs` (`user_uuid`, `request_datetime_utc`, `request_method`); -- UPDATE VERSION NUMBER UPDATE myems_system_db.tbl_versions SET version='1.2.2', release_date='2021-08-28' WHERE id=1; diff --git a/myems-api/core/equipment.py b/myems-api/core/equipment.py index ec62daac..42f39db3 100644 --- a/myems-api/core/equipment.py +++ b/myems-api/core/equipment.py @@ -3,23 +3,20 @@ import simplejson as json import mysql.connector import config import uuid -from log import decorator_record_action_log +from core.userlogger import user_logger class EquipmentCollection: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentCollection""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log def on_get(req, resp): cnx = mysql.connector.connect(**config.myems_system_db) cursor = cnx.cursor(dictionary=True) @@ -62,7 +59,7 @@ class EquipmentCollection: resp.body = json.dumps(result) @staticmethod - @decorator_record_action_log + @user_logger def on_post(req, resp): """Handles POST requests""" try: @@ -150,18 +147,15 @@ class EquipmentCollection: class EquipmentItem: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentItem""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log def on_get(req, resp, id_): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -208,7 +202,7 @@ class EquipmentItem: resp.body = json.dumps(meta_result) @staticmethod - @decorator_record_action_log + @user_logger def on_delete(req, resp, id_): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -295,7 +289,7 @@ class EquipmentItem: resp.status = falcon.HTTP_204 @staticmethod - @decorator_record_action_log + @user_logger def on_put(req, resp, id_): """Handles PUT requests""" if not id_.isdigit() or int(id_) <= 0: @@ -392,7 +386,7 @@ class EquipmentItem: # Clone an Equipment @staticmethod - @decorator_record_action_log + @user_logger def on_post(req, resp, id_): """Handles PUT requests""" if not id_.isdigit() or int(id_) <= 0: @@ -538,18 +532,15 @@ class EquipmentItem: class EquipmentParameterCollection: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentParameterCollection""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log def on_get(req, resp, id_): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -672,7 +663,7 @@ class EquipmentParameterCollection: resp.body = json.dumps(result) @staticmethod - @decorator_record_action_log + @user_logger def on_post(req, resp, id_): """Handles POST requests""" if not id_.isdigit() or int(id_) <= 0: @@ -856,18 +847,16 @@ class EquipmentParameterCollection: class EquipmentParameterItem: @staticmethod - @decorator_record_action_log + @user_logger def __init__(): """Initializes EquipmentParameterItem""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_, pid): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log def on_get(req, resp, id_, pid): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -984,7 +973,7 @@ class EquipmentParameterItem: resp.body = json.dumps(meta_result) @staticmethod - @decorator_record_action_log + @user_logger def on_delete(req, resp, id_, pid): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -1031,7 +1020,7 @@ class EquipmentParameterItem: resp.status = falcon.HTTP_204 @staticmethod - @decorator_record_action_log + @user_logger def on_put(req, resp, id_, pid): """Handles POST requests""" if not id_.isdigit() or int(id_) <= 0: @@ -1233,18 +1222,15 @@ class EquipmentParameterItem: class EquipmentMeterCollection: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentMeterCollection""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log def on_get(req, resp, id_): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -1293,7 +1279,7 @@ class EquipmentMeterCollection: resp.body = json.dumps(result) @staticmethod - @decorator_record_action_log + @user_logger def on_post(req, resp, id_): """Handles POST requests""" try: @@ -1365,18 +1351,16 @@ class EquipmentMeterCollection: class EquipmentMeterItem: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentMeterItem""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_, mid): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log + @user_logger def on_delete(req, resp, id_, mid): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -1427,18 +1411,15 @@ class EquipmentMeterItem: class EquipmentOfflineMeterCollection: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentOfflineMeterCollection""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log def on_get(req, resp, id_): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -1487,7 +1468,7 @@ class EquipmentOfflineMeterCollection: resp.body = json.dumps(result) @staticmethod - @decorator_record_action_log + @user_logger def on_post(req, resp, id_): """Handles POST requests""" try: @@ -1559,18 +1540,16 @@ class EquipmentOfflineMeterCollection: class EquipmentOfflineMeterItem: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentOfflineMeterItem""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_, mid): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log + @user_logger def on_delete(req, resp, id_, mid): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -1622,18 +1601,15 @@ class EquipmentOfflineMeterItem: class EquipmentVirtualMeterCollection: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentVirtualMeterCollection""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log def on_get(req, resp, id_): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -1682,7 +1658,7 @@ class EquipmentVirtualMeterCollection: resp.body = json.dumps(result) @staticmethod - @decorator_record_action_log + @user_logger def on_post(req, resp, id_): """Handles POST requests""" try: @@ -1754,18 +1730,16 @@ class EquipmentVirtualMeterCollection: class EquipmentVirtualMeterItem: @staticmethod - @decorator_record_action_log def __init__(): """Initializes EquipmentVirtualMeterItem""" pass @staticmethod - @decorator_record_action_log def on_options(req, resp, id_, mid): resp.status = falcon.HTTP_200 @staticmethod - @decorator_record_action_log + @user_logger def on_delete(req, resp, id_, mid): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', diff --git a/myems-api/core/userlogger.py b/myems-api/core/userlogger.py new file mode 100644 index 00000000..0e48a0c8 --- /dev/null +++ b/myems-api/core/userlogger.py @@ -0,0 +1,107 @@ +import os +from functools import wraps +import config +import mysql.connector +from datetime import datetime +import uuid +from gunicorn.http.body import Body +import simplejson as json + + +def write_log(user_uuid, request_method, resource_type, resource_id, request_body): + """ + :param user_uuid: user_uuid + :param request_method: 'POST', 'PUT', 'DELETE' + :param resource_type: class_name + :param resource_id: int + :param request_body: json in raw string + """ + cnx = None + cursor = None + try: + cnx = mysql.connector.connect(**config.myems_user_db) + cursor = cnx.cursor() + add_row = (" INSERT INTO tbl_logs " + " (user_uuid, request_datetime_utc, request_method, resource_type, resource_id, request_body) " + " VALUES (%s, %s, %s, %s, %s , %s) ") + cursor.execute(add_row, (user_uuid, + datetime.utcnow(), + request_method, + resource_type, + resource_id if resource_id else None, + request_body if request_body else None, + )) + cnx.commit() + except Exception as e: + print(str(e)) + finally: + if cnx: + cnx.disconnect() + if cursor: + cursor.close() + + +def user_logger(func): + @wraps(func) + def logger(*args, **kwargs): + qualified_name = func.__qualname__ + class_name = qualified_name.split(".")[0] + func_name = qualified_name.split(".")[1] + + if func_name not in ("on_post", "on_put", "on_delete"): + # do not log for other HTTP Methods + func(*args, **kwargs) + return + req, resp = args + cookies = req.cookies + if cookies is not None and 'user_uuid' in cookies.keys(): + user_uuid = cookies['user_uuid'] + else: + # todo: deal with requests with NULL user_uuid + print('user_logger: user_uuid is NULL') + # do not log for NULL user_uuid + func(*args, **kwargs) + return + + if func_name == "on_post": + try: + file_name = str(uuid.uuid4()) + with open(file_name, "wb") as fw: + reads = req.stream.read() + fw.write(reads) + raw_json = reads.decode('utf-8') + with open(file_name, "rb") as fr: + req.stream = Body(fr) + func(*args, **kwargs) + write_log(user_uuid=user_uuid, request_method='POST', resource_type=class_name, + resource_id=kwargs.get('id_'), request_body=raw_json) + os.remove(file_name) + except Exception as e: + print('user_logger:' + str(e)) + return + elif func_name == "on_put": + try: + file_name = str(uuid.uuid4()) + with open(file_name, "wb") as fw: + reads = req.stream.read() + fw.write(reads) + raw_json = reads.decode('utf-8') + with open(file_name, "rb") as fr: + req.stream = Body(fr) + func(*args, **kwargs) + write_log(user_uuid=user_uuid, request_method="POST", resource_type=class_name, + resource_id=kwargs.get('id_'), request_body=raw_json) + os.remove(file_name) + except Exception as e: + print('user_logger:' + str(e)) + return + elif func_name == "on_delete": + try: + func(*args, **kwargs) + write_log(user_uuid=user_uuid, request_method="DELETE", resource_type=class_name, + resource_id=kwargs.get('id_'), request_body=json.dumps(kwargs)) + except Exception as e: + print('user_logger:' + str(e)) + return + + return logger diff --git a/myems-api/log.py b/myems-api/log.py deleted file mode 100644 index dc3e89e0..00000000 --- a/myems-api/log.py +++ /dev/null @@ -1,170 +0,0 @@ -import os -from functools import wraps -import config -import mysql.connector -from datetime import datetime, timezone -import uuid -from gunicorn.http.body import Body - - -def write_log(user_uuid, action, _class, record_id, record_text): - """ - :param user_uuid: user_uuid - :param action: create, update, delete and others: login, logout, reset password, change password - :param _class: class_name - :param record_id: int - :param record_text: str - """ - now = datetime.utcnow() - cnx = None - cursor = None - try: - cnx = mysql.connector.connect(**config.myems_user_db) - cursor = cnx.cursor() - cursor.execute(" SELECT display_name " - " FROM tbl_users " - " WHERE uuid = %s ", - (user_uuid,)) - row = cursor.fetchone() - user = dict() - if row is not None and len(row) > 0: - user["name"] = row[0] - else: - user["name"] = "visitor" - - cnx = mysql.connector.connect(**config.myems_user_db) - cursor = cnx.cursor() - add_row = (" INSERT INTO tbl_action_logs " - " (user_name, date_time_utc, action, class, record_id, record_text) " - " VALUES (%s, %s, %s, %s, %s , %s) ") - cursor.execute(add_row, (user['name'], - now, - action, - _class, - record_id if record_id else None, - record_text if record_text else None, - )) - cnx.commit() - except Exception as e: - print(str(e)) - finally: - if cnx: - cnx.disconnect() - if cursor: - cursor.close() - - -def judge_admin(user_uuid): - cnx = None - cursor = None - try: - cnx = mysql.connector.connect(**config.myems_user_db) - cursor = cnx.cursor() - cursor.execute(" SELECT is_admin " - " FROM tbl_users " - " WHERE uuid = %s ", - (user_uuid,)) - row = cursor.fetchone() - user = dict() - if row is not None and len(row) > 0: - user["admin"] = True if row[0] == 1 else False - else: - user["admin"] = False - return user["admin"] - except Exception as e: - print(str(e)) - return False - finally: - if cnx: - cnx.disconnect() - if cursor: - cursor.close() - - -def decorator_record_action_log(func): - @wraps(func) - def log_fun(*args, **kwargs): - type_dict = { - "on_post": "create", - "on_put": "update", - "on_delete": "delete", - } - - func_names = func.__qualname__ - class_name = func_names.split(".")[0] - fun_name = func_names.split(".")[1] - - # Judge on_post, on_put, on_delete - if fun_name not in type_dict.keys(): - return func(*args, **kwargs) - - action = type_dict.get(fun_name) - - # Judge is_admin or not - if len(args) > 1: - req, resp = args - cookies = req.cookies - if cookies is not None and 'user_uuid' in cookies.keys(): - user_uuid = cookies['user_uuid'] - is_admin = judge_admin(user_uuid) - else: - user_uuid = None - is_admin = False - else: - return func(*args, **kwargs) - if not is_admin: - return func(*args, **kwargs) - - if class_name == "UserLogin": - action = "login" - elif class_name == "UserLogout": - action = "logout" - elif class_name == "ResetPassword": - action = "reset password" - elif class_name == "ChangePassword": - action = "change password" - else: - pass - - if fun_name == "on_post": - file_name = str(uuid.uuid4()) - with open(file_name, "wb") as fw: - reads = req.stream.read() - fw.write(reads) - raw_json = reads.decode('utf-8') - - with open(file_name, "rb") as fr: - req.stream = Body(fr) - write_log(user_uuid=user_uuid, action=action, _class=class_name, - record_id=None, record_text=raw_json) - func(*args, **kwargs) - os.remove(file_name) - return - - elif fun_name == "on_put": - id_ = kwargs.get('id_') - file_name = str(uuid.uuid4()) - with open(file_name, "wb") as fw: - reads = req.stream.read() - fw.write(reads) - raw_json = reads.decode('utf-8') - - with open(file_name, "rb") as fr: - req.stream = Body(fr) - write_log(user_uuid=user_uuid, action=action, _class=class_name, - record_id=id_, record_text=raw_json) - func(*args, **kwargs) - os.remove(file_name) - return - - elif fun_name == "on_delete": - id_ = kwargs.get('id_') - write_log(user_uuid=user_uuid, action=action, _class=class_name, - record_id=id_, record_text=None) - func(*args, **kwargs) - return - else: - func(*args, **kwargs) - return - - return log_fun