From 3565d0dbccdfef069b09d9dc37ca5cebb25a4389 Mon Sep 17 00:00:00 2001 From: hyh123a Date: Wed, 25 Aug 2021 15:13:34 +0800 Subject: [PATCH 1/6] add the log function and test in equipments --- myems-api/core/equipment.py | 41 +++++++++++ myems-api/log.py | 140 ++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 myems-api/log.py diff --git a/myems-api/core/equipment.py b/myems-api/core/equipment.py index 84edc4d5..ae938394 100644 --- a/myems-api/core/equipment.py +++ b/myems-api/core/equipment.py @@ -3,18 +3,22 @@ import simplejson as json import mysql.connector import config import uuid +from log import decorator_record_action_log class EquipmentCollection: @staticmethod + @decorator_record_action_log def __init__(): 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) @@ -57,6 +61,7 @@ class EquipmentCollection: resp.body = json.dumps(result) @staticmethod + @decorator_record_action_log def on_post(req, resp): """Handles POST requests""" try: @@ -144,14 +149,17 @@ class EquipmentCollection: class EquipmentItem: @staticmethod + @decorator_record_action_log def __init__(): 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', @@ -198,6 +206,7 @@ class EquipmentItem: resp.body = json.dumps(meta_result) @staticmethod + @decorator_record_action_log def on_delete(req, resp, id_): if not id_.isdigit() or int(id_) <= 0: raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', @@ -284,6 +293,7 @@ class EquipmentItem: resp.status = falcon.HTTP_204 @staticmethod + @decorator_record_action_log def on_put(req, resp, id_): """Handles PUT requests""" if not id_.isdigit() or int(id_) <= 0: @@ -380,6 +390,7 @@ class EquipmentItem: # Clone an Equipment @staticmethod + @decorator_record_action_log def on_post(req, resp, id_): """Handles PUT requests""" if not id_.isdigit() or int(id_) <= 0: @@ -525,14 +536,17 @@ class EquipmentItem: class EquipmentParameterCollection: @staticmethod + @decorator_record_action_log def __init__(): 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', @@ -655,6 +669,7 @@ class EquipmentParameterCollection: resp.body = json.dumps(result) @staticmethod + @decorator_record_action_log def on_post(req, resp, id_): """Handles POST requests""" if not id_.isdigit() or int(id_) <= 0: @@ -838,14 +853,17 @@ class EquipmentParameterCollection: class EquipmentParameterItem: @staticmethod + @decorator_record_action_log def __init__(): 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', @@ -962,6 +980,7 @@ class EquipmentParameterItem: resp.body = json.dumps(meta_result) @staticmethod + @decorator_record_action_log 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', @@ -1008,6 +1027,7 @@ class EquipmentParameterItem: resp.status = falcon.HTTP_204 @staticmethod + @decorator_record_action_log def on_put(req, resp, id_, pid): """Handles POST requests""" if not id_.isdigit() or int(id_) <= 0: @@ -1209,14 +1229,17 @@ class EquipmentParameterItem: class EquipmentMeterCollection: @staticmethod + @decorator_record_action_log def __init__(): 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', @@ -1265,6 +1288,7 @@ class EquipmentMeterCollection: resp.body = json.dumps(result) @staticmethod + @decorator_record_action_log def on_post(req, resp, id_): """Handles POST requests""" try: @@ -1336,14 +1360,17 @@ class EquipmentMeterCollection: class EquipmentMeterItem: @staticmethod + @decorator_record_action_log def __init__(): pass @staticmethod + @decorator_record_action_log def on_options(req, resp, id_, mid): resp.status = falcon.HTTP_200 @staticmethod + @decorator_record_action_log 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', @@ -1394,14 +1421,17 @@ class EquipmentMeterItem: class EquipmentOfflineMeterCollection: @staticmethod + @decorator_record_action_log def __init__(): 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', @@ -1450,6 +1480,7 @@ class EquipmentOfflineMeterCollection: resp.body = json.dumps(result) @staticmethod + @decorator_record_action_log def on_post(req, resp, id_): """Handles POST requests""" try: @@ -1521,14 +1552,17 @@ class EquipmentOfflineMeterCollection: class EquipmentOfflineMeterItem: @staticmethod + @decorator_record_action_log def __init__(): pass @staticmethod + @decorator_record_action_log def on_options(req, resp, id_, mid): resp.status = falcon.HTTP_200 @staticmethod + @decorator_record_action_log 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', @@ -1580,14 +1614,17 @@ class EquipmentOfflineMeterItem: class EquipmentVirtualMeterCollection: @staticmethod + @decorator_record_action_log def __init__(): 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', @@ -1636,6 +1673,7 @@ class EquipmentVirtualMeterCollection: resp.body = json.dumps(result) @staticmethod + @decorator_record_action_log def on_post(req, resp, id_): """Handles POST requests""" try: @@ -1707,14 +1745,17 @@ class EquipmentVirtualMeterCollection: class EquipmentVirtualMeterItem: @staticmethod + @decorator_record_action_log def __init__(): pass @staticmethod + @decorator_record_action_log def on_options(req, resp, id_, mid): resp.status = falcon.HTTP_200 @staticmethod + @decorator_record_action_log 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/log.py b/myems-api/log.py new file mode 100644 index 00000000..383db162 --- /dev/null +++ b/myems-api/log.py @@ -0,0 +1,140 @@ +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.now() + 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, 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)) + if cnx: + cnx.disconnect() + if cursor: + cursor.close() + 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] + if fun_name not in type_dict.keys(): + return func(*args, **kwargs) + + 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'] + else: + user_uuid = None + else: + return func(*args, **kwargs) + + action = type_dict.get(fun_name) + + 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 From cb0ccfddc08934b76645d9551847f5273134fca3 Mon Sep 17 00:00:00 2001 From: hyh123a Date: Wed, 25 Aug 2021 15:14:25 +0800 Subject: [PATCH 2/6] add the tbl_action_logs --- database/myems_user_db.sql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/database/myems_user_db.sql b/database/myems_user_db.sql index 92a477cb..a65c6799 100644 --- a/database/myems_user_db.sql +++ b/database/myems_user_db.sql @@ -87,3 +87,20 @@ CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_notifications` ( `url` VARCHAR(128), PRIMARY KEY (`id`)); CREATE INDEX `tbl_notifications_index_1` ON `myems_user_db`.`tbl_notifications` (`user_id`, `created_datetime_utc`, `status`); + + +-- --------------------------------------------------------------------------------------------------------------------- +-- Table `myems_user_db`.`tbl_action_logs` +-- --------------------------------------------------------------------------------------------------------------------- +DROP TABLE IF EXISTS `myems_user_db`.`tbl_action_logs` ; + +CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_action_logs` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_name` VARCHAR(256) NOT NULL, + `date_time` DATETIME NOT NULL, + `action` VARCHAR(256) NOT NULL, + `class` VARCHAR(256) NOT NULL, + `record_id` BIGINT NULL, + `record_text` JSON NULL, + PRIMARY KEY (`id`)); +CREATE INDEX `tbl_action_logs_index_1` ON `myems_user_db`.`tbl_action_logs` (`user_name`, `date_time`, `action`); From 329b5f732b49bff9e53b09df6db496356b13eee2 Mon Sep 17 00:00:00 2001 From: hyh123a Date: Wed, 25 Aug 2021 15:49:18 +0800 Subject: [PATCH 3/6] add the judge for admin --- myems-api/log.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/myems-api/log.py b/myems-api/log.py index 383db162..d89903c4 100644 --- a/myems-api/log.py +++ b/myems-api/log.py @@ -58,6 +58,37 @@ def write_log(user_uuid, action, _class, record_id, record_text): 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)) + if cnx: + cnx.disconnect() + if cursor: + cursor.close() + return False + finally: + if cnx: + cnx.disconnect() + if cursor: + cursor.close() + + def decorator_record_action_log(func): @wraps(func) def log_fun(*args, **kwargs): @@ -70,20 +101,27 @@ def decorator_record_action_log(func): 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) - - action = type_dict.get(fun_name) + if not is_admin: + return func(*args, **kwargs) if class_name == "UserLogin": action = "login" From 279ebde9f96624ade590ab1cc9f9250d33d9167d Mon Sep 17 00:00:00 2001 From: hyh123a Date: Thu, 26 Aug 2021 10:02:30 +0800 Subject: [PATCH 4/6] modify the date_time to utc --- database/myems_user_db.sql | 4 ++-- myems-api/log.py | 12 ++---------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/database/myems_user_db.sql b/database/myems_user_db.sql index a65c6799..c23aacb2 100644 --- a/database/myems_user_db.sql +++ b/database/myems_user_db.sql @@ -97,10 +97,10 @@ DROP TABLE IF EXISTS `myems_user_db`.`tbl_action_logs` ; CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_action_logs` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `user_name` VARCHAR(256) NOT NULL, - `date_time` DATETIME NOT NULL, + `date_time_utc` DATETIME NOT NULL, `action` VARCHAR(256) NOT NULL, `class` VARCHAR(256) NOT NULL, `record_id` BIGINT NULL, `record_text` JSON NULL, PRIMARY KEY (`id`)); -CREATE INDEX `tbl_action_logs_index_1` ON `myems_user_db`.`tbl_action_logs` (`user_name`, `date_time`, `action`); +CREATE INDEX `tbl_action_logs_index_1` ON `myems_user_db`.`tbl_action_logs` (`user_name`, `date_time_utc`, `action`); diff --git a/myems-api/log.py b/myems-api/log.py index d89903c4..dc3e89e0 100644 --- a/myems-api/log.py +++ b/myems-api/log.py @@ -15,7 +15,7 @@ def write_log(user_uuid, action, _class, record_id, record_text): :param record_id: int :param record_text: str """ - now = datetime.now() + now = datetime.utcnow() cnx = None cursor = None try: @@ -35,7 +35,7 @@ def write_log(user_uuid, action, _class, record_id, record_text): cnx = mysql.connector.connect(**config.myems_user_db) cursor = cnx.cursor() add_row = (" INSERT INTO tbl_action_logs " - " (user_name, date_time, action, class, record_id, record_text) " + " (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, @@ -47,10 +47,6 @@ def write_log(user_uuid, action, _class, record_id, record_text): cnx.commit() except Exception as e: print(str(e)) - if cnx: - cnx.disconnect() - if cursor: - cursor.close() finally: if cnx: cnx.disconnect() @@ -77,10 +73,6 @@ def judge_admin(user_uuid): return user["admin"] except Exception as e: print(str(e)) - if cnx: - cnx.disconnect() - if cursor: - cursor.close() return False finally: if cnx: From 8cb724ffd70626edf7b6a953938d535e33ef30e4 Mon Sep 17 00:00:00 2001 From: "13621160019@163.com" <13621160019@163.com> Date: Fri, 27 Aug 2021 12:42:12 +0800 Subject: [PATCH 5/6] updated myems_user_db.tbl_logs in database --- database/myems_system_db.sql | 2 +- database/myems_user_db.sql | 26 ++++++-------------------- database/upgrade/upgrade1.2.2.sql | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 database/upgrade/upgrade1.2.2.sql diff --git a/database/myems_system_db.sql b/database/myems_system_db.sql index 9244d43e..3d54cd70 100644 --- a/database/myems_system_db.sql +++ b/database/myems_system_db.sql @@ -1243,6 +1243,6 @@ USE `myems_system_db`; INSERT INTO `myems_system_db`.`tbl_versions` (`id`, `version`, `release_date`) VALUES -(1, '1.2.1', '2021-08-19'); +(1, '1.2.2', '2021-08-28'); COMMIT; diff --git a/database/myems_user_db.sql b/database/myems_user_db.sql index c23aacb2..ac1b26a6 100644 --- a/database/myems_user_db.sql +++ b/database/myems_user_db.sql @@ -67,10 +67,13 @@ 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, - `utc_date_time` DATETIME NOT NULL, - `activity` VARCHAR(256) 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`); -- ---------------------------------------------------------------------------------- -- Table `myems_user_db`.`tbl_notifications` @@ -87,20 +90,3 @@ CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_notifications` ( `url` VARCHAR(128), PRIMARY KEY (`id`)); CREATE INDEX `tbl_notifications_index_1` ON `myems_user_db`.`tbl_notifications` (`user_id`, `created_datetime_utc`, `status`); - - --- --------------------------------------------------------------------------------------------------------------------- --- Table `myems_user_db`.`tbl_action_logs` --- --------------------------------------------------------------------------------------------------------------------- -DROP TABLE IF EXISTS `myems_user_db`.`tbl_action_logs` ; - -CREATE TABLE IF NOT EXISTS `myems_user_db`.`tbl_action_logs` ( - `id` BIGINT NOT NULL AUTO_INCREMENT, - `user_name` VARCHAR(256) NOT NULL, - `date_time_utc` DATETIME NOT NULL, - `action` VARCHAR(256) NOT NULL, - `class` VARCHAR(256) NOT NULL, - `record_id` BIGINT NULL, - `record_text` JSON NULL, - PRIMARY KEY (`id`)); -CREATE INDEX `tbl_action_logs_index_1` ON `myems_user_db`.`tbl_action_logs` (`user_name`, `date_time_utc`, `action`); diff --git a/database/upgrade/upgrade1.2.2.sql b/database/upgrade/upgrade1.2.2.sql new file mode 100644 index 00000000..e93bdd63 --- /dev/null +++ b/database/upgrade/upgrade1.2.2.sql @@ -0,0 +1,15 @@ +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, + `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`); + +-- UPDATE VERSION NUMBER +UPDATE myems_system_db.tbl_versions SET version='1.2.2', release_date='2021-08-28' WHERE id=1; From 9b5b9a38d74cfa3531410a1e7e4a6166c4a90802 Mon Sep 17 00:00:00 2001 From: "13621160019@163.com" <13621160019@163.com> Date: Fri, 27 Aug 2021 17:21:01 +0800 Subject: [PATCH 6/6] updated userlogger in API --- database/myems_user_db.sql | 8 +- database/upgrade/upgrade1.2.2.sql | 4 +- myems-api/core/equipment.py | 56 +++------- myems-api/core/userlogger.py | 107 +++++++++++++++++++ myems-api/log.py | 170 ------------------------------ 5 files changed, 128 insertions(+), 217 deletions(-) create mode 100644 myems-api/core/userlogger.py delete mode 100644 myems-api/log.py 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