diff --git a/backend/core/views.py b/backend/core/views.py index 6af9f6117e..fcf90b9ce8 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -105,9 +105,12 @@ def get_serializer_class(self, **kwargs): return serializer_class + # This constant must remain defined right before the _process_request_data if we ever move _process_request_data elsewhere COMMA_SEPARATED_UUIDS_REGEX = r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}(,[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12})*$" - def _process_request_data(self, request: Request) -> None: + # Maybe this should be consider as an helper function instead of a staticmethod attached to a class. + @staticmethod + def _process_request_data(request: Request) -> None: """ Process the request data to split comma-separated UUIDs into a list and handle empty list scenarios. @@ -120,7 +123,7 @@ def _process_request_data(self, request: Request) -> None: # TODO: Come back to this once superForms v2 is out of alpha. https://github.com/ciscoheat/sveltekit-superforms/releases if isinstance(request.data[field], list) and len(request.data[field]) == 1: if isinstance(request.data[field][0], str) and re.match( - self.COMMA_SEPARATED_UUIDS_REGEX, request.data[field][0] + BaseModelViewSet.COMMA_SEPARATED_UUIDS_REGEX, request.data[field][0] ): request.data[field] = request.data[field][0].split(",") elif not request.data[field][0]: diff --git a/backend/global_settings/models.py b/backend/global_settings/models.py index 5d9e8c323c..50141c3359 100644 --- a/backend/global_settings/models.py +++ b/backend/global_settings/models.py @@ -21,7 +21,8 @@ class Names(models.TextChoices): choices=Names, default=Names.GENERAL, ) - # Value of the setting. + # Value of the setting + # This field must always be of type Dict[str, Any] value = models.JSONField(default=dict) def __str__(self): diff --git a/backend/global_settings/urls.py b/backend/global_settings/urls.py index 73aab14c9d..bc1e34e085 100644 --- a/backend/global_settings/urls.py +++ b/backend/global_settings/urls.py @@ -3,7 +3,7 @@ from iam.sso.views import SSOSettingsViewSet -from .views import GlobalSettingsViewSet, get_sso_info +from .views import GlobalSettingsViewSet, get_sso_info, update_global_settings from .routers import DefaultSettingsRouter @@ -17,8 +17,10 @@ basename="sso-settings", ) - urlpatterns = [ + # This route should ideally be placed under the routes of the routers, but the DefaultRouter usage overwrite the route and makes it inaccessible. + # Could we use DefaultSettingsRouter to register the "global" route to fix that ? + path(r"global/update/", update_global_settings, name="update_global_settings"), path(r"", include(router.urls)), path(r"", include(settings_router.urls)), path(r"sso/info/", get_sso_info, name="get_sso_info"), diff --git a/backend/global_settings/views.py b/backend/global_settings/views.py index fbb172c6c5..b913567114 100644 --- a/backend/global_settings/views.py +++ b/backend/global_settings/views.py @@ -3,6 +3,7 @@ from rest_framework.response import Response from ciso_assistant.settings import CISO_ASSISTANT_URL +from core.views import BaseModelViewSet from iam.sso.models import SSOSettings from .serializers import GlobalSettingsSerializer @@ -33,11 +34,43 @@ def update(self, request, *args, **kwargs): ) +UPDATABLE_GLOBAL_SETTINGS = frozenset( + ["lang"] +) # This represents the list of GlobalSettings an admin has the right to change. + + +@api_view(["PATCH"]) +@permission_classes([permissions.IsAdminUser]) +def update_global_settings(request): + """ + API endpoint that returns the CSRF token. + """ + BaseModelViewSet._process_request_data(request) + + global_settings = GlobalSettings.objects.filter(name="general").first() + if global_settings is not None: + global_settings = global_settings.value + else: + global_settings = {} + + for key, value in request.data.items(): + # There is no schema verification for this + # An attacker may be able to break a ciso-assistant instance by injecting values with bad types in future global settings. + if key in UPDATABLE_GLOBAL_SETTINGS: + global_settings[key] = value + + GlobalSettings.objects.update_or_create( + name="general", defaults={"value": global_settings} + ) + + return Response({}) + + @api_view(["GET"]) @permission_classes([permissions.AllowAny]) def get_sso_info(request): """ - API endpoint that returns the CSRF token. + API endpoint that return the SSO configuration info """ settings = SSOSettings.objects.get() sp_entity_id = settings.settings["sp"].get("entity_id") diff --git a/backend/iam/models.py b/backend/iam/models.py index 08b21d686f..e0555b93c2 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -502,6 +502,11 @@ def get_admin_users() -> List[Self]: def is_admin(self) -> bool: return self.user_groups.filter(name="BI-UG-ADM").exists() + # The following property exist solely for compatibilty between the User model and the DRF permission class IsAdminUser + @property + def is_staff(self): + return self.is_admin() + class Role(NameDescriptionMixin, FolderMixin): """A role is a list of permissions""" diff --git a/backend/library/views.py b/backend/library/views.py index 1433309407..20a868c1ca 100644 --- a/backend/library/views.py +++ b/backend/library/views.py @@ -7,7 +7,6 @@ HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, - HTTP_422_UNPROCESSABLE_ENTITY, ) from rest_framework.parsers import FileUploadParser @@ -129,7 +128,7 @@ def import_library(self, request, pk): except Exception: return Response( {"error": "Failed to load library"}, # This must translated - status=HTTP_422_UNPROCESSABLE_ENTITY, + status=HTTP_400_BAD_REQUEST, ) @action(detail=True, methods=["get"]) @@ -172,7 +171,7 @@ def upload_library(self, request): logger.error("Failed to store library content", error=e) return HttpResponse( json.dumps({"error": "Failed to store library content."}), - status=HTTP_422_UNPROCESSABLE_ENTITY, + status=HTTP_400_BAD_REQUEST, ) return HttpResponse(json.dumps({}), status=HTTP_200_OK) @@ -323,5 +322,5 @@ def _update(self, request, pk): if error_msg is None: return Response(status=HTTP_204_NO_CONTENT) return Response( - error_msg, status=HTTP_422_UNPROCESSABLE_ENTITY + error_msg, status=HTTP_400_BAD_REQUEST ) # We must make at least one error message diff --git a/frontend/messages/ar.json b/frontend/messages/ar.json index e7c14d3045..390a8b0482 100644 --- a/frontend/messages/ar.json +++ b/frontend/messages/ar.json @@ -618,7 +618,7 @@ "or": "أو", "errorImportingLibrary": "خطأ أثناء استيراد المكتبة", "libraryImportError": "حدث خطأ أثناء استيراد المكتبة", - "ssoSettingsupdated": "تم تحديث إعدادات تسجيل الدخول الموحد", + "ssoSettingsUpdated": "تم تحديث إعدادات تسجيل الدخول الموحد", "ssoSettings": "إعدادات تسجيل الدخول الموحد", "ssoSettingsDescription": "قم بتكوين إعدادات تسجيل الدخول الموحد هنا.", "sso": "SSO", diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 16dba3009e..14f59f4ebd 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -599,7 +599,7 @@ "or": "oder", "errorImportingLibrary": "Fehler beim Importieren der Bibliothek", "libraryImportError": "Beim Importieren Ihrer Bibliothek ist ein Fehler aufgetreten.", - "ssoSettingsupdated": "SSO-Einstellungen aktualisiert", + "ssoSettingsUpdated": "SSO-Einstellungen aktualisiert", "ssoSettings": "SSO-Einstellungen", "ssoSettingsDescription": "Konfigurieren Sie hier Ihre Single Sign-On-Einstellungen.", "sso": "SSO", diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 6d71c79a4c..edcc7e373d 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -641,7 +641,7 @@ "or": "or", "errorImportingLibrary": "Error during library import", "libraryImportError": "An error occurred during library import", - "ssoSettingsupdated": "SSO settings updated", + "ssoSettingsUpdated": "SSO settings updated", "ssoSettings": "SSO settings", "ssoSettingsDescription": "Configure your Single Sign-On settings here.", "sso": "SSO", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index df0cc23e17..64a4c91e61 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -599,7 +599,7 @@ "or": "o", "errorImportingLibrary": "Error al importar la biblioteca", "libraryImportError": "Ocurrió un error durante la importación de su biblioteca.", - "ssoSettingsupdated": "Configuración de SSO actualizada", + "ssoSettingsUpdated": "Configuración de SSO actualizada", "ssoSettings": "Configuración de inicio de sesión único", "ssoSettingsDescription": "Configure sus ajustes de inicio de sesión único aquí.", "sso": "SSO", diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 64c3763089..af9a583868 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -599,7 +599,7 @@ "or": "ou", "errorImportingLibrary": "Erreur lors de l'importation de la bibliothèque", "libraryImportError": "Une erreur s'est produite lors de l'importation de la bibliothèque", - "ssoSettingsupdated": "Paramètres SSO mis à jour", + "ssoSettingsUpdated": "Paramètres SSO mis à jour", "ssoSettings": "Paramètres SSO", "ssoSettingsDescription": "Configurez vos paramètres d'authentification unique ici.", "sso": "SSO", diff --git a/frontend/messages/hi.json b/frontend/messages/hi.json index 9effdf8e89..3932ce4f0c 100644 --- a/frontend/messages/hi.json +++ b/frontend/messages/hi.json @@ -637,7 +637,7 @@ "or": "या", "errorImportingLibrary": "लाइब्रेरी आयात के दौरान त्रुटि", "libraryImportError": "लाइब्रेरी आयात के दौरान एक त्रुटि उत्पन्न हुई", - "ssoSettingsupdated": "SSO सेटिंग्स अपडेट की गईं", + "ssoSettingsUpdated": "SSO सेटिंग्स अपडेट की गईं", "ssoSettings": "SSO सेटिंग्स", "ssoSettingsDescription": "यहाँ अपने सिंगल साइन-ऑन सेटिंग्स कॉन्फ़िगर करें।", "sso": "SSO", diff --git a/frontend/messages/it.json b/frontend/messages/it.json index 1f59817306..ce6c6112d2 100644 --- a/frontend/messages/it.json +++ b/frontend/messages/it.json @@ -599,7 +599,7 @@ "or": "O", "errorImportingLibrary": "Errore durante l'importazione della biblioteca", "libraryImportError": "Si è verificato un errore durante l'importazione della tua biblioteca.", - "ssoSettingsupdated": "Impostazioni SSO aggiornate", + "ssoSettingsUpdated": "Impostazioni SSO aggiornate", "ssoSettings": "Impostazioni SSO", "ssoSettingsDescription": "Configura qui le tue impostazioni Single Sign-On.", "sso": "SSO", diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json index 73a343c288..14016a15ef 100644 --- a/frontend/messages/nl.json +++ b/frontend/messages/nl.json @@ -599,7 +599,7 @@ "or": "of", "errorImportingLibrary": "Fout bij het importeren van de bibliotheek", "libraryImportError": "Er is een fout opgetreden tijdens het importeren van je bibliotheek.", - "ssoSettingsupdated": "SSO-instellingen bijgewerkt", + "ssoSettingsUpdated": "SSO-instellingen bijgewerkt", "ssoSettings": "SSO-instellingen", "ssoSettingsDescription": "Configureer hier uw Single Sign-On-instellingen.", "sso": "SSO", diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index 7047be37e3..f3fce05efb 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -636,7 +636,7 @@ "or": "lub", "errorImportingLibrary": "Błąd podczas importowania biblioteki", "libraryImportError": "Wystąpił błąd podczas importowania biblioteki", - "ssoSettingsupdated": "Ustawienia SSO zaktualizowane", + "ssoSettingsUpdated": "Ustawienia SSO zaktualizowane", "ssoSettings": "Ustawienia SSO", "ssoSettingsDescription": "Skonfiguruj ustawienia Single Sign-On tutaj.", "sso": "SSO", diff --git a/frontend/messages/pt.json b/frontend/messages/pt.json index 9f4836ab8f..0f5c89ca78 100644 --- a/frontend/messages/pt.json +++ b/frontend/messages/pt.json @@ -599,7 +599,7 @@ "or": "ou", "errorImportingLibrary": "Erro durante a importação da biblioteca", "libraryImportError": "Ocorreu um erro durante a importação da biblioteca", - "ssoSettingsupdated": "Configurações de SSO atualizadas", + "ssoSettingsUpdated": "Configurações de SSO atualizadas", "ssoSettings": "Configurações de SSO", "ssoSettingsDescription": "Defina suas configurações de logon único aqui.", "sso": "SSO", diff --git a/frontend/messages/ro.json b/frontend/messages/ro.json index 4a5226b391..e83758945e 100644 --- a/frontend/messages/ro.json +++ b/frontend/messages/ro.json @@ -634,7 +634,7 @@ "or": "sau", "errorImportingLibrary": "Eroare în timpul importului bibliotecii", "libraryImportError": "A apărut o eroare în timpul importului bibliotecii", - "ssoSettingsupdated": "Setările SSO au fost actualizate", + "ssoSettingsUpdated": "Setările SSO au fost actualizate", "ssoSettings": "Setări SSO", "ssoSettingsDescription": "Configurați setările dvs. Single Sign-On aici.", "sso": "SSO", diff --git a/frontend/messages/ur.json b/frontend/messages/ur.json index a3020efafe..dd13911df9 100644 --- a/frontend/messages/ur.json +++ b/frontend/messages/ur.json @@ -637,7 +637,7 @@ "or": "یا", "errorImportingLibrary": "لائبریری درآمد کرتے وقت خرابی", "libraryImportError": "لائبریری درآمد کرتے وقت ایک خرابی پیش آئی", - "ssoSettingsupdated": "SSO کی ترتیبات کو اپ ڈیٹ کر دیا گیا", + "ssoSettingsUpdated": "SSO کی ترتیبات کو اپ ڈیٹ کر دیا گیا", "ssoSettings": "SSO کی ترتیبات", "ssoSettingsDescription": "اپنی سنگل سائن آن کی ترتیبات یہاں ترتیب دیں۔", "sso": "SSO", diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte index 4588490906..d2f90e2cd3 100644 --- a/frontend/src/lib/components/Forms/ModelForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm.svelte @@ -23,6 +23,7 @@ import FrameworksForm from './ModelForm/FrameworkForm.svelte'; import UsersForm from './ModelForm/UserForm.svelte'; import SsoSettingsForm from './ModelForm/SsoSettingForm.svelte'; + import GlobalSettingsForm from './ModelForm/GlobalSettingForm.svelte'; import AutocompleteSelect from './AutocompleteSelect.svelte'; @@ -242,6 +243,8 @@ {:else if URLModel === 'sso-settings'} + {:else if URLModel === 'global-settings'} + {/if}
{#if closeModal} diff --git a/frontend/src/lib/components/Forms/ModelForm/GlobalSettingForm.svelte b/frontend/src/lib/components/Forms/ModelForm/GlobalSettingForm.svelte index 48c96a0caf..d90bcfd724 100644 --- a/frontend/src/lib/components/Forms/ModelForm/GlobalSettingForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm/GlobalSettingForm.svelte @@ -11,12 +11,12 @@ export let formDataCache: Record = {}; export let data: any = {}; - const langOptions = availableLanguageTags.map( - (lang) => { return { - label: LOCALE_DISPLAY_MAP[lang] ?? "[Unknown Language]", + const langOptions = availableLanguageTags.map((lang) => { + return { + label: LOCALE_DISPLAY_MAP[lang] ?? '[Unknown Language]', value: lang - };} - ); + }; + });