2024-06-26 13:30:47 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import json
|
|
|
|
import re
|
|
|
|
from typing import Union
|
|
|
|
import db
|
|
|
|
import config
|
2024-07-10 08:14:44 +00:00
|
|
|
import appConfig
|
2024-06-26 13:30:47 +00:00
|
|
|
from urllib.parse import parse_qs
|
|
|
|
|
|
|
|
|
2024-07-01 11:44:05 +00:00
|
|
|
VERBOSE_MODE = False
|
2024-07-03 10:43:20 +00:00
|
|
|
APIQUERY_MODE = False
|
2024-07-02 07:23:08 +00:00
|
|
|
NOTIFICATION_COUNT = 1
|
2024-07-01 11:44:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
def set_verbose_mode(enabled: bool):
|
|
|
|
global VERBOSE_MODE
|
|
|
|
VERBOSE_MODE = enabled
|
|
|
|
|
|
|
|
|
2024-07-03 10:43:20 +00:00
|
|
|
def set_apiquery_mode(enabled: bool):
|
|
|
|
global APIQUERY_MODE
|
|
|
|
APIQUERY_MODE = enabled
|
|
|
|
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
def get_notifications() -> list[dict]:
|
|
|
|
return list(db.NOTIFICATION_MESSAGES)
|
|
|
|
|
|
|
|
|
2024-07-09 10:19:20 +00:00
|
|
|
def get_notifications_history() -> dict:
|
2024-07-08 10:18:31 +00:00
|
|
|
return {
|
|
|
|
'history': list(db.NOTIFICATION_HISTORY),
|
|
|
|
'config': {
|
|
|
|
'buffer_resolution_per_minute': db.NOTIFICATION_HISTORY_BUFFER_RESOLUTION_PER_MIN,
|
|
|
|
'buffer_timestamp_min': db.NOTIFICATION_HISTORY_BUFFER_TIMESPAN_MIN,
|
|
|
|
'frequency': db.NOTIFICATION_HISTORY_FREQUENCY,
|
2024-07-09 10:19:20 +00:00
|
|
|
'notification_history_size': db.notification_history_buffer_size,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def get_users_activity() -> dict:
|
|
|
|
return {
|
|
|
|
'activity': {user_id: list(activity) for user_id, activity in db.USER_ACTIVITY.items()},
|
|
|
|
'config': {
|
|
|
|
'timestamp_min': db.USER_ACTIVITY_TIMESPAN_MIN,
|
|
|
|
'buffer_resolution_per_minute': db.USER_ACTIVITY_BUFFER_RESOLUTION_PER_MIN,
|
|
|
|
'frequency': db.USER_ACTIVITY_FREQUENCY,
|
|
|
|
'activity_buffer_size': db.user_activity_buffer_size,
|
2024-07-08 10:18:31 +00:00
|
|
|
},
|
|
|
|
}
|
2024-07-04 17:46:05 +00:00
|
|
|
|
|
|
|
|
2024-07-01 13:10:18 +00:00
|
|
|
def reset_notifications():
|
2024-07-01 13:13:01 +00:00
|
|
|
db.resetNotificationMessage()
|
2024-07-01 13:10:18 +00:00
|
|
|
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
def record_notification(notification: dict):
|
|
|
|
db.NOTIFICATION_MESSAGES.appendleft(notification)
|
|
|
|
|
|
|
|
|
2024-07-04 17:46:05 +00:00
|
|
|
def record_notification_history(message_count: int):
|
|
|
|
db.NOTIFICATION_HISTORY.append(message_count)
|
|
|
|
|
|
|
|
|
2024-07-09 10:19:20 +00:00
|
|
|
def record_user_activity(user_id: int, count: int):
|
|
|
|
db.addUserActivity(user_id, count)
|
|
|
|
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
def get_user_id(data: dict):
|
|
|
|
if 'user_id' in data:
|
|
|
|
return int(data['user_id'])
|
|
|
|
if 'Log' in data:
|
|
|
|
data = data['Log']
|
|
|
|
if 'user_id' in data:
|
|
|
|
return int(data['user_id'])
|
2024-07-12 13:47:57 +00:00
|
|
|
if 'AuditLog' in data:
|
|
|
|
data = data['AuditLog']
|
|
|
|
if 'user_id' in data:
|
|
|
|
return int(data['user_id'])
|
2024-06-26 13:30:47 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_email_id_pair(data: dict):
|
|
|
|
if 'Log' in data:
|
|
|
|
data = data['Log']
|
|
|
|
if 'email' in data and 'user_id' in data:
|
|
|
|
return (int(data['user_id']), data['email'],)
|
|
|
|
return (None, None,)
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_authkey_id_pair(data: dict):
|
|
|
|
authkey_title_regex = r".*API key.*\((\w+)\)"
|
|
|
|
if 'Log' in data:
|
|
|
|
data = data['Log']
|
|
|
|
if 'user_id' in data and 'title' in data :
|
|
|
|
if data['title'].startswith('Successful authentication using API key'):
|
|
|
|
authkey_search = re.search(authkey_title_regex, data['title'], re.IGNORECASE)
|
2024-07-01 11:12:23 +00:00
|
|
|
if authkey_search is not None:
|
|
|
|
authkey = authkey_search.group(1)
|
|
|
|
return (int(data['user_id']), authkey,)
|
2024-06-26 13:30:47 +00:00
|
|
|
return (None, None,)
|
|
|
|
|
|
|
|
|
|
|
|
def is_http_request(data: dict) -> bool:
|
|
|
|
if ('url' in data and
|
|
|
|
'request_method' in data and
|
|
|
|
'response_code' in data and
|
|
|
|
'user_id' in data):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def get_content_type(data: dict) -> Union[None, str]:
|
|
|
|
if 'request' not in data:
|
|
|
|
return None
|
|
|
|
request_content = data['request']
|
|
|
|
content_type, _ = request_content.split('\n\n')
|
|
|
|
return content_type
|
|
|
|
|
|
|
|
|
|
|
|
def clean_form_urlencoded_data(post_body_parsed: dict) -> dict:
|
|
|
|
cleaned = {}
|
|
|
|
for k, v in post_body_parsed.items():
|
|
|
|
if k.startswith('data[') and not k.startswith('data[_'):
|
|
|
|
clean_k = '.'.join([k for k in re.split(r'[\[\]]', k) if k != ''][1:])
|
|
|
|
clean_v = v[0] if type(v) is list and len(v) == 1 else v
|
|
|
|
cleaned[clean_k] = clean_v
|
|
|
|
return cleaned
|
|
|
|
|
|
|
|
|
|
|
|
def get_request_post_body(data: dict) -> dict:
|
|
|
|
if 'request' not in data:
|
|
|
|
return {}
|
|
|
|
request_content = data['request']
|
|
|
|
content_type, post_body = request_content.split('\n\n')
|
|
|
|
if content_type == 'application/json':
|
2024-06-26 13:49:41 +00:00
|
|
|
post_body_parsed = json.loads(post_body) if len(post_body) > 0 else {}
|
2024-06-26 13:30:47 +00:00
|
|
|
return post_body_parsed
|
|
|
|
elif content_type == 'application/x-www-form-urlencoded':
|
|
|
|
post_body_parsed = parse_qs(post_body)
|
|
|
|
post_body_clean = clean_form_urlencoded_data(post_body_parsed)
|
|
|
|
return post_body_clean
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
def is_api_request(data: dict) -> bool:
|
|
|
|
content_type = get_content_type(data)
|
|
|
|
return content_type == 'application/json'
|
|
|
|
|
|
|
|
|
|
|
|
def get_notification_message(data: dict) -> dict:
|
2024-07-02 07:23:08 +00:00
|
|
|
global NOTIFICATION_COUNT
|
|
|
|
id = NOTIFICATION_COUNT
|
|
|
|
NOTIFICATION_COUNT += 1
|
2024-06-26 13:30:47 +00:00
|
|
|
user = db.USER_ID_TO_EMAIL_MAPPING.get(int(data['user_id']), '?')
|
|
|
|
time = data['created'].split(' ')[1].split('.')[0]
|
|
|
|
url = data['url']
|
|
|
|
http_method = data.get('request_method', 'GET')
|
|
|
|
response_code = data.get('response_code', '?')
|
|
|
|
user_agent = data.get('user_agent', '?')
|
|
|
|
_, action = get_scope_action_from_url(url)
|
2024-06-28 15:14:05 +00:00
|
|
|
http_method = 'DELETE' if (http_method == 'POST' or http_method == 'PUT') and action == 'delete' else http_method # small override for UI
|
2024-06-26 13:30:47 +00:00
|
|
|
payload = get_request_post_body(data)
|
|
|
|
return {
|
2024-07-02 07:23:08 +00:00
|
|
|
'id': id,
|
2024-06-26 13:30:47 +00:00
|
|
|
'user': user,
|
|
|
|
'time': time,
|
|
|
|
'url': url,
|
|
|
|
'http_method': http_method,
|
|
|
|
'user_agent': user_agent,
|
|
|
|
'is_api_request': is_api_request(data),
|
|
|
|
'response_code': response_code,
|
|
|
|
'payload': payload,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-06-28 15:14:05 +00:00
|
|
|
def get_scope_action_from_url(url) -> Union[str, None]:
|
2024-06-26 13:30:47 +00:00
|
|
|
split = url.split('/')
|
2024-06-28 15:14:05 +00:00
|
|
|
if len(split) > 2:
|
|
|
|
return (split[1], split[2],)
|
|
|
|
else:
|
|
|
|
return (None, None,)
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
def is_accepted_notification(notification) -> bool:
|
2024-07-01 11:44:05 +00:00
|
|
|
global VERBOSE_MODE
|
|
|
|
|
2024-07-18 12:51:36 +00:00
|
|
|
if notification['user_agent'] == 'SkillAegis': # Ignore message generated from this app
|
2024-06-28 15:14:05 +00:00
|
|
|
return False
|
2024-07-01 11:44:05 +00:00
|
|
|
if VERBOSE_MODE:
|
|
|
|
return True
|
2024-07-03 10:43:20 +00:00
|
|
|
if APIQUERY_MODE and not notification['is_api_request']:
|
|
|
|
return False
|
2024-06-28 15:14:05 +00:00
|
|
|
if '@' not in notification['user']: # Ignore message from system
|
2024-06-26 13:30:47 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
scope, action = get_scope_action_from_url(notification['url'])
|
2024-07-10 08:14:44 +00:00
|
|
|
if scope in appConfig.live_logs_accepted_scope:
|
|
|
|
if appConfig.live_logs_accepted_scope == '*':
|
2024-06-26 13:30:47 +00:00
|
|
|
return True
|
2024-07-10 08:14:44 +00:00
|
|
|
elif action in appConfig.live_logs_accepted_scope[scope]:
|
2024-06-26 13:30:47 +00:00
|
|
|
return True
|
2024-07-09 12:05:40 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def is_accepted_user_activity(notification) -> bool:
|
|
|
|
global VERBOSE_MODE
|
|
|
|
|
2024-07-18 12:51:36 +00:00
|
|
|
if notification['user_agent'] == 'SkillAegis': # Ignore message generated from this app
|
2024-07-09 12:05:40 +00:00
|
|
|
return False
|
|
|
|
if '@' not in notification['user']: # Ignore message from system
|
|
|
|
return False
|
|
|
|
|
|
|
|
scope, action = get_scope_action_from_url(notification['url'])
|
2024-07-10 08:14:44 +00:00
|
|
|
if scope in appConfig.user_activity_accepted_scope:
|
|
|
|
if appConfig.user_activity_accepted_scope == '*':
|
2024-07-09 12:05:40 +00:00
|
|
|
return True
|
2024-07-10 08:14:44 +00:00
|
|
|
elif action in appConfig.user_activity_accepted_scope[scope]:
|
2024-07-09 12:05:40 +00:00
|
|
|
return True
|
2024-06-26 13:30:47 +00:00
|
|
|
return False
|