From 9c62099f325defb36763deb79b8f3dcf309e2bb3 Mon Sep 17 00:00:00 2001 From: luukas Date: Mon, 1 Mar 2021 02:54:20 +0200 Subject: [PATCH] Add authentication to WS, notify user when an activity log gets logged, and more --- app/classes/shared/models.py | 17 ++++++++++++-- app/classes/shared/tasks.py | 6 +---- app/classes/web/server_handler.py | 2 +- app/classes/web/tornado.py | 4 ++-- app/classes/web/websocket_handler.py | 35 +++++++++++++++++++++++++--- app/classes/web/websocket_helper.py | 11 +++++---- app/frontend/templates/base.html | 8 ++++++- 7 files changed, 65 insertions(+), 18 deletions(-) diff --git a/app/classes/shared/models.py b/app/classes/shared/models.py index 4b3bcb14..b875f89e 100644 --- a/app/classes/shared/models.py +++ b/app/classes/shared/models.py @@ -6,6 +6,7 @@ import datetime from app.classes.shared.helpers import helper from app.classes.shared.console import console from app.classes.minecraft.server_props import ServerProps +from app.classes.web.websocket_helper import websocket_helper logger = logging.getLogger(__name__) @@ -254,14 +255,14 @@ class db_shortcuts: def get_server_friendly_name(self, server_id): server_data = self.get_server_data_by_id(server_id) - friendly_name = "{}-{}".format(server_data.get('server_id', 0), server_data.get('server_name', None)) + friendly_name = "{} with ID: {}".format(server_data.get('server_name', None), server_data.get('server_id', 0)) return friendly_name def send_command(self, user_id, server_id, remote_ip, command): server_name = self.get_server_friendly_name(server_id) - self.add_to_audit_log(user_id, "Issued Command {} for Server: {}".format(command, server_name), + self.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name), server_id, remote_ip) Commands.insert({ @@ -294,6 +295,8 @@ class db_shortcuts: audit_msg = "{} {}".format(str(user_data.username).capitalize(), log_msg) + websocket_helper.broadcast('notification', audit_msg) + Audit_Log.insert({ Audit_Log.user_name: user_data.username, Audit_Log.user_id: user_id, @@ -301,6 +304,16 @@ class db_shortcuts: Audit_Log.log_msg: audit_msg, Audit_Log.source_ip: source_ip }).execute() + + @staticmethod + def add_to_audit_log_raw(user_name, user_id, server_id, log_msg, source_ip): + Audit_Log.insert({ + Audit_Log.user_name: user_name, + Audit_Log.user_id: user_id, + Audit_Log.server_id: server_id, + Audit_Log.log_msg: log_msg, + Audit_Log.source_ip: source_ip + }).execute() diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index 2e33722f..5b77d6ea 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -141,8 +141,7 @@ class TasksManager: def realtime(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - - console.debug('realtime start') + host_stats = db_helper.get_latest_hosts_stats() while True: @@ -153,11 +152,9 @@ class TasksManager: db_helper.get_latest_hosts_stats().get('mem_percent'): # Stats are different - console.debug('realtime 1') host_stats = db_helper.get_latest_hosts_stats() if len(websocket_helper.clients) > 0: # There are clients - console.debug('realtime 11') websocket_helper.broadcast('update_host_stats', { 'cpu_usage': host_stats.get('cpu_usage'), 'cpu_cores': host_stats.get('cpu_cores'), @@ -169,7 +166,6 @@ class TasksManager: time.sleep(4) else: # Stats are same - console.debug('realtime 0') time.sleep(8) diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index 66cdc059..17d8758b 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -113,7 +113,7 @@ class ServerHandler(BaseHandler): if new_server_id: db_helper.add_to_audit_log(user_data['user_id'], - "Created server {} named {}".format(server, server_name), + "created a {} {} server named \"{}\"".format(server_parts[1], str(server_parts[0]).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival" new_server_id, self.get_remote_ip()) else: diff --git a/app/classes/web/tornado.py b/app/classes/web/tornado.py index 7867cad4..866d0a10 100644 --- a/app/classes/web/tornado.py +++ b/app/classes/web/tornado.py @@ -24,7 +24,7 @@ try: from app.classes.web.server_handler import ServerHandler from app.classes.web.ajax_handler import AjaxHandler from app.classes.web.api_handler import ServersStats, NodeStats - from app.classes.web.websocket_handler import WebSocketHandler + from app.classes.web.websocket_handler import SocketHandler except ModuleNotFoundError as e: logger.critical("Import Error: Unable to load {} module".format(e, e.name)) @@ -126,7 +126,7 @@ class webserver: (r'/ajax/(.*)', AjaxHandler), (r'/api/stats/servers', ServersStats), (r'/api/stats/node', NodeStats), - (r'/ws', WebSocketHandler), + (r'/ws', SocketHandler), ] app = tornado.web.Application( diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py index 3d6ed777..5abd1684 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -2,15 +2,44 @@ import json import tornado.websocket from app.classes.shared.console import console +from app.classes.shared.models import Users, db_helper from app.classes.web.websocket_helper import websocket_helper -class WebSocketHandler(tornado.websocket.WebSocketHandler): +class SocketHandler(tornado.websocket.WebSocketHandler): + + def get_remote_ip(self): + remote_ip = self.request.headers.get("X-Real-IP") or \ + self.request.headers.get("X-Forwarded-For") or \ + self.request.remote_ip + return remote_ip + + def check_auth(self): + user_data_cookie_raw = self.get_secure_cookie('user_data') + + if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'): + user_data_cookie = user_data_cookie_raw.decode('utf-8') + user_id = json.loads(user_data_cookie)['user_id'] + query = Users.select().where(Users.user_id == user_id) + if query.exists(): + return True + return False + def open(self): + if self.check_auth(): + self.handle() + else: + websocket_helper.send_message(self, 'notification', 'Not authenticated for WebSocket connection') + self.close() + db_helper.add_to_audit_log_raw('unknown', 0, 0, 'Someone tried to connect via WebSocket without proper authentication', self.get_remote_ip()) + websocket_helper.broadcast('notification', 'Someone tried to connect via WebSocket without proper authentication') + + def handle(self): + websocket_helper.addClient(self) console.debug('Opened WebSocket connection') - websocket_helper.broadcast('notification', 'New client connected') + # websocket_helper.broadcast('notification', 'New client connected') def on_message(self, rawMessage): @@ -21,5 +50,5 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler): def on_close(self): websocket_helper.removeClient(self) console.debug('Closed WebSocket connection') - websocket_helper.broadcast('notification', 'Client disconnected') + # websocket_helper.broadcast('notification', 'Client disconnected') diff --git a/app/classes/web/websocket_helper.py b/app/classes/web/websocket_helper.py index 4962bb05..7aaf36ec 100644 --- a/app/classes/web/websocket_helper.py +++ b/app/classes/web/websocket_helper.py @@ -10,13 +10,16 @@ class WebSocketHelper: def removeClient(self, client): self.clients.add(client) + + def send_message(self, client, event_type, data): + message = str(json.dumps({'event': event_type, 'data': data})) + client.write_message(message) - def broadcast(self, message_type: str, data): - console.debug('Sending: ' + str(json.dumps({'type': message_type, 'data': data}))) - message = str(json.dumps({'event': message_type, 'data': data})) + def broadcast(self, event_type, data): + console.debug('Sending: ' + str(json.dumps({'event': event_type, 'data': data}))) for client in self.clients: try: - client.write_message(message) + self.send_message(client, event_type, data) except: pass diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index fc18f056..8b6e3ab4 100644 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -184,7 +184,13 @@ listenEvents .filter(listenedEvent => listenedEvent.event == message.event) .forEach(listenedEvent => listenedEvent.callback(message.data)) - } + }; + wsInternal.onerror = function (errorEvent) { + console.error('WebSocket Error', errorEvent); + }; + wsInternal.onclose = function (closeEvent) { + console.log('Closed WebSocket', closeEvent); + }; webSocket = {