From 2c4b90964d7fdb13bc609afdaec229c2f1a7489c Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 2 Aug 2023 19:20:26 -0400 Subject: [PATCH 01/11] Check for string response on server ping --- app/classes/minecraft/mc_ping.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/classes/minecraft/mc_ping.py b/app/classes/minecraft/mc_ping.py index 51fcaa2c..c5cb9916 100644 --- a/app/classes/minecraft/mc_ping.py +++ b/app/classes/minecraft/mc_ping.py @@ -16,6 +16,12 @@ logger = logging.getLogger(__name__) class Server: def __init__(self, data): + if isinstance(data, str): + logger.error( + "Failed to calculate stats. Expected object. " + f"Server returned string: {data}" + ) + return self.description = data.get("description") # print(self.description) if isinstance(self.description, dict): From ad9042e88fe815f85f2f143400ee850f06076903 Mon Sep 17 00:00:00 2001 From: Wout Bouckaert Date: Sat, 12 Aug 2023 21:53:26 -0600 Subject: [PATCH 02/11] Replace bleach with nh3. --- app/classes/web/ajax_handler.py | 18 +++++----- app/classes/web/base_handler.py | 4 +-- app/classes/web/file_handler.py | 22 ++++++------ app/classes/web/panel_handler.py | 60 +++++++++++++++---------------- app/classes/web/public_handler.py | 14 ++++---- app/classes/web/server_handler.py | 42 +++++++++++----------- requirements.txt | 2 +- 7 files changed, 79 insertions(+), 83 deletions(-) diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index efe8d2fa..df2d701d 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -5,7 +5,7 @@ import re import logging import time import urllib.parse -import bleach +import nh3 import tornado.web import tornado.escape @@ -29,7 +29,7 @@ class AjaxHandler(BaseHandler): @tornado.web.authenticated def get(self, page): _, _, exec_user = self.current_user - error = bleach.clean(self.get_argument("error", "WTF Error!")) + error = nh3.clean(self.get_argument("error", "WTF Error!")) template = "panel/denied.html" @@ -48,7 +48,7 @@ class AjaxHandler(BaseHandler): self.redirect("/panel/error?error=Server ID Not Found") return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) server_data = self.controller.servers.get_server_data_by_id(server_id) if not server_data: @@ -246,7 +246,7 @@ class AjaxHandler(BaseHandler): if not self.check_server_id(server_id, "get_tree"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if Helpers.validate_traversal( self.controller.servers.get_server_data_by_id(server_id)["path"], path @@ -327,7 +327,7 @@ class AjaxHandler(BaseHandler): elif page == "send_order": self.controller.users.update_server_order( - exec_user["user_id"], bleach.clean(self.get_argument("order")) + exec_user["user_id"], nh3.clean(self.get_argument("order")) ) return @@ -392,8 +392,8 @@ class AjaxHandler(BaseHandler): if not superuser: self.redirect("/panel/error?error=Unauthorized access to Backups") return - server_id = bleach.clean(self.get_argument("id", None)) - zip_name = bleach.clean(self.get_argument("zip_file", None)) + server_id = nh3.clean(self.get_argument("id", None)) + zip_name = nh3.clean(self.get_argument("zip_file", None)) svr_obj = self.controller.servers.get_server_obj(server_id) server_data = self.controller.servers.get_server_data_by_id(server_id) @@ -652,7 +652,7 @@ class AjaxHandler(BaseHandler): if not self.check_server_id(server_id, "del_backup"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id) if not ( @@ -684,7 +684,7 @@ class AjaxHandler(BaseHandler): f"Server ID not defined in {page_name} ajax call ({server_id})" ) return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) # does this server id exist? if not self.controller.servers.server_id_exists(server_id): diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py index e772d633..11a62ff8 100644 --- a/app/classes/web/base_handler.py +++ b/app/classes/web/base_handler.py @@ -2,7 +2,7 @@ import logging import re import typing as t import orjson -import bleach +import nh3 import tornado.web from app.classes.models.crafty_permissions import EnumPermissionsCrafty @@ -93,7 +93,7 @@ class BaseHandler(tornado.web.RequestHandler): if type(text) in self.nobleach: logger.debug("Auto-bleaching - bypass type") return text - return bleach.clean(text) + return nh3.clean(text) def get_argument( self, diff --git a/app/classes/web/file_handler.py b/app/classes/web/file_handler.py index e2d07476..f3c05151 100644 --- a/app/classes/web/file_handler.py +++ b/app/classes/web/file_handler.py @@ -1,6 +1,6 @@ import os import logging -import bleach +import nh3 import tornado.web import tornado.escape @@ -55,7 +55,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "get_file"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if not self.helper.is_subdir( file_path, @@ -92,7 +92,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "get_tree"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if Helpers.validate_traversal( self.controller.servers.get_server_data_by_id(server_id)["path"], path @@ -113,7 +113,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "get_tree"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if Helpers.validate_traversal( self.controller.servers.get_server_data_by_id(server_id)["path"], path @@ -161,7 +161,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "create_file"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if not self.helper.is_subdir( file_path, @@ -194,7 +194,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "create_dir"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if not self.helper.is_subdir( dir_path, @@ -259,7 +259,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "del_file"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id) if not ( @@ -293,7 +293,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "del_dir"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id) if not self.helper.is_subdir( @@ -346,7 +346,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "save_file"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if not self.helper.is_subdir( file_path, @@ -401,7 +401,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "rename_file"): return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) if item_path is None or new_item_name is None: logger.warning("Invalid path(s) in rename_file file ajax call") @@ -450,7 +450,7 @@ class FileHandler(BaseHandler): f"Server ID not defined in {page_name} file ajax call ({server_id})" ) return - server_id = bleach.clean(server_id) + server_id = nh3.clean(server_id) # does this server id exist? if not self.controller.servers.server_id_exists(server_id): diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 20c76c1a..0dc3c586 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -7,7 +7,7 @@ import json import logging import threading import urllib.parse -import bleach +import nh3 import requests import tornado.web import tornado.escape @@ -67,9 +67,7 @@ class PanelHandler(BaseHandler): ) in self.controller.crafty_perms.list_defined_crafty_permissions(): argument = int( float( - bleach.clean( - self.get_argument(f"permission_{permission.name}", "0") - ) + nh3.clean(self.get_argument(f"permission_{permission.name}", "0")) ) ) if argument: @@ -78,9 +76,7 @@ class PanelHandler(BaseHandler): ) q_argument = int( - float( - bleach.clean(self.get_argument(f"quantity_{permission.name}", "0")) - ) + float(nh3.clean(self.get_argument(f"quantity_{permission.name}", "0"))) ) if q_argument: server_quantity[permission.name] = q_argument @@ -479,7 +475,7 @@ class PanelHandler(BaseHandler): template = "panel/dashboard.html" elif page == "server_detail": - subpage = bleach.clean(self.get_argument("subpage", "")) + subpage = nh3.clean(self.get_argument("subpage", "")) server_id = self.check_server_id() if server_id is None: @@ -1284,7 +1280,7 @@ class PanelHandler(BaseHandler): template = "panel/panel_edit_user_apikeys.html" elif page == "remove_user": - user_id = bleach.clean(self.get_argument("id", None)) + user_id = nh3.clean(self.get_argument("id", None)) if ( not superuser @@ -1416,7 +1412,7 @@ class PanelHandler(BaseHandler): template = "panel/panel_edit_role.html" elif page == "remove_role": - role_id = bleach.clean(self.get_argument("id", None)) + role_id = nh3.clean(self.get_argument("id", None)) if ( not superuser @@ -1604,7 +1600,7 @@ class PanelHandler(BaseHandler): backup_path = Helpers.wtol_path(backup_path) else: backup_path = server_obj.backup_path - max_backups = bleach.clean(self.get_argument("max_backups", None)) + max_backups = nh3.clean(self.get_argument("max_backups", None)) server_obj = self.controller.servers.get_server_obj(server_id) @@ -1665,15 +1661,15 @@ class PanelHandler(BaseHandler): self.redirect("/panel/config_json") elif page == "edit_user": - if bleach.clean(self.get_argument("username", None)).lower() == "system": + if nh3.clean(self.get_argument("username", None)).lower() == "system": self.redirect( "/panel/error?error=Unauthorized access: " "system user is not editable" ) - user_id = bleach.clean(self.get_argument("id", None)) + user_id = nh3.clean(self.get_argument("id", None)) user = self.controller.users.get_user_by_id(user_id) - username = bleach.clean(self.get_argument("username", None).lower()) - theme = bleach.clean(self.get_argument("theme", "default")) + username = nh3.clean(self.get_argument("username", None).lower()) + theme = nh3.clean(self.get_argument("theme", "default")) if ( username != self.controller.users.get_user_by_id(user_id)["username"] and username in self.controller.users.get_all_usernames() @@ -1681,16 +1677,16 @@ class PanelHandler(BaseHandler): self.redirect( "/panel/error?error=Duplicate User: Useranme already exists." ) - password0 = bleach.clean(self.get_argument("password0", None)) - password1 = bleach.clean(self.get_argument("password1", None)) - email = bleach.clean(self.get_argument("email", "default@example.com")) + password0 = nh3.clean(self.get_argument("password0", None)) + password1 = nh3.clean(self.get_argument("password1", None)) + email = nh3.clean(self.get_argument("email", "default@example.com")) enabled = int(float(self.get_argument("enabled", "0"))) try: - hints = int(bleach.clean(self.get_argument("hints"))) + hints = int(nh3.clean(self.get_argument("hints"))) hints = True except: hints = False - lang = bleach.clean( + lang = nh3.clean( self.get_argument("language"), self.helper.get_setting("language") ) @@ -1699,7 +1695,7 @@ class PanelHandler(BaseHandler): # We don't want that. Automatically make them stay super user # since we know they are. if str(exec_user["user_id"]) != str(user_id): - superuser = int(bleach.clean(self.get_argument("superuser", "0"))) + superuser = int(nh3.clean(self.get_argument("superuser", "0"))) else: superuser = 1 else: @@ -1877,7 +1873,7 @@ class PanelHandler(BaseHandler): self.finish() elif page == "add_user": - username = bleach.clean(self.get_argument("username", None).lower()) + username = nh3.clean(self.get_argument("username", None).lower()) if username.lower() == "system": self.redirect( "/panel/error?error=Unauthorized access: " @@ -1885,18 +1881,18 @@ class PanelHandler(BaseHandler): " Please choose a different username." ) return - password0 = bleach.clean(self.get_argument("password0", None)) - password1 = bleach.clean(self.get_argument("password1", None)) - email = bleach.clean(self.get_argument("email", "default@example.com")) + password0 = nh3.clean(self.get_argument("password0", None)) + password1 = nh3.clean(self.get_argument("password1", None)) + email = nh3.clean(self.get_argument("email", "default@example.com")) enabled = int(float(self.get_argument("enabled", "0"))) - theme = bleach.clean(self.get_argument("theme"), "default") + theme = nh3.clean(self.get_argument("theme"), "default") hints = True - lang = bleach.clean( + lang = nh3.clean( self.get_argument("lang", self.helper.get_setting("language")) ) # We don't want a non-super user to be able to create a super user. if superuser: - new_superuser = int(bleach.clean(self.get_argument("superuser", "0"))) + new_superuser = int(nh3.clean(self.get_argument("superuser", "0"))) else: new_superuser = 0 @@ -1971,8 +1967,8 @@ class PanelHandler(BaseHandler): self.redirect("/panel/panel_config") elif page == "edit_role": - role_id = bleach.clean(self.get_argument("id", None)) - role_name = bleach.clean(self.get_argument("role_name", None)) + role_id = nh3.clean(self.get_argument("id", None)) + role_name = nh3.clean(self.get_argument("role_name", None)) role = self.controller.roles.get_role(role_id) @@ -2018,7 +2014,7 @@ class PanelHandler(BaseHandler): self.redirect("/panel/panel_config") elif page == "add_role": - role_name = bleach.clean(self.get_argument("role_name", None)) + role_name = nh3.clean(self.get_argument("role_name", None)) if exec_user["superuser"]: manager = self.get_argument("manager", None) if manager == "": @@ -2092,7 +2088,7 @@ class PanelHandler(BaseHandler): } if page == "remove_apikey": - key_id = bleach.clean(self.get_argument("id", None)) + key_id = nh3.clean(self.get_argument("id", None)) if not superuser: self.redirect("/panel/error?error=Unauthorized access: not superuser") diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index 76c6a8be..b7d1be9b 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -1,5 +1,5 @@ import logging -import bleach +import nh3 from app.classes.shared.helpers import Helpers from app.classes.models.users import HelperUsers @@ -28,8 +28,8 @@ class PublicHandler(BaseHandler): # self.clear_cookie("user_data") def get(self, page=None): - error = bleach.clean(self.get_argument("error", "Invalid Login!")) - error_msg = bleach.clean(self.get_argument("error_msg", "")) + error = nh3.clean(self.get_argument("error", "Invalid Login!")) + error_msg = nh3.clean(self.get_argument("error_msg", "")) page_data = { "version": self.helper.get_version_string(), @@ -82,8 +82,8 @@ class PublicHandler(BaseHandler): ) def post(self, page=None): - error = bleach.clean(self.get_argument("error", "Invalid Login!")) - error_msg = bleach.clean(self.get_argument("error_msg", "")) + error = nh3.clean(self.get_argument("error", "Invalid Login!")) + error_msg = nh3.clean(self.get_argument("error_msg", "")) page_data = { "version": self.helper.get_version_string(), @@ -100,8 +100,8 @@ class PublicHandler(BaseHandler): if self.request.query: next_page = "/login?" + self.request.query - entered_username = bleach.clean(self.get_argument("username")) - entered_password = bleach.clean(self.get_argument("password")) + entered_username = nh3.clean(self.get_argument("username")) + entered_password = nh3.clean(self.get_argument("password")) # pylint: disable=no-member try: diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index eae3ce0c..42cb8ce5 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -4,7 +4,7 @@ import os import time import tornado.web import tornado.escape -import bleach +import nh3 from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.shared.helpers import Helpers @@ -195,8 +195,8 @@ class ServerHandler(BaseHandler): } if page == "command": - server_id = bleach.clean(self.get_argument("id", None)) - command = bleach.clean(self.get_argument("command", None)) + server_id = nh3.clean(self.get_argument("id", None)) + command = nh3.clean(self.get_argument("command", None)) if server_id is not None: if command == "clone_server": @@ -311,24 +311,24 @@ class ServerHandler(BaseHandler): user_roles = self.controller.roles.get_all_roles() else: user_roles = self.get_user_roles() - server = bleach.clean(self.get_argument("server", "")) - server_name = bleach.clean(self.get_argument("server_name", "")) - min_mem = bleach.clean(self.get_argument("min_memory", "")) - max_mem = bleach.clean(self.get_argument("max_memory", "")) - port = bleach.clean(self.get_argument("port", "")) + server = nh3.clean(self.get_argument("server", "")) + server_name = nh3.clean(self.get_argument("server_name", "")) + min_mem = nh3.clean(self.get_argument("min_memory", "")) + max_mem = nh3.clean(self.get_argument("max_memory", "")) + port = nh3.clean(self.get_argument("port", "")) if int(port) < 1 or int(port) > 65535: self.redirect( "/panel/error?error=Constraint Error: " "Port must be greater than 0 and less than 65535" ) return - import_type = bleach.clean(self.get_argument("create_type", "")) - import_server_path = bleach.clean(self.get_argument("server_path", "")) - import_server_jar = bleach.clean(self.get_argument("server_jar", "")) + import_type = nh3.clean(self.get_argument("create_type", "")) + import_server_path = nh3.clean(self.get_argument("server_path", "")) + import_server_jar = nh3.clean(self.get_argument("server_jar", "")) server_parts = server.split("|") captured_roles = [] for role in user_roles: - if bleach.clean(self.get_argument(str(role), "")) == "on": + if nh3.clean(self.get_argument(str(role), "")) == "on": captured_roles.append(role) if not server_name: @@ -372,7 +372,7 @@ class ServerHandler(BaseHandler): ) elif import_type == "import_zip": # here import_server_path means the zip path - zip_path = bleach.clean(self.get_argument("root_path")) + zip_path = nh3.clean(self.get_argument("root_path")) good_path = Helpers.check_path_exists(zip_path) if not good_path: self.redirect("/panel/error?error=Temp path not found!") @@ -476,9 +476,9 @@ class ServerHandler(BaseHandler): user_roles = self.controller.roles.get_all_roles() else: user_roles = self.controller.roles.get_all_roles() - server = bleach.clean(self.get_argument("server", "")) - server_name = bleach.clean(self.get_argument("server_name", "")) - port = bleach.clean(self.get_argument("port", "")) + server = nh3.clean(self.get_argument("server", "")) + server_name = nh3.clean(self.get_argument("server_name", "")) + port = nh3.clean(self.get_argument("port", "")) if not port: port = 19132 @@ -488,13 +488,13 @@ class ServerHandler(BaseHandler): "Port must be greater than 0 and less than 65535" ) return - import_type = bleach.clean(self.get_argument("create_type", "")) - import_server_path = bleach.clean(self.get_argument("server_path", "")) - import_server_exe = bleach.clean(self.get_argument("server_jar", "")) + import_type = nh3.clean(self.get_argument("create_type", "")) + import_server_path = nh3.clean(self.get_argument("server_path", "")) + import_server_exe = nh3.clean(self.get_argument("server_jar", "")) server_parts = server.split("|") captured_roles = [] for role in user_roles: - if bleach.clean(self.get_argument(str(role), "")) == "on": + if nh3.clean(self.get_argument(str(role), "")) == "on": captured_roles.append(role) if not server_name: @@ -536,7 +536,7 @@ class ServerHandler(BaseHandler): ) elif import_type == "import_zip": # here import_server_path means the zip path - zip_path = bleach.clean(self.get_argument("root_path")) + zip_path = nh3.clean(self.get_argument("root_path")) good_path = Helpers.check_path_exists(zip_path) if not good_path: self.redirect("/panel/error?error=Temp path not found!") diff --git a/requirements.txt b/requirements.txt index 98e095f1..df3360a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ apscheduler==3.8.1 argon2-cffi==21.3 -bleach==4.1 +nh3==0.2.14 cached_property==1.5.2 colorama==0.4 croniter==1.3.5 From 7eb8e2bfd950fd6b906a299b958ecc5c180756d5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 24 Aug 2023 13:02:44 -0400 Subject: [PATCH 03/11] Add get users to crafty command --- app/classes/shared/command.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/classes/shared/command.py b/app/classes/shared/command.py index 26fdd2f0..cebe76b7 100644 --- a/app/classes/shared/command.py +++ b/app/classes/shared/command.py @@ -92,6 +92,9 @@ class MainPrompt(cmd.Cmd): self.controller.users.update_user(user_id, {"password": new_pass}) + def do_get_users(self, _line): + Console.info(self.controller.users.get_all_usernames()) + @staticmethod def do_threads(_line): for thread in threading.enumerate(): From 5f088c4a1cff41a886a21d1e5385710e8e8d933e Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 2 Sep 2023 14:24:40 +0100 Subject: [PATCH 04/11] Bump tornado to resolve #269 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 98e095f1..79a37409 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ pyjwt==2.4.0 PyYAML==6.0.1 requests==2.31 termcolor==1.1 -tornado==6.3.2 +tornado==6.3.3 tzlocal==4.0 jsonschema==4.5.1 orjson==3.8.12 From ce6e8ea5382999d11de46660ae3a9d5d33252356 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 2 Sep 2023 14:38:22 +0100 Subject: [PATCH 05/11] Update changelog !614 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a1cc88..edce3fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ TBD ### Bug fixes - PWA: Removed the custom offline page in favour of browser default ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/607)) - Fix hidden servers appearing visible on public mobile status page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/612)) +- Correctly handle if a server returns a string instead of json data on socket ping ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/614)) ### Tweaks - Polish/Enhance display for InApp Documentation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/613)) ### Lang From 36afb3b797ea8448780baf897003a0b19355b08b Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 2 Sep 2023 15:17:09 +0100 Subject: [PATCH 06/11] Update changelog !616 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index edce3fd7..e2a8502c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ TBD - PWA: Removed the custom offline page in favour of browser default ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/607)) - Fix hidden servers appearing visible on public mobile status page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/612)) - Correctly handle if a server returns a string instead of json data on socket ping ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/614)) +### Refactor +- Refractor/Replace bleach with nh3 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/616)) ### Tweaks - Polish/Enhance display for InApp Documentation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/613)) ### Lang From 1fd0ac75818f34a9c19a77ce5b9757659ff0d4fa Mon Sep 17 00:00:00 2001 From: Zedifus Date: Sat, 2 Sep 2023 15:57:09 +0100 Subject: [PATCH 07/11] Update changelog !620 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a8502c..1245e3ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ TBD - Refractor/Replace bleach with nh3 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/616)) ### Tweaks - Polish/Enhance display for InApp Documentation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/613)) +- Add get_users command to Crafty's console ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/620)) ### Lang TBD

From 81d63575962b231235071c3e55dc64b8701a9f09 Mon Sep 17 00:00:00 2001 From: Iain Powrie Date: Mon, 4 Sep 2023 23:13:13 +0000 Subject: [PATCH 08/11] Revert "Merge branch 'refractor/remove-bleach' into 'dev'" This reverts merge request !616 --- app/classes/web/ajax_handler.py | 18 +++++----- app/classes/web/base_handler.py | 4 +-- app/classes/web/file_handler.py | 22 ++++++------ app/classes/web/panel_handler.py | 60 ++++++++++++++++--------------- app/classes/web/public_handler.py | 14 ++++---- app/classes/web/server_handler.py | 42 +++++++++++----------- requirements.txt | 2 +- 7 files changed, 83 insertions(+), 79 deletions(-) diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index df2d701d..efe8d2fa 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -5,7 +5,7 @@ import re import logging import time import urllib.parse -import nh3 +import bleach import tornado.web import tornado.escape @@ -29,7 +29,7 @@ class AjaxHandler(BaseHandler): @tornado.web.authenticated def get(self, page): _, _, exec_user = self.current_user - error = nh3.clean(self.get_argument("error", "WTF Error!")) + error = bleach.clean(self.get_argument("error", "WTF Error!")) template = "panel/denied.html" @@ -48,7 +48,7 @@ class AjaxHandler(BaseHandler): self.redirect("/panel/error?error=Server ID Not Found") return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) server_data = self.controller.servers.get_server_data_by_id(server_id) if not server_data: @@ -246,7 +246,7 @@ class AjaxHandler(BaseHandler): if not self.check_server_id(server_id, "get_tree"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if Helpers.validate_traversal( self.controller.servers.get_server_data_by_id(server_id)["path"], path @@ -327,7 +327,7 @@ class AjaxHandler(BaseHandler): elif page == "send_order": self.controller.users.update_server_order( - exec_user["user_id"], nh3.clean(self.get_argument("order")) + exec_user["user_id"], bleach.clean(self.get_argument("order")) ) return @@ -392,8 +392,8 @@ class AjaxHandler(BaseHandler): if not superuser: self.redirect("/panel/error?error=Unauthorized access to Backups") return - server_id = nh3.clean(self.get_argument("id", None)) - zip_name = nh3.clean(self.get_argument("zip_file", None)) + server_id = bleach.clean(self.get_argument("id", None)) + zip_name = bleach.clean(self.get_argument("zip_file", None)) svr_obj = self.controller.servers.get_server_obj(server_id) server_data = self.controller.servers.get_server_data_by_id(server_id) @@ -652,7 +652,7 @@ class AjaxHandler(BaseHandler): if not self.check_server_id(server_id, "del_backup"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id) if not ( @@ -684,7 +684,7 @@ class AjaxHandler(BaseHandler): f"Server ID not defined in {page_name} ajax call ({server_id})" ) return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) # does this server id exist? if not self.controller.servers.server_id_exists(server_id): diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py index 11a62ff8..e772d633 100644 --- a/app/classes/web/base_handler.py +++ b/app/classes/web/base_handler.py @@ -2,7 +2,7 @@ import logging import re import typing as t import orjson -import nh3 +import bleach import tornado.web from app.classes.models.crafty_permissions import EnumPermissionsCrafty @@ -93,7 +93,7 @@ class BaseHandler(tornado.web.RequestHandler): if type(text) in self.nobleach: logger.debug("Auto-bleaching - bypass type") return text - return nh3.clean(text) + return bleach.clean(text) def get_argument( self, diff --git a/app/classes/web/file_handler.py b/app/classes/web/file_handler.py index f3c05151..e2d07476 100644 --- a/app/classes/web/file_handler.py +++ b/app/classes/web/file_handler.py @@ -1,6 +1,6 @@ import os import logging -import nh3 +import bleach import tornado.web import tornado.escape @@ -55,7 +55,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "get_file"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if not self.helper.is_subdir( file_path, @@ -92,7 +92,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "get_tree"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if Helpers.validate_traversal( self.controller.servers.get_server_data_by_id(server_id)["path"], path @@ -113,7 +113,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "get_tree"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if Helpers.validate_traversal( self.controller.servers.get_server_data_by_id(server_id)["path"], path @@ -161,7 +161,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "create_file"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if not self.helper.is_subdir( file_path, @@ -194,7 +194,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "create_dir"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if not self.helper.is_subdir( dir_path, @@ -259,7 +259,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "del_file"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id) if not ( @@ -293,7 +293,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "del_dir"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) server_info = self.controller.servers.get_server_data_by_id(server_id) if not self.helper.is_subdir( @@ -346,7 +346,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "save_file"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if not self.helper.is_subdir( file_path, @@ -401,7 +401,7 @@ class FileHandler(BaseHandler): if not self.check_server_id(server_id, "rename_file"): return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) if item_path is None or new_item_name is None: logger.warning("Invalid path(s) in rename_file file ajax call") @@ -450,7 +450,7 @@ class FileHandler(BaseHandler): f"Server ID not defined in {page_name} file ajax call ({server_id})" ) return - server_id = nh3.clean(server_id) + server_id = bleach.clean(server_id) # does this server id exist? if not self.controller.servers.server_id_exists(server_id): diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 0dc3c586..20c76c1a 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -7,7 +7,7 @@ import json import logging import threading import urllib.parse -import nh3 +import bleach import requests import tornado.web import tornado.escape @@ -67,7 +67,9 @@ class PanelHandler(BaseHandler): ) in self.controller.crafty_perms.list_defined_crafty_permissions(): argument = int( float( - nh3.clean(self.get_argument(f"permission_{permission.name}", "0")) + bleach.clean( + self.get_argument(f"permission_{permission.name}", "0") + ) ) ) if argument: @@ -76,7 +78,9 @@ class PanelHandler(BaseHandler): ) q_argument = int( - float(nh3.clean(self.get_argument(f"quantity_{permission.name}", "0"))) + float( + bleach.clean(self.get_argument(f"quantity_{permission.name}", "0")) + ) ) if q_argument: server_quantity[permission.name] = q_argument @@ -475,7 +479,7 @@ class PanelHandler(BaseHandler): template = "panel/dashboard.html" elif page == "server_detail": - subpage = nh3.clean(self.get_argument("subpage", "")) + subpage = bleach.clean(self.get_argument("subpage", "")) server_id = self.check_server_id() if server_id is None: @@ -1280,7 +1284,7 @@ class PanelHandler(BaseHandler): template = "panel/panel_edit_user_apikeys.html" elif page == "remove_user": - user_id = nh3.clean(self.get_argument("id", None)) + user_id = bleach.clean(self.get_argument("id", None)) if ( not superuser @@ -1412,7 +1416,7 @@ class PanelHandler(BaseHandler): template = "panel/panel_edit_role.html" elif page == "remove_role": - role_id = nh3.clean(self.get_argument("id", None)) + role_id = bleach.clean(self.get_argument("id", None)) if ( not superuser @@ -1600,7 +1604,7 @@ class PanelHandler(BaseHandler): backup_path = Helpers.wtol_path(backup_path) else: backup_path = server_obj.backup_path - max_backups = nh3.clean(self.get_argument("max_backups", None)) + max_backups = bleach.clean(self.get_argument("max_backups", None)) server_obj = self.controller.servers.get_server_obj(server_id) @@ -1661,15 +1665,15 @@ class PanelHandler(BaseHandler): self.redirect("/panel/config_json") elif page == "edit_user": - if nh3.clean(self.get_argument("username", None)).lower() == "system": + if bleach.clean(self.get_argument("username", None)).lower() == "system": self.redirect( "/panel/error?error=Unauthorized access: " "system user is not editable" ) - user_id = nh3.clean(self.get_argument("id", None)) + user_id = bleach.clean(self.get_argument("id", None)) user = self.controller.users.get_user_by_id(user_id) - username = nh3.clean(self.get_argument("username", None).lower()) - theme = nh3.clean(self.get_argument("theme", "default")) + username = bleach.clean(self.get_argument("username", None).lower()) + theme = bleach.clean(self.get_argument("theme", "default")) if ( username != self.controller.users.get_user_by_id(user_id)["username"] and username in self.controller.users.get_all_usernames() @@ -1677,16 +1681,16 @@ class PanelHandler(BaseHandler): self.redirect( "/panel/error?error=Duplicate User: Useranme already exists." ) - password0 = nh3.clean(self.get_argument("password0", None)) - password1 = nh3.clean(self.get_argument("password1", None)) - email = nh3.clean(self.get_argument("email", "default@example.com")) + password0 = bleach.clean(self.get_argument("password0", None)) + password1 = bleach.clean(self.get_argument("password1", None)) + email = bleach.clean(self.get_argument("email", "default@example.com")) enabled = int(float(self.get_argument("enabled", "0"))) try: - hints = int(nh3.clean(self.get_argument("hints"))) + hints = int(bleach.clean(self.get_argument("hints"))) hints = True except: hints = False - lang = nh3.clean( + lang = bleach.clean( self.get_argument("language"), self.helper.get_setting("language") ) @@ -1695,7 +1699,7 @@ class PanelHandler(BaseHandler): # We don't want that. Automatically make them stay super user # since we know they are. if str(exec_user["user_id"]) != str(user_id): - superuser = int(nh3.clean(self.get_argument("superuser", "0"))) + superuser = int(bleach.clean(self.get_argument("superuser", "0"))) else: superuser = 1 else: @@ -1873,7 +1877,7 @@ class PanelHandler(BaseHandler): self.finish() elif page == "add_user": - username = nh3.clean(self.get_argument("username", None).lower()) + username = bleach.clean(self.get_argument("username", None).lower()) if username.lower() == "system": self.redirect( "/panel/error?error=Unauthorized access: " @@ -1881,18 +1885,18 @@ class PanelHandler(BaseHandler): " Please choose a different username." ) return - password0 = nh3.clean(self.get_argument("password0", None)) - password1 = nh3.clean(self.get_argument("password1", None)) - email = nh3.clean(self.get_argument("email", "default@example.com")) + password0 = bleach.clean(self.get_argument("password0", None)) + password1 = bleach.clean(self.get_argument("password1", None)) + email = bleach.clean(self.get_argument("email", "default@example.com")) enabled = int(float(self.get_argument("enabled", "0"))) - theme = nh3.clean(self.get_argument("theme"), "default") + theme = bleach.clean(self.get_argument("theme"), "default") hints = True - lang = nh3.clean( + lang = bleach.clean( self.get_argument("lang", self.helper.get_setting("language")) ) # We don't want a non-super user to be able to create a super user. if superuser: - new_superuser = int(nh3.clean(self.get_argument("superuser", "0"))) + new_superuser = int(bleach.clean(self.get_argument("superuser", "0"))) else: new_superuser = 0 @@ -1967,8 +1971,8 @@ class PanelHandler(BaseHandler): self.redirect("/panel/panel_config") elif page == "edit_role": - role_id = nh3.clean(self.get_argument("id", None)) - role_name = nh3.clean(self.get_argument("role_name", None)) + role_id = bleach.clean(self.get_argument("id", None)) + role_name = bleach.clean(self.get_argument("role_name", None)) role = self.controller.roles.get_role(role_id) @@ -2014,7 +2018,7 @@ class PanelHandler(BaseHandler): self.redirect("/panel/panel_config") elif page == "add_role": - role_name = nh3.clean(self.get_argument("role_name", None)) + role_name = bleach.clean(self.get_argument("role_name", None)) if exec_user["superuser"]: manager = self.get_argument("manager", None) if manager == "": @@ -2088,7 +2092,7 @@ class PanelHandler(BaseHandler): } if page == "remove_apikey": - key_id = nh3.clean(self.get_argument("id", None)) + key_id = bleach.clean(self.get_argument("id", None)) if not superuser: self.redirect("/panel/error?error=Unauthorized access: not superuser") diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index b7d1be9b..76c6a8be 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -1,5 +1,5 @@ import logging -import nh3 +import bleach from app.classes.shared.helpers import Helpers from app.classes.models.users import HelperUsers @@ -28,8 +28,8 @@ class PublicHandler(BaseHandler): # self.clear_cookie("user_data") def get(self, page=None): - error = nh3.clean(self.get_argument("error", "Invalid Login!")) - error_msg = nh3.clean(self.get_argument("error_msg", "")) + error = bleach.clean(self.get_argument("error", "Invalid Login!")) + error_msg = bleach.clean(self.get_argument("error_msg", "")) page_data = { "version": self.helper.get_version_string(), @@ -82,8 +82,8 @@ class PublicHandler(BaseHandler): ) def post(self, page=None): - error = nh3.clean(self.get_argument("error", "Invalid Login!")) - error_msg = nh3.clean(self.get_argument("error_msg", "")) + error = bleach.clean(self.get_argument("error", "Invalid Login!")) + error_msg = bleach.clean(self.get_argument("error_msg", "")) page_data = { "version": self.helper.get_version_string(), @@ -100,8 +100,8 @@ class PublicHandler(BaseHandler): if self.request.query: next_page = "/login?" + self.request.query - entered_username = nh3.clean(self.get_argument("username")) - entered_password = nh3.clean(self.get_argument("password")) + entered_username = bleach.clean(self.get_argument("username")) + entered_password = bleach.clean(self.get_argument("password")) # pylint: disable=no-member try: diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index 42cb8ce5..eae3ce0c 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -4,7 +4,7 @@ import os import time import tornado.web import tornado.escape -import nh3 +import bleach from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.shared.helpers import Helpers @@ -195,8 +195,8 @@ class ServerHandler(BaseHandler): } if page == "command": - server_id = nh3.clean(self.get_argument("id", None)) - command = nh3.clean(self.get_argument("command", None)) + server_id = bleach.clean(self.get_argument("id", None)) + command = bleach.clean(self.get_argument("command", None)) if server_id is not None: if command == "clone_server": @@ -311,24 +311,24 @@ class ServerHandler(BaseHandler): user_roles = self.controller.roles.get_all_roles() else: user_roles = self.get_user_roles() - server = nh3.clean(self.get_argument("server", "")) - server_name = nh3.clean(self.get_argument("server_name", "")) - min_mem = nh3.clean(self.get_argument("min_memory", "")) - max_mem = nh3.clean(self.get_argument("max_memory", "")) - port = nh3.clean(self.get_argument("port", "")) + server = bleach.clean(self.get_argument("server", "")) + server_name = bleach.clean(self.get_argument("server_name", "")) + min_mem = bleach.clean(self.get_argument("min_memory", "")) + max_mem = bleach.clean(self.get_argument("max_memory", "")) + port = bleach.clean(self.get_argument("port", "")) if int(port) < 1 or int(port) > 65535: self.redirect( "/panel/error?error=Constraint Error: " "Port must be greater than 0 and less than 65535" ) return - import_type = nh3.clean(self.get_argument("create_type", "")) - import_server_path = nh3.clean(self.get_argument("server_path", "")) - import_server_jar = nh3.clean(self.get_argument("server_jar", "")) + import_type = bleach.clean(self.get_argument("create_type", "")) + import_server_path = bleach.clean(self.get_argument("server_path", "")) + import_server_jar = bleach.clean(self.get_argument("server_jar", "")) server_parts = server.split("|") captured_roles = [] for role in user_roles: - if nh3.clean(self.get_argument(str(role), "")) == "on": + if bleach.clean(self.get_argument(str(role), "")) == "on": captured_roles.append(role) if not server_name: @@ -372,7 +372,7 @@ class ServerHandler(BaseHandler): ) elif import_type == "import_zip": # here import_server_path means the zip path - zip_path = nh3.clean(self.get_argument("root_path")) + zip_path = bleach.clean(self.get_argument("root_path")) good_path = Helpers.check_path_exists(zip_path) if not good_path: self.redirect("/panel/error?error=Temp path not found!") @@ -476,9 +476,9 @@ class ServerHandler(BaseHandler): user_roles = self.controller.roles.get_all_roles() else: user_roles = self.controller.roles.get_all_roles() - server = nh3.clean(self.get_argument("server", "")) - server_name = nh3.clean(self.get_argument("server_name", "")) - port = nh3.clean(self.get_argument("port", "")) + server = bleach.clean(self.get_argument("server", "")) + server_name = bleach.clean(self.get_argument("server_name", "")) + port = bleach.clean(self.get_argument("port", "")) if not port: port = 19132 @@ -488,13 +488,13 @@ class ServerHandler(BaseHandler): "Port must be greater than 0 and less than 65535" ) return - import_type = nh3.clean(self.get_argument("create_type", "")) - import_server_path = nh3.clean(self.get_argument("server_path", "")) - import_server_exe = nh3.clean(self.get_argument("server_jar", "")) + import_type = bleach.clean(self.get_argument("create_type", "")) + import_server_path = bleach.clean(self.get_argument("server_path", "")) + import_server_exe = bleach.clean(self.get_argument("server_jar", "")) server_parts = server.split("|") captured_roles = [] for role in user_roles: - if nh3.clean(self.get_argument(str(role), "")) == "on": + if bleach.clean(self.get_argument(str(role), "")) == "on": captured_roles.append(role) if not server_name: @@ -536,7 +536,7 @@ class ServerHandler(BaseHandler): ) elif import_type == "import_zip": # here import_server_path means the zip path - zip_path = nh3.clean(self.get_argument("root_path")) + zip_path = bleach.clean(self.get_argument("root_path")) good_path = Helpers.check_path_exists(zip_path) if not good_path: self.redirect("/panel/error?error=Temp path not found!") diff --git a/requirements.txt b/requirements.txt index df3360a0..98e095f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ apscheduler==3.8.1 argon2-cffi==21.3 -nh3==0.2.14 +bleach==4.1 cached_property==1.5.2 colorama==0.4 croniter==1.3.5 From 6ea46277bbf69d83f60a65aa5eb670f6ce4e152e Mon Sep 17 00:00:00 2001 From: Zedifus Date: Tue, 5 Sep 2023 00:22:49 +0100 Subject: [PATCH 09/11] Revert changelog !616 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1245e3ff..a82d64aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ TBD - Fix hidden servers appearing visible on public mobile status page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/612)) - Correctly handle if a server returns a string instead of json data on socket ping ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/614)) ### Refactor -- Refractor/Replace bleach with nh3 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/616)) +TBD ### Tweaks - Polish/Enhance display for InApp Documentation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/613)) - Add get_users command to Crafty's console ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/620)) From 88dada0b91ad0e701bb1f647a94f7d23516f155d Mon Sep 17 00:00:00 2001 From: Zedifus Date: Tue, 5 Sep 2023 00:30:11 +0100 Subject: [PATCH 10/11] Reversion release 4.2.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a82d64aa..76e569e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## --- [4.1.4] - 2023/TBD +## --- [4.2.0] - 2023/TBD ### New features TBD ### Bug fixes From 8664876c1bb377bc8754c5217a13a347a5852208 Mon Sep 17 00:00:00 2001 From: Zedifus Date: Tue, 5 Sep 2023 00:43:52 +0100 Subject: [PATCH 11/11] Update changelog !623 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76e569e5..431e5cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ TBD - PWA: Removed the custom offline page in favour of browser default ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/607)) - Fix hidden servers appearing visible on public mobile status page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/612)) - Correctly handle if a server returns a string instead of json data on socket ping ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/614)) +- Bump tornado to resolve #269 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/623)) ### Refactor TBD ### Tweaks