From ccfbad91d1af2a500b3666a703b57f0cc55d8957 Mon Sep 17 00:00:00 2001 From: amcmanu3 Date: Sun, 19 Jun 2022 23:07:48 -0400 Subject: [PATCH 01/25] Check if ping result is boolean --- app/classes/minecraft/stats.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/classes/minecraft/stats.py b/app/classes/minecraft/stats.py index 4b699717..c0261198 100644 --- a/app/classes/minecraft/stats.py +++ b/app/classes/minecraft/stats.py @@ -265,15 +265,24 @@ class Stats: logger.info( "Unable to read the server icon due to the following error:", exc_info=e ) - - ping_data = { - "online": online_stats.get("online", 0), - "max": online_stats.get("max", 0), - "players": online_stats.get("players", 0), - "server_description": ping_obj.description, - "server_version": ping_obj.version, - "server_icon": server_icon, - } + if ping_obj: + ping_data = { + "online": online_stats.get("online", 0), + "max": online_stats.get("max", 0), + "players": online_stats.get("players", 0), + "server_description": ping_obj.description, + "server_version": ping_obj.version, + "server_icon": server_icon, + } + else: + ping_data = { + "online": online_stats.get("online", 0), + "max": online_stats.get("max", 0), + "players": online_stats.get("players", 0), + "server_description": "", + "server_version": "", + "server_icon": server_icon, + } return ping_data From e820ba058af234a92ec2a686c7dad323afafd66c Mon Sep 17 00:00:00 2001 From: Zedifus Date: Mon, 20 Jun 2022 18:05:39 +0100 Subject: [PATCH 02/25] Add prototype helper (linux only) --- app/classes/shared/helpers.py | 15 +++++++++++++++ main.py | 1 + 2 files changed, 16 insertions(+) diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 31273a60..c870c918 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -15,6 +15,7 @@ import html import zipfile import pathlib import ctypes +import subprocess from datetime import datetime from socket import gethostname from contextlib import redirect_stderr, suppress @@ -81,6 +82,20 @@ class Helpers: print(f"Import Error: Unable to load {ex.name} module") installer.do_install() + @staticmethod + def find_java_installs(): + try: + paths = subprocess.check_output( + ["/usr/bin/update-alternatives", "--list", "java"], encoding="utf8" + ) + + if re.match("^(/[^/ ]*)+/?$", paths): + return paths.split("\n") + + except Exception as e: + print("Java Detect Error: ", e) + logger.error(f"Java Detect Error: {e}") + @staticmethod def float_to_string(gbs: float): s = str(float(gbs) * 1000).rstrip("0").rstrip(".") diff --git a/main.py b/main.py index dec696d0..9e1284e9 100644 --- a/main.py +++ b/main.py @@ -224,6 +224,7 @@ if __name__ == "__main__": controller_setup_thread.join() Console.info("Crafty has fully started and is now ready for use!") + console.debug(helper.find_java_installs()) crafty_prompt.prompt = f"Crafty Controller v{helper.get_version_string()} > " if not args.daemon: From 7d286e60e084a2bdf81d1146a668b5b88b4f6de3 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 20 Jun 2022 19:26:21 +0000 Subject: [PATCH 03/25] Backup/Config.json rework for API key hardening See merge request crafty-controller/crafty-4!369 --- CHANGELOG.md | 9 +- .../controllers/management_controller.py | 8 + app/classes/controllers/servers_controller.py | 5 +- app/classes/minecraft/stats.py | 2 +- app/classes/models/management.py | 21 ++ app/classes/shared/authentication.py | 12 +- app/classes/shared/file_helpers.py | 179 +++++++++++++++++- app/classes/shared/helpers.py | 61 +----- app/classes/shared/main_controller.py | 5 +- app/classes/shared/main_models.py | 2 - app/classes/shared/server.py | 66 ++----- app/classes/web/file_handler.py | 2 +- app/classes/web/panel_handler.py | 7 +- app/classes/web/server_handler.py | 16 ++ .../templates/panel/server_backup.html | 78 +++++--- app/migrations/20220618_crafty_api_secret.py | 16 ++ main.py | 5 +- 17 files changed, 336 insertions(+), 158 deletions(-) create mode 100644 app/migrations/20220618_crafty_api_secret.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ed786255..42e06868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### New features None ### Bug fixes -None +Backup/Config.json rework for API key hardening ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/369)) ### Tweaks Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/370))

@@ -31,10 +31,6 @@ Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/c ### Bug fixes - Fix winreg import pass on non-NT systems ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/344)) - Make the WebSocket automatically reconnect. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/345)) -- Fix an error when there are no servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/346)) -- Use relative paths for the jarfile and logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/347)) -- Flatten all instances of username creation or editing, usernames should be lower case. -- - ([Merge Request 1](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342)) - - ([Merge Request 2](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/351)) - Add version inheretence & config check ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/353)) - Fix support log temp file deletion issue/hang ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/354)) @@ -47,6 +43,3 @@ Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/c - Remove session.lock warning ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/338)) - Correct Dutch Spacing Issue ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/340)) - Remove no-else-* pylint exemptions and tidy code. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342)) -- Make unRAID more readable, and flatten path to lower, to fit standard practice. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/337)) -- Fix Java Pathing issues on windows ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/343/diffs?commit_id=cda2120579083d447db5dbeb5489822880f4cae7)) - diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 099dbf0d..fdbdcced 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -17,6 +17,14 @@ class ManagementController: def get_latest_hosts_stats(): return HelpersManagement.get_latest_hosts_stats() + @staticmethod + def set_crafty_api_key(key): + HelpersManagement.set_secret_api_key(key) + + @staticmethod + def get_crafty_api_key(): + return HelpersManagement.get_secret_api_key() + # ********************************************************************************** # Commands Methods # ********************************************************************************** diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index fc8ff3e8..ca59fbdc 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -5,6 +5,7 @@ import json import typing as t from app.classes.controllers.roles_controller import RolesController +from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.singleton import Singleton from app.classes.shared.server import ServerInstance @@ -28,8 +29,9 @@ logger = logging.getLogger(__name__) class ServersController(metaclass=Singleton): servers_list: ServerInstance - def __init__(self, helper, servers_helper, management_helper): + def __init__(self, helper, servers_helper, management_helper, file_helper): self.helper: Helpers = helper + self.file_helper: FileHelpers = file_helper self.servers_helper: HelperServers = servers_helper self.management_helper = management_helper self.servers_list = [] @@ -189,6 +191,7 @@ class ServersController(metaclass=Singleton): self.helper, self.management_helper, self.stats, + self.file_helper, ), "server_settings": settings.props, } diff --git a/app/classes/minecraft/stats.py b/app/classes/minecraft/stats.py index 4b699717..3c94a7dd 100644 --- a/app/classes/minecraft/stats.py +++ b/app/classes/minecraft/stats.py @@ -218,7 +218,7 @@ class Stats: return level_total_size - def get_server_players(self, server_id): # pylint: disable=no-self-use + def get_server_players(self, server_id): server = HelperServers.get_server_data_by_id(server_id) diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 6cdf7a8a..ef9fab0a 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -38,6 +38,16 @@ class AuditLog(BaseModel): table_name = "audit_log" +# ********************************************************************************** +# Crafty Settings Class +# ********************************************************************************** +class CraftySettings(BaseModel): + secret_api_key = CharField(default="") + + class Meta: + table_name = "crafty_settings" + + # ********************************************************************************** # Host_Stats Class # ********************************************************************************** @@ -231,6 +241,17 @@ class HelpersManagement: else: return + @staticmethod + def set_secret_api_key(key): + CraftySettings.insert(secret_api_key=key).execute() + + @staticmethod + def get_secret_api_key(): + settings = CraftySettings.select(CraftySettings.secret_api_key).where( + CraftySettings.id == 1 + ) + return settings[0].secret_api_key + # ********************************************************************************** # Schedules Methods # ********************************************************************************** diff --git a/app/classes/shared/authentication.py b/app/classes/shared/authentication.py index 330a8883..d9b2c053 100644 --- a/app/classes/shared/authentication.py +++ b/app/classes/shared/authentication.py @@ -5,6 +5,7 @@ import jwt from jwt import PyJWTError from app.classes.models.users import HelperUsers, ApiKeys +from app.classes.controllers.management_controller import ManagementController logger = logging.getLogger(__name__) @@ -13,11 +14,14 @@ class Authentication: def __init__(self, helper): self.helper = helper self.secret = "my secret" - self.secret = self.helper.get_setting("apikey_secret", None) - - if self.secret is None or self.secret == "random": + try: + self.secret = ManagementController.get_crafty_api_key() + if self.secret == "": + self.secret = self.helper.random_string_generator(64) + ManagementController.set_crafty_api_key(str(self.secret)) + except: self.secret = self.helper.random_string_generator(64) - self.helper.set_setting("apikey_secret", self.secret) + ManagementController.set_crafty_api_key(str(self.secret)) def generate(self, user_id, extra=None): if extra is None: diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index dad6ddef..620b34f8 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -2,14 +2,22 @@ import os import shutil import logging import pathlib +import tempfile +import zipfile from zipfile import ZipFile, ZIP_DEFLATED +from app.classes.shared.helpers import Helpers +from app.classes.shared.console import Console + logger = logging.getLogger(__name__) class FileHelpers: allowed_quotes = ['"', "'", "`"] + def __init__(self, helper): + self.helper: Helpers = helper + @staticmethod def del_dirs(path): path = pathlib.Path(path) @@ -82,7 +90,6 @@ class FileHelpers: f"Error backing up: {os.path.join(root, file)}!" f" - Error was: {e}" ) - return True @staticmethod @@ -113,3 +120,173 @@ class FileHelpers: ) return True + + def make_compressed_backup( + self, path_to_destination, path_to_zip, excluded_dirs, server_id + ): + # create a ZipFile object + path_to_destination += ".zip" + ex_replace = [p.replace("\\", "/") for p in excluded_dirs] + total_bytes = 0 + dir_bytes = Helpers.get_dir_size(path_to_zip) + results = { + "percent": 0, + "total_files": self.helper.human_readable_file_size(dir_bytes), + } + self.helper.websocket_helper.broadcast_page_params( + "/panel/server_detail", + {"id": str(server_id)}, + "backup_status", + results, + ) + with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file: + for root, dirs, files in os.walk(path_to_zip, topdown=True): + for l_dir in dirs: + if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace: + dirs.remove(l_dir) + ziproot = path_to_zip + for file in files: + if ( + str(os.path.join(root, file)).replace("\\", "/") + not in ex_replace + and file != "crafty.sqlite" + ): + try: + logger.info(f"backing up: {os.path.join(root, file)}") + if os.name == "nt": + zip_file.write( + os.path.join(root, file), + os.path.join(root.replace(ziproot, ""), file), + ) + else: + zip_file.write( + os.path.join(root, file), + os.path.join(root.replace(ziproot, "/"), file), + ) + + except Exception as e: + logger.warning( + f"Error backing up: {os.path.join(root, file)}!" + f" - Error was: {e}" + ) + total_bytes += os.path.getsize(os.path.join(root, file)) + percent = round((total_bytes / dir_bytes) * 100, 2) + results = { + "percent": percent, + "total_files": self.helper.human_readable_file_size(dir_bytes), + } + self.helper.websocket_helper.broadcast_page_params( + "/panel/server_detail", + {"id": str(server_id)}, + "backup_status", + results, + ) + + return True + + def make_backup(self, path_to_destination, path_to_zip, excluded_dirs, server_id): + # create a ZipFile object + path_to_destination += ".zip" + ex_replace = [p.replace("\\", "/") for p in excluded_dirs] + total_bytes = 0 + dir_bytes = Helpers.get_dir_size(path_to_zip) + results = { + "percent": 0, + "total_files": self.helper.human_readable_file_size(dir_bytes), + } + self.helper.websocket_helper.broadcast_page_params( + "/panel/server_detail", + {"id": str(server_id)}, + "backup_status", + results, + ) + with ZipFile(path_to_destination, "w") as zip_file: + for root, dirs, files in os.walk(path_to_zip, topdown=True): + for l_dir in dirs: + if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace: + dirs.remove(l_dir) + ziproot = path_to_zip + for file in files: + if ( + str(os.path.join(root, file)).replace("\\", "/") + not in ex_replace + and file != "crafty.sqlite" + ): + try: + logger.info(f"backing up: {os.path.join(root, file)}") + if os.name == "nt": + zip_file.write( + os.path.join(root, file), + os.path.join(root.replace(ziproot, ""), file), + ) + else: + zip_file.write( + os.path.join(root, file), + os.path.join(root.replace(ziproot, "/"), file), + ) + + except Exception as e: + logger.warning( + f"Error backing up: {os.path.join(root, file)}!" + f" - Error was: {e}" + ) + total_bytes += os.path.getsize(os.path.join(root, file)) + percent = round((total_bytes / dir_bytes) * 100, 2) + results = { + "percent": percent, + "total_files": self.helper.human_readable_file_size(dir_bytes), + } + self.helper.websocket_helper.broadcast_page_params( + "/panel/server_detail", + {"id": str(server_id)}, + "backup_status", + results, + ) + return True + + @staticmethod + def unzip_file(zip_path): + new_dir_list = zip_path.split("/") + new_dir = "" + for i in range(len(new_dir_list) - 1): + if i == 0: + new_dir += new_dir_list[i] + else: + new_dir += "/" + new_dir_list[i] + + if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path): + Helpers.ensure_dir_exists(new_dir) + temp_dir = tempfile.mkdtemp() + try: + with zipfile.ZipFile(zip_path, "r") as zip_ref: + zip_ref.extractall(temp_dir) + for i in enumerate(zip_ref.filelist): + if len(zip_ref.filelist) > 1 or not zip_ref.filelist[ + i + ].filename.endswith("/"): + break + + full_root_path = temp_dir + + for item in os.listdir(full_root_path): + if os.path.isdir(os.path.join(full_root_path, item)): + try: + FileHelpers.move_dir( + os.path.join(full_root_path, item), + os.path.join(new_dir, item), + ) + except Exception as ex: + logger.error(f"ERROR IN ZIP IMPORT: {ex}") + else: + try: + FileHelpers.move_file( + os.path.join(full_root_path, item), + os.path.join(new_dir, item), + ) + except Exception as ex: + logger.error(f"ERROR IN ZIP IMPORT: {ex}") + except Exception as ex: + Console.error(ex) + else: + return "false" + return diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 31273a60..c59841fd 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -22,7 +22,6 @@ from contextlib import redirect_stderr, suppress from app.classes.shared.null_writer import NullWriter from app.classes.shared.console import Console from app.classes.shared.installer import installer -from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.translation import Translation from app.classes.web.websocket_helper import WebSocketHelper @@ -441,53 +440,6 @@ class Helpers: return ctypes.windll.shell32.IsUserAnAdmin() == 1 return os.geteuid() == 0 - @staticmethod - def unzip_file(zip_path): - new_dir_list = zip_path.split("/") - new_dir = "" - for i in range(len(new_dir_list) - 1): - if i == 0: - new_dir += new_dir_list[i] - else: - new_dir += "/" + new_dir_list[i] - - if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path): - Helpers.ensure_dir_exists(new_dir) - temp_dir = tempfile.mkdtemp() - try: - with zipfile.ZipFile(zip_path, "r") as zip_ref: - zip_ref.extractall(temp_dir) - for i in enumerate(zip_ref.filelist): - if len(zip_ref.filelist) > 1 or not zip_ref.filelist[ - i - ].filename.endswith("/"): - break - - full_root_path = temp_dir - - for item in os.listdir(full_root_path): - if os.path.isdir(os.path.join(full_root_path, item)): - try: - FileHelpers.move_dir( - os.path.join(full_root_path, item), - os.path.join(new_dir, item), - ) - except Exception as ex: - logger.error(f"ERROR IN ZIP IMPORT: {ex}") - else: - try: - FileHelpers.move_file( - os.path.join(full_root_path, item), - os.path.join(new_dir, item), - ) - except Exception as ex: - logger.error(f"ERROR IN ZIP IMPORT: {ex}") - except Exception as ex: - Console.error(ex) - else: - return "false" - return - def ensure_logging_setup(self): log_file = os.path.join(os.path.curdir, "logs", "commander.log") session_log_file = os.path.join(os.path.curdir, "logs", "session.log") @@ -832,7 +784,7 @@ class Helpers: for item in file_list: if os.path.isdir(os.path.join(folder, item)): dir_list.append(item) - else: + elif str(item) != "crafty.sqlite": unsorted_files.append(item) file_list = sorted(dir_list, key=str.casefold) + sorted( unsorted_files, key=str.casefold @@ -863,13 +815,14 @@ class Helpers: @staticmethod def generate_dir(folder, output=""): + dir_list = [] unsorted_files = [] file_list = os.listdir(folder) for item in file_list: if os.path.isdir(os.path.join(folder, item)): dir_list.append(item) - else: + elif str(item) != "crafty.sqlite": unsorted_files.append(item) file_list = sorted(dir_list, key=str.casefold) + sorted( unsorted_files, key=str.casefold @@ -986,14 +939,6 @@ class Helpers: [parent_path, child_path] ) - @staticmethod - def copy_files(source, dest): - if os.path.isfile(source): - FileHelpers.copy_file(source, dest) - logger.info("Copying jar %s to %s", source, dest) - else: - logger.info("Source jar does not exist.") - @staticmethod def download_file(executable_url, jar_path): try: diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 39db11cd..ffe8ee0c 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -34,8 +34,9 @@ logger = logging.getLogger(__name__) class Controller: - def __init__(self, database, helper): + def __init__(self, database, helper, file_helper): self.helper: Helpers = helper + self.file_helper: FileHelpers = file_helper self.server_jars: ServerJars = ServerJars(helper) self.users_helper: HelperUsers = HelperUsers(database, self.helper) self.roles_helper: HelperRoles = HelperRoles(database) @@ -53,7 +54,7 @@ class Controller: ) self.server_perms: ServerPermsController = ServerPermsController() self.servers: ServersController = ServersController( - self.helper, self.servers_helper, self.management_helper + self.helper, self.servers_helper, self.management_helper, self.file_helper ) self.users: UsersController = UsersController( self.helper, self.users_helper, self.authentication diff --git a/app/classes/shared/main_models.py b/app/classes/shared/main_models.py index 5e809c48..ae4636c2 100644 --- a/app/classes/shared/main_models.py +++ b/app/classes/shared/main_models.py @@ -17,8 +17,6 @@ class DatabaseBuilder: logger.info("Fresh Install Detected - Creating Default Settings") Console.info("Fresh Install Detected - Creating Default Settings") default_data = self.helper.find_default_password() - # Reset this value if the DB has been dumped - self.helper.set_setting("apikey_secret", "random") username = default_data.get("username", "admin") password = default_data.get("password", "crafty") diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index eb43ff47..230cd3ba 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -9,7 +9,6 @@ import threading import logging.config import subprocess import html -import tempfile # TZLocal is set as a hidden import on win pipeline from tzlocal import get_localzone @@ -102,12 +101,14 @@ class ServerOutBuf: class ServerInstance: server_object: Servers helper: Helpers + file_helper: FileHelpers management_helper: HelpersManagement stats: Stats stats_helper: HelperServerStats - def __init__(self, server_id, helper, management_helper, stats): + def __init__(self, server_id, helper, management_helper, stats, file_helper): self.helper = helper + self.file_helper = file_helper self.management_helper = management_helper # holders for our process self.process = None @@ -869,62 +870,27 @@ class ServerInstance: f" (ID#{self.server_id}, path={self.server_path}) " f"at '{backup_filename}'" ) - - temp_dir = tempfile.mkdtemp() - self.server_scheduler.add_job( - self.backup_status, - "interval", - seconds=1, - id="backup_" + str(self.server_id), - args=[temp_dir + "/", backup_filename + ".zip"], - ) - # pylint: disable=unexpected-keyword-arg - try: - FileHelpers.copy_dir(self.server_path, temp_dir, dirs_exist_ok=True) - except shutil.Error as e: - logger.error(f"Failed to fully complete backup due to shutil error {e}") excluded_dirs = HelpersManagement.get_excluded_backup_dirs(self.server_id) server_dir = Helpers.get_os_understandable_path(self.settings["path"]) - - for my_dir in excluded_dirs: - # Take the full path of the excluded dir and replace the - # server path with the temp path, this is so that we're - # only deleting excluded dirs from the temp path - # and not the server path - excluded_dir = Helpers.get_os_understandable_path(my_dir).replace( - server_dir, Helpers.get_os_understandable_path(temp_dir) - ) - # Next, check to see if it is a directory - if os.path.isdir(excluded_dir): - # If it is a directory, - # recursively delete the entire directory from the backup - try: - FileHelpers.del_dirs(excluded_dir) - except FileNotFoundError: - Console.error( - f"Excluded dir {excluded_dir} not found. Moving on..." - ) - else: - # If not, just remove the file - try: - os.remove(excluded_dir) - except: - Console.error( - f"Excluded dir {excluded_dir} not found. Moving on..." - ) if conf["compress"]: logger.debug( "Found compress backup to be true. Calling compressed archive" ) - FileHelpers.make_compressed_archive( - Helpers.get_os_understandable_path(backup_filename), temp_dir + self.file_helper.make_compressed_backup( + Helpers.get_os_understandable_path(backup_filename), + server_dir, + excluded_dirs, + self.server_id, ) else: logger.debug( "Found compress backup to be false. Calling NON-compressed archive" ) - FileHelpers.make_archive( - Helpers.get_os_understandable_path(backup_filename), temp_dir + self.file_helper.make_backup( + Helpers.get_os_understandable_path(backup_filename), + server_dir, + excluded_dirs, + self.server_id, ) while ( @@ -939,7 +905,6 @@ class ServerInstance: self.is_backingup = False logger.info(f"Backup of server: {self.name} completed") - self.server_scheduler.remove_job("backup_" + str(self.server_id)) results = {"percent": 100, "total_files": 0, "current_file": 0} if len(self.helper.websocket_helper.clients) > 0: self.helper.websocket_helper.broadcast_page_params( @@ -964,7 +929,6 @@ class ServerInstance: logger.exception( f"Failed to create backup of server {self.name} (ID {self.server_id})" ) - self.server_scheduler.remove_job("backup_" + str(self.server_id)) results = {"percent": 100, "total_files": 0, "current_file": 0} if len(self.helper.websocket_helper.clients) > 0: self.helper.websocket_helper.broadcast_page_params( @@ -974,8 +938,6 @@ class ServerInstance: results, ) self.is_backingup = False - finally: - FileHelpers.del_dirs(temp_dir) def backup_status(self, source_path, dest_path): results = Helpers.calc_percent(source_path, dest_path) @@ -1093,7 +1055,7 @@ class ServerInstance: ) # copies to backup dir - Helpers.copy_files(current_executable, backup_executable) + FileHelpers.copy_file(current_executable, backup_executable) # boolean returns true for false for success downloaded = Helpers.download_file( diff --git a/app/classes/web/file_handler.py b/app/classes/web/file_handler.py index 886441ed..e1131c3c 100644 --- a/app/classes/web/file_handler.py +++ b/app/classes/web/file_handler.py @@ -220,7 +220,7 @@ class FileHandler(BaseHandler): path = Helpers.get_os_understandable_path(self.get_argument("path", None)) if Helpers.is_os_windows(): path = Helpers.wtol_path(path) - Helpers.unzip_file(path) + FileHelpers.unzip_file(path) self.redirect(f"/panel/server_detail?id={server_id}&subpage=files") return diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 9dfbb17a..26456c19 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -1342,6 +1342,8 @@ class PanelHandler(BaseHandler): if Helpers.is_os_windows(): log_path.replace(" ", "^ ") log_path = Helpers.wtol_path(log_path) + if not self.helper.validate_traversal(server_obj.path, log_path): + log_path = "" executable = self.get_argument("executable", None) execution_command = self.get_argument("execution_command", None) server_ip = self.get_argument("server_ip", None) @@ -1941,7 +1943,10 @@ class PanelHandler(BaseHandler): self.redirect("/panel/error?error=Invalid Key ID") return - if key.user_id != exec_user["user_id"]: + if ( + str(key.user_id) != str(exec_user["user_id"]) + and not exec_user["superuser"] + ): self.redirect( "/panel/error?error=You are not authorized to access this key." ) diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py index bf4e930d..4d961ad9 100644 --- a/app/classes/web/server_handler.py +++ b/app/classes/web/server_handler.py @@ -271,6 +271,14 @@ class ServerHandler(BaseHandler): ) if page == "step1": + if not superuser and not self.controller.crafty_perms.can_create_server( + exec_user["user_id"] + ): + self.redirect( + "/panel/error?error=Unauthorized access: " + "not a server creator or server limit reached" + ) + return if not superuser: user_roles = self.controller.roles.get_all_roles() @@ -396,6 +404,14 @@ class ServerHandler(BaseHandler): self.redirect("/panel/dashboard") if page == "bedrock_step1": + if not superuser and not self.controller.crafty_perms.can_create_server( + exec_user["user_id"] + ): + self.redirect( + "/panel/error?error=Unauthorized access: " + "not a server creator or server limit reached" + ) + return if not superuser: user_roles = self.controller.roles.get_all_roles() else: diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index eb820086..bb7446f9 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -14,7 +14,8 @@