From 8cd2d592303398d00d20f53575c31b00416f41b2 Mon Sep 17 00:00:00 2001 From: LukasDoesDev Date: Thu, 17 Dec 2020 15:39:29 +0200 Subject: [PATCH 01/11] Websockets.. Work in progress! websocket.html is just a blank page with the newest javascript helpers threading working wonderfully! Except my own code. websocket.html and thte bit from panel_handler.py can be deleted once this is done. It was just because the css and js kept loading for super long --- app/classes/shared/tasks.py | 35 ++++++++- app/classes/web/panel_handler.py | 3 + app/classes/web/tornado.py | 2 + app/classes/web/websocket_handler.py | 37 ++++++++++ app/frontend/templates/base.html | 78 +++++++++++++++++++++ app/frontend/templates/panel/websocket.html | 63 +++++++++++++++++ 6 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 app/classes/web/websocket_handler.py create mode 100644 app/frontend/templates/panel/websocket.html diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index 237d850c..e24e08ce 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -8,6 +8,7 @@ import threading from app.classes.shared.helpers import helper from app.classes.shared.console import console from app.classes.web.tornado import webserver +from app.classes.web.websocket_handler import WebSocketHandler from app.classes.minecraft.stats import stats from app.classes.shared.controller import controller @@ -24,6 +25,7 @@ except ModuleNotFoundError as e: console.critical("Import Error: Unable to load {} module".format(e, e.name)) sys.exit(1) + class TasksManager: def __init__(self): @@ -38,6 +40,9 @@ class TasksManager: self.command_thread = threading.Thread(target=self.command_watcher, daemon=True, name="command_watcher") self.command_thread.start() + self.realtime_thread = threading.Thread(target=self.realtime_thread, daemon=True, name="realtime") + self.realtime_thread.start() + def get_main_thread_run_status(self): return self.main_thread_exiting @@ -72,10 +77,8 @@ class TasksManager: db_helper.mark_command_complete(c.get('command_id', None)) - time.sleep(1) - def _main_graceful_exit(self): try: os.remove(helper.session_file) @@ -133,5 +136,33 @@ class TasksManager: logger.info("Scheduling Serverjars.com cache refresh service every 12 hours") schedule.every(12).hours.do(server_jar_obj.refresh_cache) + @staticmethod + def realtime_thread(): + console.debug('realtime zero') + while True: + if len(WebSocketHandler.connections) > 0: + print(WebSocketHandler) + WebSocketHandler.broadcast(WebSocketHandler, 'sample_data', { + 'foo': 'bar', + 'baz': 'Hello, World!' + }) + + if WebSocketHandler.host_stats.get('cpu_usage') != \ + db_helper.get_latest_hosts_stats().get('cpu_usage') or \ + WebSocketHandler.host_stats.get('mem_percent') != \ + db_helper.get_latest_hosts_stats().get('mem_percent'): + + console.debug('realtime one') + WebSocketHandler.host_stats = db_helper.get_latest_hosts_stats() + if len(WebSocketHandler.connections) > 0: + WebSocketHandler.broadcast(WebSocketHandler, 'update_host_stats', { + 'cpu': WebSocketHandler.host_stats.get('cpu_usage'), + 'mem': WebSocketHandler.host_stats.get('mem_percent') + }) + time.sleep(4) + else: + console.debug('realtime two') + time.sleep(2) + tasks_manager = TasksManager() diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 873ce834..75d2145b 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -65,6 +65,9 @@ class PanelHandler(BaseHandler): elif page == 'files_menu': template = "panel/files_menu.html" + elif page == 'websocket': + template = "panel/websocket.html" + elif page == "remove_server": server_id = self.get_argument('id', None) server_data = controller.get_server_data(server_id) diff --git a/app/classes/web/tornado.py b/app/classes/web/tornado.py index 03b0a97f..7867cad4 100644 --- a/app/classes/web/tornado.py +++ b/app/classes/web/tornado.py @@ -24,6 +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 except ModuleNotFoundError as e: logger.critical("Import Error: Unable to load {} module".format(e, e.name)) @@ -125,6 +126,7 @@ class webserver: (r'/ajax/(.*)', AjaxHandler), (r'/api/stats/servers', ServersStats), (r'/api/stats/node', NodeStats), + (r'/ws', WebSocketHandler), ] app = tornado.web.Application( diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py new file mode 100644 index 00000000..1c3ff38c --- /dev/null +++ b/app/classes/web/websocket_handler.py @@ -0,0 +1,37 @@ +import json + +import tornado.websocket +from app.classes.shared.console import console +from app.classes.shared.models import db_helper + + +class WebSocketHandler(tornado.websocket.WebSocketHandler): + connections = set() + host_stats = db_helper.get_latest_hosts_stats() + + def open(self): + self.connections.add(self) + console.debug('Opened WebSocket connection') + self.broadcast('client_joined', { + 'foo': 'bar', + }) + + def on_message(self, message): + # broadcast + # for client in self.connections: + # client.write_message(message) + + # send message to client this message was sent by + # self.write_message + + console.debug('Got message from WebSocket connection {}'.format(message)) + + def on_close(self): + self.connections.remove(self) + console.debug('Closed WebSocket connection') + + def broadcast(self, message_type: str, data): + print(str(json.dumps({'type': message_type, 'data': data}))) + message = str(json.dumps({'type': message_type, 'data': data})) + for client in self.connections: + client.write_message(message) diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index 877de0c4..0499c104 100644 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -58,6 +58,8 @@
+
+ {% block content %} {% end %} @@ -103,6 +105,82 @@ }); }); }); + + {% if request.protocol == 'https'%} + let usingWebSockets = true; + + let socketTypes = []; + + try { + + var wsInternal = new WebSocket('wss://' + location.host + '/ws'); + wsInternal.onopen = function() { + console.log('opened WebSocket connection:', wsInternal) + }; + wsInternal.onmessage = function (event) { + var message = JSON.parse(event.data); + + console.log('got message: ', message) + + socketTypes + .filter(event => event.type == message.type) + .forEach(event => event.callback(message.data)) + } + + + webSocket = { + on: function (type, callback) { + socketTypes.push({ type: type, callback: callback }) + }, + emit: function (type, data) { + var message = { + type: type, + data: data + } + + wsInternal.send(JSON.stringify(message)); + } + } + } catch (error) { + console.error('Error while making websocket helpers', error); + usingWebSockets = false; + } + {% else %} + let usingWebSockets = false; + warn('WebSockets are not supported in Crafty if not using the https protocol') + {% end%} + + function warn(message) { + var closeEl = document.createElement('span'); + var strongEL = document.createElement('strong'); + var msgEl = document.createElement('div'); + + closeEl.innerHTML = '×'; + strongEL.textContent = 'Warning: '; + msgEl.append(strongEL, message); + + + closeEl.style.marginLeft = '15px'; + closeEl.style.fontWeight = 'bold'; + closeEl.style.float = 'right'; + closeEl.style.fontSize = '22px'; + closeEl.style.lineHeight = '20px'; + closeEl.style.cursor = 'pointer'; + closeEl.style.transition = '.3s'; + + closeEl.addEventListener('click', function () {this.parentElement.style.display='none';}); + + var parentEl = document.createElement('div'); + + parentEl.style.padding = '20px'; + parentEl.style.backgroundColor = '#f7970f'; + + parentEl.appendChild(closeEl); + parentEl.appendChild(msgEl); + + document.querySelector('.warnings').appendChild(parentEl); + } + {% block js %} diff --git a/app/frontend/templates/panel/websocket.html b/app/frontend/templates/panel/websocket.html new file mode 100644 index 00000000..efaa6fbd --- /dev/null +++ b/app/frontend/templates/panel/websocket.html @@ -0,0 +1,63 @@ + + + + + + + Default + + + + + + + \ No newline at end of file From 6a253971e7145407c80931836cc69a7ce8a0fe72 Mon Sep 17 00:00:00 2001 From: LukasDoesDev Date: Mon, 11 Jan 2021 21:51:38 +0200 Subject: [PATCH 02/11] Add error when JavaScript isn't enabled --- app/frontend/templates/base.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index 0499c104..735b5f6c 100644 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -58,7 +58,11 @@
-
+
+
+
Warning: Crafty doesn't work properly when JavaScript isn't enabled!
+
+
{% block content %} {% end %} @@ -81,6 +85,9 @@ From 7691dc566e3e2b7ff50615e116ece70454c2d18f Mon Sep 17 00:00:00 2001 From: luukas Date: Sat, 27 Feb 2021 14:05:04 +0200 Subject: [PATCH 05/11] Finish WebSockets --- app/classes/web/websocket_handler.py | 5 +- app/classes/web/websocket_helper.py | 2 +- app/frontend/templates/base.html | 124 +++++++++++++++++--- app/frontend/templates/panel/dashboard.html | 26 ++-- 4 files changed, 127 insertions(+), 30 deletions(-) diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py index 1b03e855..3d6ed777 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -10,15 +10,16 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler): def open(self): websocket_helper.addClient(self) console.debug('Opened WebSocket connection') - websocket_helper.broadcast('client_joined', {}) + websocket_helper.broadcast('notification', 'New client connected') def on_message(self, rawMessage): console.debug('Got message from WebSocket connection {}'.format(rawMessage)) message = json.loads(rawMessage) - console.debug('Type: {}, Data: {}'.format(message['type'], message['data'])) + console.debug('Event Type: {}, Data: {}'.format(message['event'], message['data'])) def on_close(self): websocket_helper.removeClient(self) console.debug('Closed WebSocket connection') + websocket_helper.broadcast('notification', 'Client disconnected') diff --git a/app/classes/web/websocket_helper.py b/app/classes/web/websocket_helper.py index 49eaedcd..4962bb05 100644 --- a/app/classes/web/websocket_helper.py +++ b/app/classes/web/websocket_helper.py @@ -13,7 +13,7 @@ class WebSocketHelper: def broadcast(self, message_type: str, data): console.debug('Sending: ' + str(json.dumps({'type': message_type, 'data': data}))) - message = str(json.dumps({'type': message_type, 'data': data})) + message = str(json.dumps({'event': message_type, 'data': data})) for client in self.clients: try: client.write_message(message) diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index 735b5f6c..23ba1533 100644 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -77,6 +77,58 @@
+ +
+ + @@ -113,10 +165,10 @@ }); }); - {% if request.protocol == 'https'%} + {% if request.protocol == 'https' %} let usingWebSockets = true; - let socketTypes = []; + let listenEvents = []; try { @@ -124,24 +176,25 @@ wsInternal.onopen = function() { console.log('opened WebSocket connection:', wsInternal) }; - wsInternal.onmessage = function (event) { - var message = JSON.parse(event.data); + wsInternal.onmessage = function (rawMessage) { + var message = JSON.parse(rawMessage.data); console.log('got message: ', message) - socketTypes - .filter(event => event.type == message.type) - .forEach(event => event.callback(message.data)) + listenEvents + .filter(listenedEvent => listenedEvent.event == message.event) + .forEach(listenedEvent => listenedEvent.callback(message.data)) } webSocket = { - on: function (type, callback) { - socketTypes.push({ type: type, callback: callback }) + on: function (event, callback) { + console.log('registered ' + event + ' event'); + listenEvents.push({ event: event, callback: callback }) }, - emit: function (type, data) { + emit: function (event, data) { var message = { - type: type, + event: event, data: data } @@ -155,6 +208,7 @@ {% else %} let usingWebSockets = false; warn('WebSockets are not supported in Crafty if not using the https protocol') + var webSocket; {% end%} function warn(message) { @@ -167,13 +221,12 @@ msgEl.append(strongEL, message); + closeEl.style.float = 'right'; closeEl.style.marginLeft = '15px'; closeEl.style.fontWeight = 'bold'; - closeEl.style.float = 'right'; - closeEl.style.fontSize = '22px'; closeEl.style.lineHeight = '20px'; + closeEl.style.fontSize = '22px'; closeEl.style.cursor = 'pointer'; - closeEl.style.transition = '.3s'; closeEl.addEventListener('click', function () {this.parentElement.style.display='none';}); @@ -188,6 +241,49 @@ document.querySelector('.warnings').appendChild(parentEl); } + function closeNotification(element) { + element.parentElement.classList.add('remove'); + setTimeout(function () { + element.parentElement.remove(); + }, 500); + } + + function notify(message) { + console.log(`notify(${message}})`); + var paragraphEl = document.createElement('p'); + var closeEl = document.createElement('span'); + + paragraphEl.textContent = message; + + closeEl.innerHTML = '×'; + closeEl.addEventListener('click', function () {closeNotification(this)}); + + var parentEl = document.createElement('div'); + parentEl.appendChild(paragraphEl); + parentEl.appendChild(closeEl); + + parentEl.classList.add('notification'); + + document.querySelector('.notifications').appendChild(parentEl); + + + setTimeout(function () { + parentEl.classList.add('active'); + }, 200); + + setTimeout(function (element) { + closeNotification(element); + }, 7500, closeEl); + + ` +
+

Hello, World! This text should overflow

+ × +
+ ` + } + webSocket.on('notification', notify); + {% block js %} diff --git a/app/frontend/templates/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html index f1324728..8bac8512 100644 --- a/app/frontend/templates/panel/dashboard.html +++ b/app/frontend/templates/panel/dashboard.html @@ -266,20 +266,20 @@ $( document ).ready(function() { message: '
  Please be patient while we restart the server
This screen will refresh in a moment
' }); }); + if (webSocket) { + cpu_data = document.getElementById('cpu_data'); + cpu_usage = document.getElementById('cpu_usage'); + mem_usage = document.getElementById('mem_usage'); + mem_percent = document.getElementById('mem_percent'); - cpu_data = document.getElementById('cpu_data'); - cpu_usage = document.getElementById('cpu_usage'); - mem_usage = document.getElementById('mem_usage'); - mem_percent = document.getElementById('mem_percent'); - - webSocket.on('update_host_stats', function (hostStats) { - var cpuDataTitle = `CPU Cores: ${hostStats.cpu_cores}
CPU Cur Freq: ${hostStats.cpu_cur_freq}
CPU Max Freq: ${hostStats.cpu_max_freq}`; - cpu_data.setAttribute('data-original-title', cpuDataTitle); - cpu_usage.textContent = hostStats.cpu_usage; - mem_usage.setAttribute('data-original-title', `Memory Usage: ${hostStats.mem_usage}`); - mem_percent.textContent = hostStats.mem_percent + '%'; - - }); + webSocket.on('update_host_stats', function (hostStats) { + var cpuDataTitle = `CPU Cores: ${hostStats.cpu_cores}
CPU Cur Freq: ${hostStats.cpu_cur_freq}
CPU Max Freq: ${hostStats.cpu_max_freq}`; + cpu_data.setAttribute('data-original-title', cpuDataTitle); + cpu_usage.textContent = hostStats.cpu_usage; + mem_usage.setAttribute('data-original-title', `Memory Usage: ${hostStats.mem_usage}`); + mem_percent.textContent = hostStats.mem_percent + '%'; + }); + } }); From 7be57ecfc9367bfe12f16ec5ad670a48925c6cac Mon Sep 17 00:00:00 2001 From: luukas Date: Sat, 27 Feb 2021 14:07:16 +0200 Subject: [PATCH 06/11] Oops, forgot one thing --- app/frontend/templates/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index 23ba1533..fc18f056 100644 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -221,11 +221,11 @@ msgEl.append(strongEL, message); - closeEl.style.float = 'right'; closeEl.style.marginLeft = '15px'; closeEl.style.fontWeight = 'bold'; - closeEl.style.lineHeight = '20px'; + closeEl.style.float = 'right'; closeEl.style.fontSize = '22px'; + closeEl.style.lineHeight = '20px'; closeEl.style.cursor = 'pointer'; closeEl.addEventListener('click', function () {this.parentElement.style.display='none';}); From a965af549196762e687eaf5c2a318255212e59ff Mon Sep 17 00:00:00 2001 From: luukas Date: Sat, 27 Feb 2021 14:17:37 +0200 Subject: [PATCH 07/11] Remove unnecessary websocket.html --- app/classes/web/panel_handler.py | 3 - app/frontend/templates/panel/websocket.html | 63 --------------------- 2 files changed, 66 deletions(-) delete mode 100644 app/frontend/templates/panel/websocket.html diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 75d2145b..873ce834 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -65,9 +65,6 @@ class PanelHandler(BaseHandler): elif page == 'files_menu': template = "panel/files_menu.html" - elif page == 'websocket': - template = "panel/websocket.html" - elif page == "remove_server": server_id = self.get_argument('id', None) server_data = controller.get_server_data(server_id) diff --git a/app/frontend/templates/panel/websocket.html b/app/frontend/templates/panel/websocket.html deleted file mode 100644 index efaa6fbd..00000000 --- a/app/frontend/templates/panel/websocket.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - Default - - - - - - - \ No newline at end of file From 9c62099f325defb36763deb79b8f3dcf309e2bb3 Mon Sep 17 00:00:00 2001 From: luukas Date: Mon, 1 Mar 2021 02:54:20 +0200 Subject: [PATCH 08/11] 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 = { From 37597f920f3caa1b1d70a514a8cc2a5e1b3fcf2c Mon Sep 17 00:00:00 2001 From: luukas Date: Mon, 1 Mar 2021 19:33:15 +0200 Subject: [PATCH 09/11] Make sure clients are authenticated before sending messages. As the system is asynchronous some messages could (?) slip through before the socket is closed when auth fails. --- app/classes/web/websocket_helper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/classes/web/websocket_helper.py b/app/classes/web/websocket_helper.py index 7aaf36ec..077ea180 100644 --- a/app/classes/web/websocket_helper.py +++ b/app/classes/web/websocket_helper.py @@ -12,8 +12,9 @@ class WebSocketHelper: 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) + if client.check_auth(): + message = str(json.dumps({'event': event_type, 'data': data})) + client.write_message(message) def broadcast(self, event_type, data): console.debug('Sending: ' + str(json.dumps({'event': event_type, 'data': data}))) From 06a5f4713e57d629e61e42832c31144d17d9689d Mon Sep 17 00:00:00 2001 From: luukas Date: Fri, 5 Mar 2021 11:30:18 +0200 Subject: [PATCH 10/11] fix merge conflict in tasks.py --- app/classes/shared/tasks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index 5b77d6ea..f36c8db1 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -167,6 +167,11 @@ class TasksManager: else: # Stats are same time.sleep(8) + def log_watcher(self): + console.debug('in log_watcher') + helper.check_for_old_logs(db_helper) + schedule.every(6).hours.do(lambda: helper.check_for_old_logs(db_helper)) + From 458456a71663e2e36bd53b08ffa89fe38e7e53a5 Mon Sep 17 00:00:00 2001 From: luukas Date: Fri, 5 Mar 2021 11:35:05 +0200 Subject: [PATCH 11/11] try fix merge conflict --- app/classes/shared/models.py | 13 ++++++++----- app/classes/shared/tasks.py | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/classes/shared/models.py b/app/classes/shared/models.py index b875f89e..2100d26a 100644 --- a/app/classes/shared/models.py +++ b/app/classes/shared/models.py @@ -253,16 +253,19 @@ class db_shortcuts: query = Commands.select().where(Commands.executed == 0) return self.return_rows(query) - def get_server_friendly_name(self, server_id): - server_data = self.get_server_data_by_id(server_id) + @staticmethod + def get_server_friendly_name(server_id): + server_data = db_helper.get_server_data_by_id(server_id) 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): + @staticmethod + def send_command(user_id, server_id, remote_ip, command): - server_name = self.get_server_friendly_name(server_id) + server_name = db_helper.get_server_friendly_name(server_id) - self.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name), + # Example: Admin issued command start_server for server Survival + db_helper.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name), server_id, remote_ip) Commands.insert({ diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index f36c8db1..71977189 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -167,6 +167,7 @@ class TasksManager: else: # Stats are same time.sleep(8) + def log_watcher(self): console.debug('in log_watcher') helper.check_for_old_logs(db_helper)