2024-06-26 13:30:47 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2024-07-09 10:19:20 +00:00
|
|
|
import collections
|
2024-07-02 09:41:17 +00:00
|
|
|
import functools
|
2024-06-26 13:30:47 +00:00
|
|
|
import json
|
|
|
|
import sys
|
2024-07-02 09:41:17 +00:00
|
|
|
import time
|
2024-06-26 13:30:47 +00:00
|
|
|
import zmq
|
|
|
|
import socketio
|
2024-07-02 14:48:55 +00:00
|
|
|
from aiohttp import web
|
|
|
|
import zmq.asyncio
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
import exercise as exercise_model
|
|
|
|
import notification as notification_model
|
|
|
|
import db
|
2024-07-01 13:02:19 +00:00
|
|
|
import config
|
2024-07-02 11:46:15 +00:00
|
|
|
from config import logger
|
2024-07-01 11:12:23 +00:00
|
|
|
import misp_api
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
|
2024-07-04 17:46:05 +00:00
|
|
|
ZMQ_MESSAGE_COUNT_LAST_TIMESPAN = 0
|
2024-07-01 12:54:33 +00:00
|
|
|
ZMQ_MESSAGE_COUNT = 0
|
2024-07-03 11:30:12 +00:00
|
|
|
ZMQ_LAST_TIME = None
|
2024-07-09 10:19:20 +00:00
|
|
|
USER_ACTIVITY = collections.defaultdict(int)
|
2024-07-01 12:54:33 +00:00
|
|
|
|
|
|
|
|
2024-07-02 09:41:17 +00:00
|
|
|
def debounce(debounce_seconds: int = 1):
|
|
|
|
func_last_execution_time = {}
|
|
|
|
def decorator(func):
|
|
|
|
@functools.wraps(func)
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
now = time.time()
|
|
|
|
key = func.__name__
|
|
|
|
if key not in func_last_execution_time:
|
|
|
|
func_last_execution_time[key] = now
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
elif now >= func_last_execution_time[key] + debounce_seconds:
|
|
|
|
func_last_execution_time[key] = now
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
# Initialize ZeroMQ context and subscriber socket
|
2024-07-02 14:48:55 +00:00
|
|
|
context = zmq.asyncio.Context()
|
|
|
|
zsocket = context.socket(zmq.SUB)
|
2024-07-01 13:02:19 +00:00
|
|
|
zmq_url = config.zmq_url
|
2024-06-26 13:30:47 +00:00
|
|
|
zsocket.connect(zmq_url)
|
2024-07-02 14:48:55 +00:00
|
|
|
zsocket.setsockopt_string(zmq.SUBSCRIBE, '')
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Initialize Socket.IO server
|
2024-07-04 12:49:26 +00:00
|
|
|
sio = socketio.AsyncServer(cors_allowed_origins='*', async_mode='aiohttp')
|
2024-07-02 14:48:55 +00:00
|
|
|
app = web.Application()
|
|
|
|
sio.attach(app)
|
|
|
|
|
|
|
|
|
|
|
|
async def index(request):
|
|
|
|
with open('dist/index.html') as f:
|
|
|
|
return web.Response(text=f.read(), content_type='text/html')
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def connect(sid, environ):
|
2024-07-02 11:56:10 +00:00
|
|
|
logger.debug("Client connected: %s", sid)
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def disconnect(sid):
|
2024-07-02 11:56:10 +00:00
|
|
|
logger.debug("Client disconnected: %s", sid)
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def get_exercises(sid):
|
2024-06-26 13:30:47 +00:00
|
|
|
return exercise_model.get_exercises()
|
|
|
|
|
2024-07-01 09:21:01 +00:00
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def get_selected_exercises(sid):
|
2024-07-01 09:21:01 +00:00
|
|
|
return exercise_model.get_selected_exercises()
|
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def change_exercise_selection(sid, payload):
|
2024-07-01 09:21:01 +00:00
|
|
|
return exercise_model.change_exercise_selection(payload['exercise_uuid'], payload['selected'])
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def get_progress(sid):
|
2024-06-26 13:30:47 +00:00
|
|
|
return exercise_model.get_progress()
|
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def get_notifications(sid):
|
2024-06-26 13:30:47 +00:00
|
|
|
return notification_model.get_notifications()
|
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def mark_task_completed(sid, payload):
|
2024-07-01 09:21:01 +00:00
|
|
|
return exercise_model.mark_task_completed(int(payload['user_id']), payload['exercise_uuid'], payload['task_uuid'])
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def mark_task_incomplete(sid, payload):
|
2024-07-01 09:21:01 +00:00
|
|
|
return exercise_model.mark_task_incomplete(int(payload['user_id']), payload['exercise_uuid'], payload['task_uuid'])
|
|
|
|
|
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def reset_all_exercise_progress(sid):
|
2024-07-01 09:21:01 +00:00
|
|
|
return exercise_model.resetAllExerciseProgress()
|
2024-06-26 13:30:47 +00:00
|
|
|
|
2024-07-01 13:10:18 +00:00
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def reset_notifications(sid):
|
2024-07-01 13:10:18 +00:00
|
|
|
return notification_model.reset_notifications()
|
|
|
|
|
2024-07-01 11:12:23 +00:00
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def get_diagnostic(sid):
|
2024-07-03 09:51:44 +00:00
|
|
|
return await getDiagnostic()
|
2024-07-01 11:12:23 +00:00
|
|
|
|
2024-07-09 10:19:20 +00:00
|
|
|
@sio.event
|
|
|
|
async def get_users_activity(sid):
|
|
|
|
return notification_model.get_users_activity()
|
|
|
|
|
2024-07-01 11:44:05 +00:00
|
|
|
@sio.event
|
2024-07-02 14:48:55 +00:00
|
|
|
async def toggle_verbose_mode(sid, payload):
|
2024-07-01 11:44:05 +00:00
|
|
|
return notification_model.set_verbose_mode(payload['verbose'])
|
|
|
|
|
2024-07-03 10:43:20 +00:00
|
|
|
@sio.event
|
|
|
|
async def toggle_apiquery_mode(sid, payload):
|
|
|
|
return notification_model.set_apiquery_mode(payload['apiquery'])
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
@sio.on('*')
|
2024-07-02 14:48:55 +00:00
|
|
|
async def any_event(event, sid, data={}):
|
2024-07-02 11:56:10 +00:00
|
|
|
logger.info('>> Unhandled event %s', event)
|
2024-06-26 13:30:47 +00:00
|
|
|
|
2024-07-02 14:48:55 +00:00
|
|
|
async def handleMessage(topic, s, message):
|
2024-07-04 17:46:05 +00:00
|
|
|
global ZMQ_MESSAGE_COUNT_LAST_TIMESPAN
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
data = json.loads(message)
|
|
|
|
|
|
|
|
if topic == 'misp_json_audit':
|
|
|
|
user_id, email = notification_model.get_user_email_id_pair(data)
|
2024-07-09 14:28:15 +00:00
|
|
|
if user_id is not None and user_id != 0 and '@' in email:
|
2024-06-26 13:30:47 +00:00
|
|
|
if user_id not in db.USER_ID_TO_EMAIL_MAPPING:
|
|
|
|
db.USER_ID_TO_EMAIL_MAPPING[user_id] = email
|
2024-07-02 14:48:55 +00:00
|
|
|
await sio.emit('new_user', email)
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
user_id, authkey = notification_model.get_user_authkey_id_pair(data)
|
2024-07-09 14:28:15 +00:00
|
|
|
if user_id is not None and user_id != 0:
|
2024-06-26 13:30:47 +00:00
|
|
|
if authkey not in db.USER_ID_TO_AUTHKEY_MAPPING:
|
|
|
|
db.USER_ID_TO_AUTHKEY_MAPPING[user_id] = authkey
|
|
|
|
return
|
|
|
|
|
|
|
|
if notification_model.is_http_request(data):
|
|
|
|
notification = notification_model.get_notification_message(data)
|
|
|
|
if notification_model.is_accepted_notification(notification):
|
|
|
|
notification_model.record_notification(notification)
|
2024-07-04 17:46:05 +00:00
|
|
|
ZMQ_MESSAGE_COUNT_LAST_TIMESPAN += 1
|
2024-07-09 12:05:40 +00:00
|
|
|
await sio.emit('notification', notification)
|
|
|
|
if notification_model.is_accepted_user_activity(notification):
|
2024-07-09 10:19:20 +00:00
|
|
|
user_id = notification_model.get_user_id(data)
|
|
|
|
if user_id is not None:
|
|
|
|
USER_ACTIVITY[user_id] += 1
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
user_id = notification_model.get_user_id(data)
|
|
|
|
if user_id is not None:
|
|
|
|
if exercise_model.is_accepted_query(data):
|
2024-07-04 12:49:26 +00:00
|
|
|
context = get_context(topic, user_id, data)
|
|
|
|
succeeded_once = await exercise_model.check_active_tasks(user_id, data, context)
|
2024-06-26 13:30:47 +00:00
|
|
|
if succeeded_once:
|
2024-07-03 09:51:44 +00:00
|
|
|
await sendRefreshScore()
|
2024-07-02 09:41:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
@debounce(debounce_seconds=1)
|
2024-07-02 14:48:55 +00:00
|
|
|
async def sendRefreshScore():
|
|
|
|
await sio.emit('refresh_score')
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
|
2024-07-04 12:49:26 +00:00
|
|
|
def get_context(topic: str, user_id: int, data: dict) -> dict:
|
2024-07-04 09:21:10 +00:00
|
|
|
context = {
|
2024-07-04 12:49:26 +00:00
|
|
|
'zmq_topic': topic,
|
2024-07-04 09:21:10 +00:00
|
|
|
'user_id': user_id,
|
2024-07-04 12:49:26 +00:00
|
|
|
'user_email': db.USER_ID_TO_EMAIL_MAPPING.get(user_id, None),
|
|
|
|
'user_authkey': db.USER_ID_TO_AUTHKEY_MAPPING.get(user_id, None),
|
2024-07-04 09:21:10 +00:00
|
|
|
}
|
2024-06-26 13:30:47 +00:00
|
|
|
if 'Log' in data:
|
|
|
|
if 'request_is_rest' in data['Log']:
|
|
|
|
context['request_is_rest'] = data['Log']['request_is_rest']
|
|
|
|
elif 'authkey_id' in data:
|
|
|
|
context['request_is_rest'] = True
|
|
|
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2024-07-03 09:51:44 +00:00
|
|
|
async def getDiagnostic() -> dict:
|
2024-07-01 12:54:33 +00:00
|
|
|
global ZMQ_MESSAGE_COUNT
|
|
|
|
|
2024-07-01 12:31:29 +00:00
|
|
|
diagnostic = {}
|
2024-07-03 09:51:44 +00:00
|
|
|
misp_version = await misp_api.getVersion()
|
2024-07-01 12:31:29 +00:00
|
|
|
if misp_version is None:
|
|
|
|
diagnostic['online'] = False
|
|
|
|
return diagnostic
|
|
|
|
diagnostic['version'] = misp_version
|
2024-07-03 09:51:44 +00:00
|
|
|
misp_settings = await misp_api.getSettings()
|
2024-07-01 12:31:29 +00:00
|
|
|
diagnostic['settings'] = misp_settings
|
2024-07-01 12:54:33 +00:00
|
|
|
diagnostic['zmq_message_count'] = ZMQ_MESSAGE_COUNT
|
2024-07-01 12:31:29 +00:00
|
|
|
return diagnostic
|
2024-07-01 11:12:23 +00:00
|
|
|
|
|
|
|
|
2024-07-04 17:46:05 +00:00
|
|
|
async def notification_history():
|
|
|
|
global ZMQ_MESSAGE_COUNT_LAST_TIMESPAN
|
|
|
|
while True:
|
|
|
|
await sio.sleep(db.NOTIFICATION_HISTORY_FREQUENCY)
|
|
|
|
notification_model.record_notification_history(ZMQ_MESSAGE_COUNT_LAST_TIMESPAN)
|
|
|
|
ZMQ_MESSAGE_COUNT_LAST_TIMESPAN = 0
|
|
|
|
payload = notification_model.get_notifications_history()
|
|
|
|
await sio.emit('update_notification_history', payload)
|
|
|
|
|
|
|
|
|
2024-07-09 10:19:20 +00:00
|
|
|
async def record_users_activity():
|
|
|
|
global USER_ACTIVITY
|
|
|
|
|
|
|
|
while True:
|
|
|
|
await sio.sleep(db.USER_ACTIVITY_FREQUENCY)
|
|
|
|
for user_id, activity in USER_ACTIVITY.items():
|
|
|
|
notification_model.record_user_activity(user_id, activity)
|
|
|
|
USER_ACTIVITY[user_id] = 0
|
|
|
|
payload = notification_model.get_users_activity()
|
|
|
|
await sio.emit('update_users_activity', payload)
|
|
|
|
|
|
|
|
|
2024-07-03 11:30:12 +00:00
|
|
|
async def keepalive():
|
|
|
|
global ZMQ_LAST_TIME
|
|
|
|
while True:
|
|
|
|
await sio.sleep(5)
|
|
|
|
payload = {
|
|
|
|
'zmq_last_time': ZMQ_LAST_TIME,
|
|
|
|
}
|
|
|
|
await sio.emit('keep_alive', payload)
|
|
|
|
|
|
|
|
|
2024-07-08 08:21:59 +00:00
|
|
|
async def backup_exercises_progress():
|
|
|
|
while True:
|
|
|
|
await sio.sleep(5)
|
|
|
|
exercise_model.backup_exercises_progress()
|
|
|
|
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
# Function to forward zmq messages to Socket.IO
|
2024-07-02 14:48:55 +00:00
|
|
|
async def forward_zmq_to_socketio():
|
2024-07-03 11:30:12 +00:00
|
|
|
global ZMQ_MESSAGE_COUNT, ZMQ_LAST_TIME
|
2024-07-01 12:54:33 +00:00
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
while True:
|
2024-07-02 14:48:55 +00:00
|
|
|
message = await zsocket.recv_string()
|
2024-06-26 13:30:47 +00:00
|
|
|
topic, s, m = message.partition(" ")
|
|
|
|
try:
|
2024-07-01 12:54:33 +00:00
|
|
|
ZMQ_MESSAGE_COUNT += 1
|
2024-07-03 11:30:12 +00:00
|
|
|
ZMQ_LAST_TIME = time.time()
|
2024-07-09 12:38:23 +00:00
|
|
|
await handleMessage(topic, s, m)
|
2024-06-26 13:30:47 +00:00
|
|
|
except Exception as e:
|
2024-07-09 12:38:23 +00:00
|
|
|
print(e)
|
2024-07-02 11:56:10 +00:00
|
|
|
logger.error('Error handling message %s', e)
|
2024-06-26 13:30:47 +00:00
|
|
|
|
|
|
|
|
2024-07-02 14:48:55 +00:00
|
|
|
async def init_app():
|
|
|
|
sio.start_background_task(forward_zmq_to_socketio)
|
2024-07-03 11:30:12 +00:00
|
|
|
sio.start_background_task(keepalive)
|
2024-07-04 17:46:05 +00:00
|
|
|
sio.start_background_task(notification_history)
|
2024-07-09 10:19:20 +00:00
|
|
|
sio.start_background_task(record_users_activity)
|
2024-07-08 08:21:59 +00:00
|
|
|
sio.start_background_task(backup_exercises_progress)
|
2024-07-02 14:48:55 +00:00
|
|
|
return app
|
|
|
|
|
|
|
|
|
|
|
|
app.router.add_static('/assets', 'dist/assets')
|
|
|
|
app.router.add_get('/', index)
|
|
|
|
|
2024-06-26 13:30:47 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
exercises_loaded = exercise_model.load_exercises()
|
|
|
|
if not exercises_loaded:
|
2024-07-02 11:46:15 +00:00
|
|
|
logger.critical('Could not load exercises')
|
2024-06-26 13:30:47 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
2024-07-08 08:21:59 +00:00
|
|
|
exercise_model.restore_exercices_progress()
|
|
|
|
|
2024-07-02 14:48:55 +00:00
|
|
|
web.run_app(init_app(), host=config.server_host, port=config.server_port)
|