Skip to content

Commit

Permalink
Add a global setting for the default language
Browse files Browse the repository at this point in the history
  • Loading branch information
monsieurswag committed Oct 1, 2024
1 parent 7d7399f commit c3783f2
Show file tree
Hide file tree
Showing 25 changed files with 145 additions and 45 deletions.
7 changes: 5 additions & 2 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]:
Expand Down
3 changes: 2 additions & 1 deletion backend/global_settings/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
6 changes: 4 additions & 2 deletions backend/global_settings/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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"),
Expand Down
35 changes: 34 additions & 1 deletion backend/global_settings/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions backend/iam/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
7 changes: 3 additions & 4 deletions backend/library/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"])
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion frontend/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@
"or": "أو",
"errorImportingLibrary": "خطأ أثناء استيراد المكتبة",
"libraryImportError": "حدث خطأ أثناء استيراد المكتبة",
"ssoSettingsupdated": "تم تحديث إعدادات تسجيل الدخول الموحد",
"ssoSettingsUpdated": "تم تحديث إعدادات تسجيل الدخول الموحد",
"ssoSettings": "إعدادات تسجيل الدخول الموحد",
"ssoSettingsDescription": "قم بتكوين إعدادات تسجيل الدخول الموحد هنا.",
"sso": "SSO",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@
"or": "या",
"errorImportingLibrary": "लाइब्रेरी आयात के दौरान त्रुटि",
"libraryImportError": "लाइब्रेरी आयात के दौरान एक त्रुटि उत्पन्न हुई",
"ssoSettingsupdated": "SSO सेटिंग्स अपडेट की गईं",
"ssoSettingsUpdated": "SSO सेटिंग्स अपडेट की गईं",
"ssoSettings": "SSO सेटिंग्स",
"ssoSettingsDescription": "यहाँ अपने सिंगल साइन-ऑन सेटिंग्स कॉन्फ़िगर करें।",
"sso": "SSO",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/ro.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion frontend/messages/ur.json
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@
"or": "یا",
"errorImportingLibrary": "لائبریری درآمد کرتے وقت خرابی",
"libraryImportError": "لائبریری درآمد کرتے وقت ایک خرابی پیش آئی",
"ssoSettingsupdated": "SSO کی ترتیبات کو اپ ڈیٹ کر دیا گیا",
"ssoSettingsUpdated": "SSO کی ترتیبات کو اپ ڈیٹ کر دیا گیا",
"ssoSettings": "SSO کی ترتیبات",
"ssoSettingsDescription": "اپنی سنگل سائن آن کی ترتیبات یہاں ترتیب دیں۔",
"sso": "SSO",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/components/Forms/ModelForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -242,6 +243,8 @@
<UsersForm {form} {model} {cacheLocks} {formDataCache} {shape} />
{:else if URLModel === 'sso-settings'}
<SsoSettingsForm {form} {model} {cacheLocks} {formDataCache} {data} />
{:else if URLModel === 'global-settings'}
<GlobalSettingsForm {form} {model} {cacheLocks} {formDataCache} {data} />
{/if}
<div class="flex flex-row justify-between space-x-4">
{#if closeModal}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
export let formDataCache: Record<string, any> = {};
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
};}
);
};
});
</script>

<Select
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/components/Forms/Select.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
{@const defaultValue = blank ? '' : null}
<option value={defaultValue} selected>--</option>
{/if}
{JSON.stringify(options)}
{#each options as option}
<option value={option.value} style="background-color: {color_map[option.value]}">
{safeTranslate(option.label)}
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/lib/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ export const LOCALE_DISPLAY_MAP = {
pt: '🇵🇹 Português',
pl: '🇵🇱 Polski',
ro: '🇷🇴 Română',
ar: '🇸🇦 العربية'
ar: '🇸🇦 العربية',
hi: '🇮🇳 हिन्दी',
ur: '🇵🇰 اردو'
};

export const ISO_8601_REGEX =
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ export const SSOSettingsSchema = z.object({
want_name_id_encrypted: z.boolean().optional().nullable()
});

export const GlobalSettingsSchema = z.object({
lang: z.string()
});

export const EntitiesSchema = baseNamedObject({
folder: z.string(),
mission: z.string().optional(),
Expand Down Expand Up @@ -343,6 +347,7 @@ const SCHEMA_MAP: Record<string, AnyZodObject> = {
evidences: EvidenceSchema,
users: UserCreateSchema,
'sso-settings': SSOSettingsSchema,
'global-settings': GlobalSettingsSchema,
entities: EntitiesSchema,
'entity-assessments': EntityAssessmentSchema,
representatives: representativeSchema,
Expand Down
Loading

0 comments on commit c3783f2

Please sign in to comment.