From e6cb5b0b099d8a4922019fa2ba165c75202855ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Diot?= Date: Thu, 19 Jan 2023 16:15:26 +0100 Subject: [PATCH] Made the UI independent + update job download plugins --- src/common/core/jobs/jobs/download-plugins.py | 19 +- src/common/db/Database.py | 129 +++++++- src/common/db/model.py | 44 ++- src/common/gen/Configurator.py | 12 +- src/common/gen/save_config.py | 26 +- src/ui/main.py | 293 +++++++++++++----- src/ui/src/Config.py | 114 +++---- 7 files changed, 452 insertions(+), 185 deletions(-) diff --git a/src/common/core/jobs/jobs/download-plugins.py b/src/common/core/jobs/jobs/download-plugins.py index 27ca3744d..e15df4f59 100644 --- a/src/common/core/jobs/jobs/download-plugins.py +++ b/src/common/core/jobs/jobs/download-plugins.py @@ -1,14 +1,14 @@ #!/usr/bin/python3 from io import BytesIO -from os import getenv, listdir, makedirs, chmod, stat, _exit -from os.path import isfile, dirname +from os import getenv, listdir, makedirs, chmod, stat, _exit, walk +from os.path import join, isfile, dirname from stat import S_IEXEC from sys import exit as sys_exit, path as sys_path from uuid import uuid4 from glob import glob from json import load, loads -from shutil import copytree, rmtree +from shutil import chown, copytree, rmtree from traceback import format_exc from zipfile import ZipFile @@ -97,6 +97,7 @@ try: continue external_plugins = [] + external_plugins_ids = [] for plugin in listdir("/etc/bunkerweb/plugins"): with open( f"/etc/bunkerweb/plugins/{plugin}/plugin.json", @@ -105,6 +106,18 @@ try: plugin_file = load(f) external_plugins.append(plugin_file) + external_plugins_ids.append(plugin_file["id"]) + + db_plugins = db.get_plugins() + for plugin in db_plugins: + if plugin["external"] is True and plugin["id"] not in external_plugins_ids: + external_plugins.append(plugin) + + # Fix permissions for the certificates + for root, dirs, files in walk("/data/plugins", topdown=False): + for name in files + dirs: + chown(join(root, name), "root", 101) + chmod(join(root, name), 0o770) if external_plugins: err = db.update_external_plugins(external_plugins) diff --git a/src/common/db/Database.py b/src/common/db/Database.py index 4e40cefe9..859d597cd 100644 --- a/src/common/db/Database.py +++ b/src/common/db/Database.py @@ -1111,10 +1111,14 @@ class Database: Jobs.name == job["name"] ).update(updates) - if exists(f"/usr/share/bunkerweb/core/{plugin['id']}/ui"): - if {"template.html", "actions.py"}.issubset( - listdir(f"/usr/share/bunkerweb/core/{plugin['id']}/ui") - ): + path_ui = ( + f"/var/tmp/bunkerweb/ui/{plugin['id']}/ui" + if exists(f"/var/tmp/bunkerweb/ui/{plugin['id']}/ui") + else f"/etc/bunkerweb/plugins/{plugin['id']}/ui" + ) + + if exists(path_ui): + if {"template.html", "actions.py"}.issubset(listdir(path_ui)): db_plugin_page = ( session.query(Plugin_pages) .with_entities( @@ -1127,12 +1131,12 @@ class Database: if db_plugin_page is None: with open( - f"/usr/share/bunkerweb/core/{plugin['id']}/ui/template.html", + f"{path_ui}/template.html", "r", ) as file: template = file.read().encode("utf-8") with open( - f"/usr/share/bunkerweb/core/{plugin['id']}/ui/actions.py", + f"{path_ui}/actions.py", "r", ) as file: actions = file.read().encode("utf-8") @@ -1149,18 +1153,16 @@ class Database: else: # TODO test this updates = {} template_checksum = file_hash( - f"/usr/share/bunkerweb/core/{plugin['id']}/ui/template.html" - ) - actions_checksum = file_hash( - f"/usr/share/bunkerweb/core/{plugin['id']}/ui/actions.py" + f"{path_ui}/template.html" ) + actions_checksum = file_hash(f"{path_ui}/actions.py") if ( template_checksum != db_plugin_page.template_checksum ): with open( - f"/usr/share/bunkerweb/core/{plugin['id']}/ui/template.html", + f"{path_ui}/template.html", "r", ) as file: updates.update( @@ -1174,7 +1176,7 @@ class Database: if actions_checksum != db_plugin_page.actions_checksum: with open( - f"/usr/share/bunkerweb/core/{plugin['id']}/ui/actions.py", + f"{path_ui}/actions.py", "r", ) as file: updates.update( @@ -1258,6 +1260,79 @@ class Database: return "" + def get_plugins(self) -> List[Dict[str, Any]]: + """Get plugins.""" + plugins = [] + with self.__db_session() as session: + for plugin in ( + session.query(Plugins) + .with_entities( + Plugins.id, + Plugins.order, + Plugins.name, + Plugins.description, + Plugins.version, + Plugins.external, + ) + .order_by(Plugins.order) + .all() + ): + page = ( + session.query(Plugin_pages) + .with_entities(Plugin_pages.id) + .filter_by(plugin_id=plugin.id) + .first() + ) + data = { + "id": plugin.id, + "order": plugin.order, + "name": plugin.name, + "description": plugin.description, + "version": plugin.version, + "external": plugin.external, + "page": page is not None, + "settings": {}, + } + + for setting in ( + session.query(Settings) + .with_entities( + Settings.id, + Settings.context, + Settings.default, + Settings.help, + Settings.name, + Settings.label, + Settings.regex, + Settings.type, + Settings.multiple, + ) + .filter_by(plugin_id=plugin.id) + .all() + ): + data["settings"][setting.id] = { + "context": setting.context, + "default": setting.default, + "help": setting.help, + "id": setting.name, + "label": setting.label, + "regex": setting.regex, + "type": setting.type, + } | ({"multiple": setting.multiple} if setting.multiple else {}) + + if setting.type == "select": + data["settings"][setting.id]["select"] = [ + select.value + for select in session.query(Selects) + .with_entities(Selects.value) + .filter_by(setting_id=setting.id) + .all() + ] + + plugins.append(data) + + return plugins + def get_plugins_errors(self) -> int: """Get plugins errors.""" with self.__db_session() as session: @@ -1381,3 +1456,33 @@ class Database: .all() ) ] + + def get_plugin_actions(self, plugin: str) -> Optional[Any]: + """get actions file for the plugin""" + with self.__db_session() as session: + page = ( + session.query(Plugin_pages) + .with_entities(Plugin_pages.actions_file) + .filter_by(plugin_id=plugin) + .first() + ) + + if page is None: + return None + + return page.actions_file + + def get_plugin_template(self, plugin: str) -> Optional[Any]: + """get template file for the plugin""" + with self.__db_session() as session: + page = ( + session.query(Plugin_pages) + .with_entities(Plugin_pages.template_file) + .filter_by(plugin_id=plugin) + .first() + ) + + if page is None: + return None + + return page.template_file diff --git a/src/common/db/model.py b/src/common/db/model.py index 4d4581c5a..83ba6ab05 100644 --- a/src/common/db/model.py +++ b/src/common/db/model.py @@ -61,12 +61,10 @@ class Plugins(Base): external = Column(Boolean, default=False, nullable=False) settings = relationship( - "Settings", back_populates="plugin", cascade="all, delete, delete-orphan" + "Settings", back_populates="plugin", cascade="all, delete-orphan" ) - jobs = relationship( - "Jobs", back_populates="plugin", cascade="all, delete, delete-orphan" - ) - pages = relationship("Plugin_pages", back_populates="plugin", cascade="all, delete") + jobs = relationship("Jobs", back_populates="plugin", cascade="all, delete-orphan") + pages = relationship("Plugin_pages", back_populates="plugin", cascade="all") class Settings(Base): @@ -81,7 +79,7 @@ class Settings(Base): name = Column(String(256), primary_key=True) plugin_id = Column( String(64), - ForeignKey("plugins.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("plugins.id", onupdate="cascade", ondelete="cascade"), nullable=False, ) context = Column(CONTEXTS_ENUM, nullable=False) @@ -92,12 +90,12 @@ class Settings(Base): type = Column(SETTINGS_TYPES_ENUM, nullable=False) multiple = Column(String(128), nullable=True) - selects = relationship("Selects", back_populates="setting", cascade="all, delete") + selects = relationship("Selects", back_populates="setting", cascade="all") services = relationship( - "Services_settings", back_populates="setting", cascade="all, delete" + "Services_settings", back_populates="setting", cascade="all" ) global_value = relationship( - "Global_values", back_populates="setting", cascade="all, delete" + "Global_values", back_populates="setting", cascade="all" ) plugin = relationship("Plugins", back_populates="settings") @@ -107,7 +105,7 @@ class Global_values(Base): setting_id = Column( String(256), - ForeignKey("settings.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("settings.id", onupdate="cascade", ondelete="cascade"), primary_key=True, ) value = Column(String(4096), nullable=False) @@ -124,14 +122,12 @@ class Services(Base): method = Column(METHODS_ENUM, nullable=False) settings = relationship( - "Services_settings", back_populates="service", cascade="all, delete" + "Services_settings", back_populates="service", cascade="all" ) custom_configs = relationship( - "Custom_configs", back_populates="service", cascade="all, delete" - ) - jobs_cache = relationship( - "Jobs_cache", back_populates="service", cascade="all, delete" + "Custom_configs", back_populates="service", cascade="all" ) + jobs_cache = relationship("Jobs_cache", back_populates="service", cascade="all") class Services_settings(Base): @@ -139,12 +135,12 @@ class Services_settings(Base): service_id = Column( String(64), - ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("services.id", onupdate="cascade", ondelete="cascade"), primary_key=True, ) setting_id = Column( String(256), - ForeignKey("settings.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("settings.id", onupdate="cascade", ondelete="cascade"), primary_key=True, ) value = Column(String(4096), nullable=False) @@ -162,7 +158,7 @@ class Jobs(Base): name = Column(String(128), primary_key=True) plugin_id = Column( String(64), - ForeignKey("plugins.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("plugins.id", onupdate="cascade", ondelete="cascade"), ) file_name = Column(String(256), nullable=False) every = Column(SCHEDULES_ENUM, nullable=False) @@ -171,7 +167,7 @@ class Jobs(Base): last_run = Column(DateTime, nullable=True) plugin = relationship("Plugins", back_populates="jobs") - cache = relationship("Jobs_cache", back_populates="job", cascade="all, delete") + cache = relationship("Jobs_cache", back_populates="job", cascade="all") class Plugin_pages(Base): @@ -184,7 +180,7 @@ class Plugin_pages(Base): ) plugin_id = Column( String(64), - ForeignKey("plugins.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("plugins.id", onupdate="cascade", ondelete="cascade"), nullable=False, ) template_file = Column(LargeBinary(length=(2**32) - 1), nullable=False) @@ -206,12 +202,12 @@ class Jobs_cache(Base): ) job_name = Column( String(128), - ForeignKey("jobs.name", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("jobs.name", onupdate="cascade", ondelete="cascade"), nullable=False, ) service_id = Column( String(64), - ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("services.id", onupdate="cascade", ondelete="cascade"), nullable=True, ) file_name = Column( @@ -237,7 +233,7 @@ class Custom_configs(Base): ) service_id = Column( String(64), - ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("services.id", onupdate="cascade", ondelete="cascade"), nullable=True, ) type = Column(CUSTOM_CONFIGS_TYPES_ENUM, nullable=False) @@ -254,7 +250,7 @@ class Selects(Base): setting_id = Column( String(256), - ForeignKey("settings.id", onupdate="CASCADE", ondelete="CASCADE"), + ForeignKey("settings.id", onupdate="cascade", ondelete="cascade"), primary_key=True, ) value = Column(String(256), primary_key=True) diff --git a/src/common/gen/Configurator.py b/src/common/gen/Configurator.py index 331cf470f..b4e035549 100644 --- a/src/common/gen/Configurator.py +++ b/src/common/gen/Configurator.py @@ -14,9 +14,11 @@ class Configurator: self, settings: str, core: Union[str, dict], - plugins: str, + plugins: Union[str, dict], variables: Union[str, dict], logger: Logger, + *, + plugins_settings: list = None, ): self.__logger = logger self.__settings = self.__load_settings(settings) @@ -26,8 +28,12 @@ class Configurator: else: self.__core = core - self.__plugins_settings = [] - self.__plugins = self.__load_plugins(plugins, "plugins") + self.__plugins_settings = plugins_settings or [] + + if isinstance(plugins, str): + self.__plugins = self.__load_plugins(plugins, "plugins") + else: + self.__plugins = plugins if isinstance(variables, str): self.__variables = self.__load_variables(variables) diff --git a/src/common/gen/save_config.py b/src/common/gen/save_config.py index 2b5fc8947..428a20776 100644 --- a/src/common/gen/save_config.py +++ b/src/common/gen/save_config.py @@ -145,6 +145,18 @@ if __name__ == "__main__": db = None apis = [] + plugins = args.plugins + plugins_settings = None + if not exists("/usr/sbin/nginx") and args.method == "ui": + db = Database(logger) + plugins = {} + plugins_settings = [] + for plugin in db.get_plugins(): + del plugin["page"] + del plugin["external"] + plugins_settings.append(plugin) + plugins.update(plugin["settings"]) + # Check existences and permissions logger.info("Checking arguments ...") files = [args.settings] + ([args.variables] if args.variables else []) @@ -200,7 +212,12 @@ if __name__ == "__main__": # Compute the config logger.info("Computing config ...") config = Configurator( - args.settings, core_settings, args.plugins, args.variables, logger + args.settings, + core_settings, + plugins, + args.variables, + logger, + plugins_settings=plugins_settings, ) config_files = config.get_config() custom_confs = [ @@ -288,7 +305,12 @@ if __name__ == "__main__": if config_files is None: logger.info("Computing config ...") config = Configurator( - args.settings, core_settings, args.plugins, tmp_config, logger + args.settings, + core_settings, + plugins, + tmp_config, + logger, + plugins_settings=plugins_settings, ) config_files = config.get_config() diff --git a/src/ui/main.py b/src/ui/main.py index caeebd58a..edd7a188f 100755 --- a/src/ui/main.py +++ b/src/ui/main.py @@ -1,7 +1,9 @@ from contextlib import suppress +from importlib.machinery import SourceFileLoader, SourcelessFileLoader from io import BytesIO from pathlib import Path from signal import SIGINT, signal, SIGTERM +from tempfile import NamedTemporaryFile from bs4 import BeautifulSoup from copy import deepcopy from datetime import datetime, timedelta, timezone @@ -688,25 +690,40 @@ def plugins(): variables = deepcopy(request.form.to_dict()) del variables["csrf_token"] - if variables["external"] == "false": + if variables["external"] != "True": flash(f"Can't delete internal plugin {variables['name']}", "error") return redirect(url_for("loading", next=url_for("plugins"))), 500 - variables["path"] = f"/etc/bunkerweb/plugins/{variables['name']}" + if not exists("/usr/sbin/nginx"): + plugins = app.config["CONFIG"].get_plugins() + for plugin in deepcopy(plugins): + if plugin["external"] is False or plugin["id"] == variables["name"]: + del plugins[plugins.index(plugin)] - operation = app.config["CONFIGFILES"].check_path( - variables["path"], "/etc/bunkerweb/plugins/" - ) + err = db.update_external_plugins(plugins) + if err: + flash( + f"Couldn't update external plugins to database: {err}", + "error", + ) + else: + variables["path"] = f"/etc/bunkerweb/plugins/{variables['name']}" - if operation: - flash(operation, "error") - return redirect(url_for("loading", next=url_for("plugins"))), 500 + operation = app.config["CONFIGFILES"].check_path( + variables["path"], "/etc/bunkerweb/plugins/" + ) - operation, error = app.config["CONFIGFILES"].delete_path(variables["path"]) + if operation: + flash(operation, "error") + return redirect(url_for("loading", next=url_for("plugins"))), 500 - if error: - flash(operation, "error") - return redirect(url_for("loading", next=url_for("plugins"))) + operation, error = app.config["CONFIGFILES"].delete_path( + variables["path"] + ) + + if error: + flash(operation, "error") + return redirect(url_for("loading", next=url_for("plugins"))) else: if not exists("/var/tmp/bunkerweb/ui") or not listdir( "/var/tmp/bunkerweb/ui" @@ -758,13 +775,33 @@ def plugins(): ) raise Exception - if exists(f"/etc/bunkerweb/plugins/{folder_name}"): - raise FileExistsError + if not exists("/usr/sbin/nginx"): + plugins = app.config["CONFIG"].get_plugins() + for plugin in deepcopy(plugins): + if plugin["id"] == folder_name: + raise FileExistsError + elif plugin["external"] is False: + del plugins[plugins.index(plugin)] - copytree( - f"/var/tmp/bunkerweb/ui/{temp_folder_name}", - f"/etc/bunkerweb/plugins/{folder_name}", - ) + plugins.append(plugin_file) + err = db.update_external_plugins(plugins) + if err: + error = 1 + flash( + f"Couldn't update external plugins to database: {err}", + "error", + ) + raise Exception + else: + if exists( + f"/etc/bunkerweb/plugins/{folder_name}" + ): + raise FileExistsError + + copytree( + f"/var/tmp/bunkerweb/ui/{temp_folder_name}", + f"/etc/bunkerweb/plugins/{folder_name}", + ) except KeyError: zip_file.extractall( f"/var/tmp/bunkerweb/ui/{temp_folder_name}" @@ -812,13 +849,33 @@ def plugins(): ) raise Exception - if exists(f"/etc/bunkerweb/plugins/{folder_name}"): - raise FileExistsError + if not exists("/usr/sbin/nginx"): + plugins = app.config["CONFIG"].get_plugins() + for plugin in deepcopy(plugins): + if plugin["id"] == folder_name: + raise FileExistsError + elif plugin["external"] is False: + del plugins[plugins.index(plugin)] - copytree( - f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}", - f"/etc/bunkerweb/plugins/{folder_name}", - ) + plugins.append(plugin_file) + err = db.update_external_plugins(plugins) + if err: + error = 1 + flash( + f"Couldn't update external plugins to database: {err}", + "error", + ) + raise Exception + else: + if exists( + f"/etc/bunkerweb/plugins/{folder_name}" + ): + raise FileExistsError + + copytree( + f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}", + f"/etc/bunkerweb/plugins/{folder_name}", + ) except BadZipFile: errors += 1 error = 1 @@ -861,13 +918,33 @@ def plugins(): ) raise Exception - if exists(f"/etc/bunkerweb/plugins/{folder_name}"): - raise FileExistsError + if not exists("/usr/sbin/nginx"): + plugins = app.config["CONFIG"].get_plugins() + for plugin in deepcopy(plugins): + if plugin["id"] == folder_name: + raise FileExistsError + elif plugin["external"] is False: + del plugins[plugins.index(plugin)] - copytree( - f"/var/tmp/bunkerweb/ui/{temp_folder_name}", - f"/etc/bunkerweb/plugins/{folder_name}", - ) + plugins.append(plugin_file) + err = db.update_external_plugins(plugins) + if err: + error = 1 + flash( + f"Couldn't update external plugins to database: {err}", + "error", + ) + raise Exception + else: + if exists( + f"/etc/bunkerweb/plugins/{folder_name}" + ): + raise FileExistsError + + copytree( + f"/var/tmp/bunkerweb/ui/{temp_folder_name}", + f"/etc/bunkerweb/plugins/{folder_name}", + ) except KeyError: tar_file.extractall( f"/var/tmp/bunkerweb/ui/{temp_folder_name}", @@ -915,13 +992,33 @@ def plugins(): ) raise Exception - if exists(f"/etc/bunkerweb/plugins/{folder_name}"): - raise FileExistsError + if not exists("/usr/sbin/nginx"): + plugins = app.config["CONFIG"].get_plugins() + for plugin in deepcopy(plugins): + if plugin["id"] == folder_name: + raise FileExistsError + elif plugin["external"] is False: + del plugins[plugins.index(plugin)] - copytree( - f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}", - f"/etc/bunkerweb/plugins/{folder_name}", - ) + plugins.append(plugin_file) + err = db.update_external_plugins(plugins) + if err: + error = 1 + flash( + f"Couldn't update external plugins to database: {err}", + "error", + ) + raise Exception + else: + if exists( + f"/etc/bunkerweb/plugins/{folder_name}" + ): + raise FileExistsError + + copytree( + f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}", + f"/etc/bunkerweb/plugins/{folder_name}", + ) except ReadError: errors += 1 error = 1 @@ -993,14 +1090,12 @@ def plugins(): # Fix permissions for plugins folders for root, dirs, files in walk("/etc/bunkerweb/plugins", topdown=False): for name in files + dirs: - chown(join(root, name), 101, 101) + chown(join(root, name), "root", 101) chmod(join(root, name), 0o770) if operation: flash(operation) - app.config["CONFIG"].reload_plugins() - # Reload instances app.config["RELOADING"] = True Thread( @@ -1023,19 +1118,28 @@ def plugins(): if request.args.get("plugin_id", False): plugin_id = request.args.get("plugin_id") - page_path = "" + template = None - if exists(f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html"): - page_path = f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html" - elif exists(f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html"): - page_path = f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html" + if not exists("/usr/sbin/nginx"): + page = db.get_plugin_template(plugin_id) + + if page is not None: + template = Template(page.decode("utf-8")) else: - flash(f"Plugin {plugin_id} not found", "error") + page_path = "" - if page_path: - with open(page_path, "r") as f: - template = Template(f.read()) + if exists(f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html"): + page_path = f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html" + elif exists(f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html"): + page_path = f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html" + else: + flash(f"Plugin {plugin_id} not found", "error") + if page_path: + with open(page_path, "r") as f: + template = Template(f.read()) + + if template is not None: return template.render( csrf_token=generate_csrf, url_for=url_for, @@ -1047,7 +1151,6 @@ def plugins(): ), ) - app.config["CONFIG"].reload_plugins() plugins = app.config["CONFIG"].get_plugins() plugins_internal = 0 plugins_external = 0 @@ -1096,33 +1199,66 @@ def custom_plugin(plugin): ) return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin))) - if not exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py") and not exists( - f"/usr/share/bunkerweb/core/{plugin}/ui/actions.py" - ): - flash( - f"The actions.py file for the plugin {plugin} does not exist", - "error", - ) - return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin))) + if not exists("/usr/sbin/nginx"): + module = db.get_plugin_actions(plugin) - # Add the custom plugin to sys.path - sys_path.append( - ( - "/etc/bunkerweb/plugins" - if exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py") - else "/usr/share/bunkerweb/core" + if module is None: + flash( + f"The actions.py file for the plugin {plugin} does not exist", + "error", + ) + return redirect( + url_for("loading", next=url_for("plugins", plugin_id=plugin)) + ) + + try: + # Try to import the custom plugin + with NamedTemporaryFile(mode="wb", suffix=".py", delete=True) as temp: + temp.write(module) + temp.flush() + temp.seek(0) + loader = SourceFileLoader("actions", temp.name) + actions = loader.load_module() + except: + flash( + f"An error occurred while importing the plugin {plugin}:
{format_exc()}", + "error", + ) + return redirect( + url_for("loading", next=url_for("plugins", plugin_id=plugin)) + ) + else: + if not exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py") and not exists( + f"/usr/share/bunkerweb/core/{plugin}/ui/actions.py" + ): + flash( + f"The actions.py file for the plugin {plugin} does not exist", + "error", + ) + return redirect( + url_for("loading", next=url_for("plugins", plugin_id=plugin)) + ) + + # Add the custom plugin to sys.path + sys_path.append( + ( + "/etc/bunkerweb/plugins" + if exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py") + else "/usr/share/bunkerweb/core" + ) + + f"/{plugin}/ui/" ) - + f"/{plugin}/ui/" - ) - try: - # Try to import the custom plugin - import actions - except: - flash( - f"An error occurred while importing the plugin {plugin}:
{format_exc()}", - "error", - ) - return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin))) + try: + # Try to import the custom plugin + import actions + except: + flash( + f"An error occurred while importing the plugin {plugin}:
{format_exc()}", + "error", + ) + return redirect( + url_for("loading", next=url_for("plugins", plugin_id=plugin)) + ) error = False res = None @@ -1145,10 +1281,11 @@ def custom_plugin(plugin): ) error = True finally: - # Remove the custom plugin from the shared library - sys_path.pop() - sys_modules.pop("actions") - del actions + if exists("/usr/sbin/nginx"): + # Remove the custom plugin from the shared library + sys_path.pop() + sys_modules.pop("actions") + del actions if ( request.method != "POST" diff --git a/src/ui/src/Config.py b/src/ui/src/Config.py index df4f32c96..563ca2b78 100644 --- a/src/ui/src/Config.py +++ b/src/ui/src/Config.py @@ -1,6 +1,7 @@ from copy import deepcopy -from os import listdir, remove +from os import listdir, mkdir, remove from pathlib import Path +from shutil import rmtree from time import sleep from flask import flash from os.path import exists, isfile @@ -35,54 +36,6 @@ class Config: sleep(3) env = self.__db.get_config() - self.reload_plugins() - - def reload_plugins(self) -> None: - self.__plugins = [] - external_plugins = [] - - for foldername in list(iglob("/etc/bunkerweb/plugins/*")) + list( - iglob("/usr/share/bunkerweb/core/*") - ): - content = listdir(foldername) - if "plugin.json" not in content: - continue - - with open(f"{foldername}/plugin.json", "r") as f: - plugin = json_load(f) - - plugin.update( - { - "page": False, - "external": foldername.startswith("/etc/bunkerweb/plugins"), - } - ) - - if plugin["external"] is True: - external_plugin = deepcopy(plugin) - del external_plugin["external"] - del external_plugin["page"] - external_plugins.append(external_plugin) - - if "ui" in content: - if "template.html" in listdir(f"{foldername}/ui"): - plugin["page"] = True - - self.__plugins.append(plugin) - - self.__plugins.sort(key=lambda plugin: plugin.get("name")) - self.__plugins_settings = { - **{k: v for x in self.__plugins for k, v in x["settings"].items()}, - **self.__settings, - } - - if external_plugins: - err = self.__db.update_external_plugins(external_plugins) - if err: - self.__logger.error( - f"Couldn't update external plugins to database: {err}", - ) - def __env_to_dict(self, filename: str) -> dict: """Converts the content of an env file into a dict @@ -139,17 +92,17 @@ class Config: conf = deepcopy(global_conf) servers = [] + plugins_settings = self.get_plugins_settings() for service in services_conf: server_name = service["SERVER_NAME"].split(" ")[0] for k in service.keys(): key_without_server_name = k.replace(f"{server_name}_", "") if ( - self.__plugins_settings[key_without_server_name]["context"] - != "global" - if key_without_server_name in self.__plugins_settings + plugins_settings[key_without_server_name]["context"] != "global" + if key_without_server_name in plugins_settings else True ): - if not k.startswith(server_name) or k in self.__plugins_settings: + if not k.startswith(server_name) or k in plugins_settings: conf[f"{server_name}_{k}"] = service[k] else: conf[k] = service[k] @@ -178,10 +131,44 @@ class Config: remove(env_file) def get_plugins_settings(self) -> dict: - return self.__plugins_settings + return { + **{k: v for x in self.get_plugins() for k, v in x["settings"].items()}, + **self.__settings, + } def get_plugins(self) -> List[dict]: - return self.__plugins + if not exists("/usr/sbin/nginx"): + plugins = self.__db.get_plugins() + plugins.sort(key=lambda x: x["name"]) + return plugins + + plugins = [] + + for foldername in list(iglob("/etc/bunkerweb/plugins/*")) + list( + iglob("/usr/share/bunkerweb/core/*") + ): + content = listdir(foldername) + if "plugin.json" not in content: + continue + + with open(f"{foldername}/plugin.json", "r") as f: + plugin = json_load(f) + + plugin.update( + { + "page": False, + "external": foldername.startswith("/etc/bunkerweb/plugins"), + } + ) + + if "ui" in content: + if "template.html" in listdir(f"{foldername}/ui"): + plugin["page"] = True + + plugins.append(plugin) + + plugins.sort(key=lambda x: x["name"]) + return plugins def get_settings(self) -> dict: return self.__settings @@ -212,6 +199,7 @@ class Config: """ if exists("/usr/sbin/nginx"): services = [] + plugins_settings = self.get_plugins_settings() for filename in iglob("/etc/nginx/**/variables.env"): service = filename.split("/")[3] env = { @@ -219,8 +207,7 @@ class Config: {"value": v, "method": "ui"} if methods is True else v ) for k, v in self.__env_to_dict(filename).items() - if k.startswith(f"{service}_") - or k in self.__plugins_settings.keys() + if k.startswith(f"{service}_") or k in plugins_settings.keys() } services.append(env) @@ -242,11 +229,12 @@ class Config: Return the error code """ error = 0 + plugins_settings = self.get_plugins_settings() for k, v in variables.items(): check = False - if k in self.__plugins_settings: - if _global ^ (self.__plugins_settings[k]["context"] == "global"): + if k in plugins_settings: + if _global ^ (plugins_settings[k]["context"] == "global"): error = 1 flash(f"Variable {k} is not valid.", "error") continue @@ -255,16 +243,16 @@ class Config: else: setting = k[0 : k.rfind("_")] if ( - setting not in self.__plugins_settings - or "multiple" not in self.__plugins_settings[setting] + setting not in plugins_settings + or "multiple" not in plugins_settings[setting] ): error = 1 flash(f"Variable {k} is not valid.", "error") continue if not ( - _global ^ (self.__plugins_settings[setting]["context"] == "global") - ) and re_search(self.__plugins_settings[setting]["regex"], v): + _global ^ (plugins_settings[setting]["context"] == "global") + ) and re_search(plugins_settings[setting]["regex"], v): check = True if not check: