From 7eeb18f55c43db384f5f23ca91831e755599390a Mon Sep 17 00:00:00 2001 From: Michael Wedl <michael@syslifters.com> Date: Wed, 30 Oct 2024 10:48:02 +0100 Subject: [PATCH] Plugin system bugfixes --- Dockerfile | 5 ++-- api/src/reportcreator_api/conf/plugins.py | 30 ++++++++++++++-------- api/src/reportcreator_api/conf/settings.py | 2 +- api/src/reportcreator_api/conf/urls.py | 5 ++-- api/start.sh | 5 ++++ deploy/sysreptor/docker-compose.yml | 1 + frontend/src/plugins/pluginLoader.ts | 2 +- plugins/build.sh | 2 +- plugins/cyberchef/build.sh | 11 +++++--- 9 files changed, 42 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index e5ff9a3ab..b6a156787 100644 --- a/Dockerfile +++ b/Dockerfile @@ -210,10 +210,9 @@ FROM --platform=$BUILDPLATFORM api-test AS api-statics # Do not post-process nuxt files, because they already have hash names (and django failes to post-process them) RUN python3 manage.py collectstatic --no-input --clear COPY --from=frontend /app/frontend/dist/index.html /app/frontend/dist/static/ /app/api/frontend/static/ -RUN mv /app/api/frontend/static/index.html /app/api/frontend/index.html \ - && python3 manage.py collectstatic --no-input --no-post-process - COPY --from=plugin-builder --chown=user:user /app/plugins/ /app/plugins/ +RUN mv /app/api/frontend/static/index.html /app/api/frontend/index.html \ + && ENABLED_PLUGINS='*' python3 manage.py collectstatic --no-input --no-post-process FROM api-test AS api diff --git a/api/src/reportcreator_api/conf/plugins.py b/api/src/reportcreator_api/conf/plugins.py index 45a7bcc5d..1c2244b84 100644 --- a/api/src/reportcreator_api/conf/plugins.py +++ b/api/src/reportcreator_api/conf/plugins.py @@ -122,7 +122,7 @@ def remove_entry(path: Path): path.unlink(missing_ok=True) -def create_plugin_module_dir(dst: Path, srcs: list[Path]): +def collect_plugins(dst: Path, srcs: list[Path]): # Collect plugins from all plugin directories all_module_dirs = [] for plugins_dir in srcs: @@ -166,10 +166,17 @@ def create_plugin_module_dir(dst: Path, srcs: list[Path]): def load_plugins(plugin_dirs: list[Path], enabled_plugins: list[str]): dst = Path(__file__).parent.parent.parent / 'sysreptor_plugins' # Collect all plugin modules in dst directory - create_plugin_module_dir( - dst=dst, - srcs=plugin_dirs, - ) + try: + collect_plugins( + dst=dst, + srcs=plugin_dirs, + ) + except Exception: + logging.exception('Error while collecting plugins') + + if not dst.is_dir() or not (dst / '__init__.py').is_file(): + logging.warning(f'Cannot load plugins: Plugin directory "{dst}" not found') + return [] # Load sysreptor_plugins module from dst directory load_module_from_dir('sysreptor_plugins', dst / '__init__.py') @@ -196,7 +203,8 @@ def load_plugins(plugin_dirs: list[Path], enabled_plugins: list[str]): plugin_config_class.name = module_name if any(c.plugin_id == plugin_config_class.plugin_id for c in available_plugin_configs): - raise ImproperlyConfigured(f'Duplicate plugin_id: {plugin_config_class.plugin_id}') + logging.warning(f'Duplicate plugin_id: {plugin_config_class.plugin_id}') + continue available_plugin_configs.append(plugin_config_class) @@ -206,15 +214,17 @@ def load_plugins(plugin_dirs: list[Path], enabled_plugins: list[str]): for plugin_config_class in available_plugin_configs: plugin_id = plugin_config_class.plugin_id plugin_name = plugin_config_class.name.split('.')[-1] - if enabled_plugin_id in [plugin_id, plugin_name]: + if enabled_plugin_id in [plugin_id, plugin_name, '*']: # Add to installed_apps app_class = plugin_config_class.__module__ + '.' + plugin_config_class.__name__ app_label = plugin_config_class.label if app_class not in installed_apps: installed_apps.append(app_class) - logging.info(f'Enabling plugin {plugin_name} ({plugin_id=}, {app_label=}, {app_class=})', file=sys.stderr) # noqa: T201 - break + logging.info(f'Enabling plugin {plugin_name} ({plugin_id=}, {app_label=}, {app_class=})') + if enabled_plugin_id != '*': + break else: - logging.warning(f'Plugin "{enabled_plugin_id}" not found in plugins') + if enabled_plugin_id != '*': + logging.warning(f'Plugin "{enabled_plugin_id}" not found in plugins') return installed_apps diff --git a/api/src/reportcreator_api/conf/settings.py b/api/src/reportcreator_api/conf/settings.py index 08cd228fa..c8148cfdb 100644 --- a/api/src/reportcreator_api/conf/settings.py +++ b/api/src/reportcreator_api/conf/settings.py @@ -101,7 +101,7 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'frontend'], + 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ diff --git a/api/src/reportcreator_api/conf/urls.py b/api/src/reportcreator_api/conf/urls.py index dbcfeea38..26ec61a74 100644 --- a/api/src/reportcreator_api/conf/urls.py +++ b/api/src/reportcreator_api/conf/urls.py @@ -3,7 +3,8 @@ from django.contrib import admin from django.http import HttpResponse from django.urls import include, path, re_path -from django.views.generic.base import RedirectView, TemplateView +from django.views.generic.base import RedirectView +from django.views.static import serve from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerSplitView from rest_framework.routers import DefaultRouter from rest_framework_nested.routers import NestedSimpleRouter @@ -167,7 +168,7 @@ path('favicon.ico', RedirectView.as_view(url='/static/favicon.ico', permanent=True)), # Fallback URL for SPA - re_path(r'^(?!(api|admin)).*/?$', TemplateView.as_view(template_name='index.html')), + re_path(r'^(?!(api|admin|static)).*/?$', lambda request, *args, **kwargs: serve(request, path='index.html', document_root=settings.BASE_DIR / 'frontend')), ] diff --git a/api/start.sh b/api/start.sh index 2065da996..de229b584 100644 --- a/api/start.sh +++ b/api/start.sh @@ -1,10 +1,15 @@ #!/bin/bash +# Add custom CA certificates if [[ -n "$CA_CERTIFICATES" ]]; then echo "${CA_CERTIFICATES}" >> /usr/local/share/ca-certificates/custom-user-cert.crt update-ca-certificates fi +# Run DB migrations python3 manage.py migrate +# Collect static files (of custom plugins) +python3 manage.py collectstatic --noinput --no-post-process +# Start web application gunicorn --bind=:8000 \ --worker-class=uvicorn.workers.UvicornWorker \ --workers=${SERVER_WORKERS} \ diff --git a/deploy/sysreptor/docker-compose.yml b/deploy/sysreptor/docker-compose.yml index 7add1d5c0..ba3e73ddb 100644 --- a/deploy/sysreptor/docker-compose.yml +++ b/deploy/sysreptor/docker-compose.yml @@ -34,6 +34,7 @@ services: restart: unless-stopped app: image: 'syslifters/sysreptor:${SYSREPTOR_VERSION:-latest}' + # build: ../../ container_name: 'sysreptor-app' init: true volumes: diff --git a/frontend/src/plugins/pluginLoader.ts b/frontend/src/plugins/pluginLoader.ts index 437b57259..3bc87268a 100644 --- a/frontend/src/plugins/pluginLoader.ts +++ b/frontend/src/plugins/pluginLoader.ts @@ -72,7 +72,7 @@ export function usePluginHelpers(pluginHelperOptions: { pluginConfig: PluginConf }); } let iframeSrc = attrs.src; - if (!iframeSrc.startsWith('/')) { + if (!iframeSrc.startsWith('/') && !iframeSrc.startsWith('https://') && !iframeSrc.startsWith('http://')) { // Convert relative src to absolute if (iframeSrc.startsWith('api/')) { iframeSrc = `/api/plugins/${pluginHelperOptions.pluginConfig.id}/${iframeSrc}`; diff --git a/plugins/build.sh b/plugins/build.sh index 88df3338c..97093dcb6 100755 --- a/plugins/build.sh +++ b/plugins/build.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -e # exit on error for plugin in ./*; do diff --git a/plugins/cyberchef/build.sh b/plugins/cyberchef/build.sh index 55a62af74..3e5203a92 100755 --- a/plugins/cyberchef/build.sh +++ b/plugins/cyberchef/build.sh @@ -1,9 +1,14 @@ -#!/bin/sh +#!/bin/bash -if [ ! -d static/cyberchef ]; then +set -e + +CYBERCHEF_VERSION="v10.19.2" + +if [ ! -f "./static/cyberchef/CyberChef_${CYBERCHEF_VERSION}.html" ]; then echo "Downloading CyberChef" + rm -rf static/cyberchef/* mkdir -p static/cyberchef - curl https://github.com/gchq/CyberChef/releases/download/v10.19.2/CyberChef_v10.19.2.zip -o cyberchef.zip + curl -L "https://github.com/gchq/CyberChef/releases/download/v10.19.2/CyberChef_${CYBERCHEF_VERSION}.zip" -o cyberchef.zip unzip cyberchef.zip -d static/cyberchef rm cyberchef.zip else