diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py index 4005e965..27d68141 100644 --- a/app/classes/shared/file_helpers.py +++ b/app/classes/shared/file_helpers.py @@ -325,3 +325,12 @@ class FileHelpers: else: return "false" return + + def unzip_server(self, zip_path, user_id): + if Helpers.check_file_perms(zip_path): + temp_dir = tempfile.mkdtemp() + with zipfile.ZipFile(zip_path, "r") as zip_ref: + # extracts archive to temp directory + zip_ref.extractall(temp_dir) + if user_id: + return temp_dir diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 9acf1187..576dc654 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -1135,17 +1135,6 @@ class Helpers:
  • """ return output - def unzip_server(self, zip_path, user_id): - if Helpers.check_file_perms(zip_path): - temp_dir = tempfile.mkdtemp() - with zipfile.ZipFile(zip_path, "r") as zip_ref: - # extracts archive to temp directory - zip_ref.extractall(temp_dir) - if user_id: - self.websocket_helper.broadcast_user( - user_id, "send_temp_path", {"path": temp_dir} - ) - @staticmethod def unzip_backup_archive(backup_path, zip_name): zip_path = os.path.join(backup_path, zip_name) diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index acdc1cac..cc2a8671 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -41,10 +41,10 @@ scheduler_intervals = { class TasksManager: controller: Controller - def __init__(self, helper, controller): + def __init__(self, helper, controller, file_helper): self.helper: Helpers = helper self.controller: Controller = controller - self.tornado: Webserver = Webserver(helper, controller, self) + self.tornado: Webserver = Webserver(helper, controller, self, file_helper) try: self.tz = get_localzone() except ZoneInfoNotFoundError as e: diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index e38648f4..0fa17dcf 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -9,7 +9,7 @@ import bleach import tornado.web import tornado.escape -from app.classes.shared.console import Console +from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.helpers import Helpers from app.classes.shared.server import ServerOutBuf from app.classes.web.base_handler import BaseHandler @@ -148,32 +148,6 @@ class AjaxHandler(BaseHandler): self.controller.cached_login = "login_1.jpg" return - elif page == "unzip_server": - path = urllib.parse.unquote(self.get_argument("path", "")) - if not path: - path = os.path.join( - self.controller.project_root, - "imports", - urllib.parse.unquote(self.get_argument("file", "")), - ) - if Helpers.check_file_exists(path): - self.helper.unzip_server(path, exec_user["user_id"]) - else: - user_id = exec_user["user_id"] - if user_id: - time.sleep(5) - user_lang = self.controller.users.get_user_lang_by_id(user_id) - self.helper.websocket_helper.broadcast_user( - user_id, - "send_start_error", - { - "error": self.helper.translation.translate( - "error", "no-file", user_lang - ) - }, - ) - return - elif page == "jar_cache": if not superuser: self.redirect("/panel/error?error=Not a super user") @@ -208,25 +182,3 @@ class AjaxHandler(BaseHandler): new_dir = urllib.parse.unquote(self.get_argument("server_dir")) self.controller.update_master_server_dir(new_dir, exec_user["user_id"]) return - - def check_server_id(self, server_id, page_name): - if server_id is None: - logger.warning( - f"Server ID not defined in {page_name} ajax call ({server_id})" - ) - Console.warning( - f"Server ID not defined in {page_name} ajax call ({server_id})" - ) - return - server_id = bleach.clean(server_id) - - # does this server id exist? - if not self.controller.servers.server_id_exists(server_id): - logger.warning( - f"Server ID not found in {page_name} ajax call ({server_id})" - ) - Console.warning( - f"Server ID not found in {page_name} ajax call ({server_id})" - ) - return - return True diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py index e772d633..33fe9936 100644 --- a/app/classes/web/base_handler.py +++ b/app/classes/web/base_handler.py @@ -8,6 +8,7 @@ import tornado.web from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.models.users import ApiKeys from app.classes.shared.helpers import Helpers +from app.classes.shared.file_helpers import FileHelpers from app.classes.shared.main_controller import Controller from app.classes.shared.translation import Translation from app.classes.models.management import DatabaseShortcuts @@ -24,15 +25,22 @@ class BaseHandler(tornado.web.RequestHandler): helper: Helpers controller: Controller translator: Translation + file_helper: FileHelpers # noinspection PyAttributeOutsideInit def initialize( - self, helper=None, controller=None, tasks_manager=None, translator=None + self, + helper=None, + controller=None, + tasks_manager=None, + translator=None, + file_helper=None, ): self.helper = helper self.controller = controller self.tasks_manager = tasks_manager self.translator = translator + self.file_helper = file_helper def set_default_headers(self) -> None: """ diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py index 43d21d08..102161ab 100644 --- a/app/classes/web/routes/api/api_handlers.py +++ b/app/classes/web/routes/api/api_handlers.py @@ -51,6 +51,7 @@ from app.classes.web.routes.api.users.user.pfp import ApiUsersUserPfpHandler from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandler from app.classes.web.routes.api.crafty.config.index import ApiCraftyConfigIndexHandler from app.classes.web.routes.api.crafty.clogs.index import ApiCraftyLogIndexHandler +from app.classes.web.routes.api.crafty.imports.index import ApiImportFilesIndexHandler def api_handlers(handler_args): @@ -76,6 +77,11 @@ def api_handlers(handler_args): ApiCraftyLogIndexHandler, handler_args, ), + ( + r"/api/v2/import/file/unzip/?", + ApiImportFilesIndexHandler, + handler_args, + ), # User routes ( r"/api/v2/users/?", diff --git a/app/classes/web/routes/api/crafty/imports/index.py b/app/classes/web/routes/api/crafty/imports/index.py new file mode 100644 index 00000000..3bcae9af --- /dev/null +++ b/app/classes/web/routes/api/crafty/imports/index.py @@ -0,0 +1,123 @@ +import os +import logging +import json +import html +from jsonschema import validate +from jsonschema.exceptions import ValidationError +from app.classes.models.crafty_permissions import EnumPermissionsCrafty +from app.classes.shared.helpers import Helpers +from app.classes.web.base_api_handler import BaseApiHandler + +logger = logging.getLogger(__name__) +files_get_schema = { + "type": "object", + "properties": { + "page": {"type": "string", "minLength": 1}, + "folder": {"type": "string"}, + "unzip": {"type": "boolean", "default": "True"}, + }, + "additionalProperties": False, + "minProperties": 1, +} + + +class ApiImportFilesIndexHandler(BaseApiHandler): + def post(self): + auth_data = self.authenticate_user() + if not auth_data: + return + + if ( + EnumPermissionsCrafty.SERVER_CREATION + not in self.controller.crafty_perms.get_crafty_permissions_list( + auth_data[4]["user_id"] + ) + and not auth_data[4]["superuser"] + ): + # if the user doesn't have Files or Backup permission, return an error + return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) + + try: + data = json.loads(self.request.body) + except json.decoder.JSONDecodeError as e: + return self.finish_json( + 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} + ) + try: + validate(data, files_get_schema) + except ValidationError as e: + return self.finish_json( + 400, + { + "status": "error", + "error": "INVALID_JSON_SCHEMA", + "error_data": str(e), + }, + ) + # TODO: limit some columns for specific permissions? + folder = data["folder"] + user_id = auth_data[4]["user_id"] + root_path = False + if data["unzip"]: + if Helpers.check_file_exists(folder): + folder = self.file_helper.unzip_server(folder, user_id) + root_path = True + else: + if user_id: + user_lang = self.controller.users.get_user_lang_by_id(user_id) + self.helper.websocket_helper.broadcast_user( + user_id, + "send_start_error", + { + "error": self.helper.translation.translate( + "error", "no-file", user_lang + ) + }, + ) + else: + if not self.helper.check_path_exists(folder): + if user_id: + user_lang = self.controller.users.get_user_lang_by_id(user_id) + self.helper.websocket_helper.broadcast_user( + user_id, + "send_start_error", + { + "error": self.helper.translation.translate( + "error", "no-file", user_lang + ) + }, + ) + return_json = { + "root_path": { + "path": folder, + "top": root_path, + } + } + + 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: + unsorted_files.append(item) + file_list = sorted(dir_list, key=str.casefold) + sorted( + unsorted_files, key=str.casefold + ) + for raw_filename in file_list: + filename = html.escape(raw_filename) + rel = os.path.join(folder, raw_filename) + dpath = os.path.join(folder, filename) + dpath = self.helper.wtol_path(dpath) + if os.path.isdir(rel): + return_json[filename] = { + "path": dpath, + "dir": True, + } + else: + return_json[filename] = { + "path": dpath, + "dir": False, + } + self.finish_json(200, {"status": "ok", "data": return_json}) diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index d2b047d7..7b56220b 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -48,13 +48,14 @@ class Webserver: controller: Controller helper: Helpers - def __init__(self, helper, controller, tasks_manager): + def __init__(self, helper, controller, tasks_manager, file_helper): self.ioloop = None self.http_server = None self.https_server = None self.helper = helper self.controller = controller self.tasks_manager = tasks_manager + self.file_helper = file_helper self._asyncio_patch() @staticmethod @@ -146,6 +147,7 @@ class Webserver: "controller": self.controller, "tasks_manager": self.tasks_manager, "translator": self.helper.translation, + "file_helper": self.file_helper, } handlers = [ (r"/", DefaultHandler, handler_args), diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py index 78b33951..a2e7f5a4 100644 --- a/app/classes/web/websocket_handler.py +++ b/app/classes/web/websocket_handler.py @@ -18,12 +18,18 @@ class SocketHandler(tornado.websocket.WebSocketHandler): io_loop = None def initialize( - self, helper=None, controller=None, tasks_manager=None, translator=None + self, + helper=None, + controller=None, + tasks_manager=None, + translator=None, + file_helper=None, ): self.helper = helper self.controller = controller self.tasks_manager = tasks_manager self.translator = translator + self.file_helper = file_helper self.io_loop = tornado.ioloop.IOLoop.current() def get_remote_ip(self): diff --git a/main.py b/main.py index 2338517b..8ba07b6a 100644 --- a/main.py +++ b/main.py @@ -171,7 +171,7 @@ if __name__ == "__main__": Console.info("Remote change complete.") import3 = Import3(helper, controller) - tasks_manager = TasksManager(helper, controller) + tasks_manager = TasksManager(helper, controller, file_helper) tasks_manager.start_webserver() def signal_handler(signum, _frame):