diff --git a/backend/core/helpers.py b/backend/core/helpers.py index 7b50a30cc..a6792e8ff 100644 --- a/backend/core/helpers.py +++ b/backend/core/helpers.py @@ -1278,7 +1278,7 @@ def duplicate_and_link_object(new_obj, duplicate_object, target_folder, field_na # Get parent and sub-folders of the target folder target_parent_folders = target_folder.get_parent_folders() - sub_folders = target_folder.sub_folders() + sub_folders = target_folder.get_sub_folders() # Get all related objects for the specified field related_objects = getattr(source_object, field_name).all() diff --git a/backend/core/models.py b/backend/core/models.py index 1a72b87f2..5ab669cfd 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1787,9 +1787,6 @@ def risk_assessments(self): def projects(self): return {risk_assessment.project for risk_assessment in self.risk_assessments} - def parent_project(self): - pass - def __str__(self): return self.name diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 1b2661a14..c8490d807 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -338,8 +338,15 @@ class AppliedControlReadSerializer(AppliedControlWriteSerializer): ranking_score = serializers.IntegerField(source="get_ranking_score") owner = FieldsRelatedField(many=True) - has_evidences = serializers.BooleanField() - eta_missed = serializers.BooleanField() + # These properties shouldn't be displayed in the frontend detail view as they are simple derivations from fields already displayed in the detail view. + # has_evidences = serializers.BooleanField() + # eta_missed = serializers.BooleanField() + + +class AppliedControlDuplicateSerializer(BaseModelSerializer): + class Meta: + model = AppliedControl + fields = ["name", "description", "folder"] class PolicyWriteSerializer(AppliedControlWriteSerializer): diff --git a/backend/core/views.py b/backend/core/views.py index 73a74924e..12e99d5f6 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -1154,6 +1154,53 @@ def get_timeline_info(self, request): colorMap[domain.name] = next(color_cycle) return Response({"entries": entries, "colorMap": colorMap}) + @action( + detail=True, + name="Duplicate applied control", + methods=["post"], + serializer_class=AppliedControlDuplicateSerializer, + ) + def duplicate(self, request, pk): + (object_ids_view, _, _) = RoleAssignment.get_accessible_object_ids( + Folder.get_root_folder(), request.user, AppliedControl + ) + if UUID(pk) not in object_ids_view: + return Response( + {"results": "applied control duplicated"}, + status=status.HTTP_404_NOT_FOUND, + ) + + applied_control = self.get_object() + data = request.data + new_folder = Folder.objects.get(id=data["folder"]) + duplicate_applied_control = AppliedControl.objects.create( + reference_control=applied_control.reference_control, + name=data["name"], + description=data["description"], + folder=new_folder, + ref_id=applied_control.ref_id, + category=applied_control.category, + csf_function=applied_control.csf_function, + priority=applied_control.priority, + status=applied_control.status, + start_date=applied_control.start_date, + eta=applied_control.eta, + expiry_date=applied_control.expiry_date, + link=applied_control.link, + effort=applied_control.effort, + cost=applied_control.cost, + ) + duplicate_applied_control.owner.set(applied_control.owner.all()) + if data["duplicate_evidences"]: + duplicate_related_objects( + applied_control, duplicate_applied_control, new_folder, "evidences" + ) + duplicate_applied_control.save() + + return Response( + {"results": AppliedControlReadSerializer(duplicate_applied_control).data} + ) + @action(detail=False, methods=["get"]) def ids(self, request): my_map = dict() diff --git a/backend/iam/models.py b/backend/iam/models.py index e67ce56ec..b25cb9ee7 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -2,7 +2,7 @@ Inspired from Azure IAM model""" from collections import defaultdict -from typing import Any, List, Self, Tuple +from typing import Any, List, Self, Tuple, Generator import uuid from allauth.account.models import EmailAddress from django.utils import timezone @@ -101,24 +101,22 @@ class Meta: def __str__(self) -> str: return self.name.__str__() - def sub_folders(self) -> List[Self]: + def get_sub_folders(self) -> Generator[Self, None, None]: """Return the list of subfolders""" - def sub_folders_in(f, sub_folder_list): - for sub_folder in f.folder_set.all(): - sub_folder_list.append(sub_folder) - sub_folders_in(sub_folder, sub_folder_list) - return sub_folder_list + def sub_folders_in(folder): + for sub_folder in folder.folder_set.all(): + yield sub_folder + yield from sub_folders_in(sub_folder) - return sub_folders_in(self, []) + yield from sub_folders_in(self) - def get_parent_folders(self) -> List[Self]: + # Should we update data-model.md now that this method is a generator ? + def get_parent_folders(self) -> Generator[Self, None, None]: """Return the list of parent folders""" - return ( - [self.parent_folder] + Folder.get_parent_folders(self.parent_folder) - if self.parent_folder - else [] - ) + current_folder = self + while (current_folder := current_folder.parent_folder) is not None: + yield current_folder @staticmethod def _navigate_structure(start, path): @@ -657,11 +655,11 @@ def get_accessible_folders( ]: for f in ra.perimeter_folders.all(): folders_set.add(f) - folders_set.update(f.sub_folders()) + folders_set.update(f.get_sub_folders()) # calculate perimeter perimeter = set() perimeter.add(folder) - perimeter.update(folder.sub_folders()) + perimeter.update(folder.get_sub_folders()) # return filtered result return [ x.id @@ -698,7 +696,7 @@ def get_accessible_object_ids( folder_for_object = {x: Folder.get_folder(x) for x in all_objects} perimeter = set() perimeter.add(folder) - perimeter.update(folder.sub_folders()) + perimeter.update(folder.get_sub_folders()) for ra in [ x for x in RoleAssignment.get_role_assignments(user) @@ -707,7 +705,7 @@ def get_accessible_object_ids( ra_permissions = ra.role.permissions.all() for my_folder in perimeter & set(ra.perimeter_folders.all()): target_folders = ( - [my_folder] + my_folder.sub_folders() + [my_folder, *my_folder.get_sub_folders()] if ra.is_recursive else [my_folder] ) diff --git a/frontend/messages/ar.json b/frontend/messages/ar.json index ec7de51fd..1bcdb4f62 100644 --- a/frontend/messages/ar.json +++ b/frontend/messages/ar.json @@ -679,6 +679,7 @@ "back": "خلف", "duplicate": "ينسخ", "duplicateRiskAssessment": "تكرار تقييم المخاطر", + "duplicateAppliedControl": "تكرار عنصر التحكم المطبق", "size": "الحجم", "favicon": "الرمز المفضل", "logo": "الشعار", @@ -862,5 +863,7 @@ "tagsHelpText": "تُستخدم العلامات لتصنيف العناصر وتصفيتها. يمكنك إضافة علامات في قسم \"إضافي\"", "forgotPassword": "هل نسيت كلمة السر", "scoreSemiColon": "نتيجة:", - "mappingInferenceHelpText": "هذه المتغيرات ثابتة ولن تتغير اعتمادًا على المصدر." + "mappingInferenceHelpText": "هذه المتغيرات ثابتة ولن تتغير اعتمادًا على المصدر.", + "bringTheEvidences": "أحضر الأدلة", + "bringTheEvidencesHelpText": "في حالة التعطيل، سيتم تكرار الكائن بدون أدلته" } diff --git a/frontend/messages/cz.json b/frontend/messages/cz.json index 8627041f5..20895bae2 100644 --- a/frontend/messages/cz.json +++ b/frontend/messages/cz.json @@ -852,5 +852,7 @@ "exploreButton": "Prozkoumat", "tags": "Štítky", "addTag": "Přidat štítek", - "tagsHelpText": "Štítky pomáhají kategorizovat a filtrovat více objektů. Můžete je spravovat v menu Extra." + "tagsHelpText": "Štítky pomáhají kategorizovat a filtrovat více objektů. Můžete je spravovat v menu Extra.", + "bringTheEvidences": "Přineste důkazy", + "bringTheEvidencesHelpText": "Pokud je zakázáno, objekt bude duplikován bez jeho důkazů" } diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 75a2f2542..3be9296d1 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -678,6 +678,7 @@ "back": "Zurückkehren", "duplicate": "Duplikat", "duplicateRiskAssessment": "Duplizieren Sie die Risikobewertung", + "duplicateAppliedControl": "Duplizieren Sie das angewendete kontrolle", "size": "Größe", "favicon": "Favicon", "logo": "Logo", @@ -861,5 +862,7 @@ "tagsHelpText": "Tags werden zum Kategorisieren und Filtern der Elemente verwendet. Sie können Tags im Abschnitt Extra hinzufügen", "forgotPassword": "Passwort vergessen", "scoreSemiColon": "Punktzahl:", - "mappingInferenceHelpText": "Diese Variablen sind fest und ändern sich je nach Quelle nicht." + "mappingInferenceHelpText": "Diese Variablen sind fest und ändern sich je nach Quelle nicht.", + "bringTheEvidences": "Bringen Sie die Beweise", + "bringTheEvidencesHelpText": "Wenn deaktiviert, wird das Objekt ohne seine Beweise dupliziert" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 8be7b78d0..9d5b87a36 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -682,6 +682,7 @@ "back": "Back", "duplicate": "Duplicate", "duplicateRiskAssessment": "Duplicate the risk assessment", + "duplicateAppliedControl": "Duplicate the applied control", "size": "Size", "favicon": "Favicon", "logo": "Logo", @@ -919,5 +920,7 @@ "ebiosWs5_3": "Define security measures", "ebiosWs5_4": "Assess and document residual risks", "ebiosWs5_5": "Establish risk monitoring framework", - "activity": "Activity" + "activity": "Activity", + "bringTheEvidences": "Bring the evidences", + "bringTheEvidencesHelpText": "If disabled, the object will be duplicated without its evidences" } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index e1589f59f..6d3af4836 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -678,6 +678,7 @@ "back": "Devolver", "duplicate": "Duplicar", "duplicateRiskAssessment": "Duplicar la evaluación de riesgo", + "duplicateAppliedControl": "Duplicar el control aplicado", "size": "Tamaño", "favicon": "Favicon", "logo": "Logo", @@ -861,5 +862,7 @@ "tagsHelpText": "Las etiquetas se utilizan para categorizar y filtrar los elementos. Puedes agregar etiquetas en la sección Extra", "forgotPassword": "Has olvidado tu contraseña", "scoreSemiColon": "Puntaje:", - "mappingInferenceHelpText": "Estas variables son fijas y no cambiarán dependiendo de la fuente." + "mappingInferenceHelpText": "Estas variables son fijas y no cambiarán dependiendo de la fuente.", + "bringTheEvidences": "Traer las evidencias", + "bringTheEvidencesHelpText": "Si está deshabilitado, el objeto se duplicará sin sus evidencias." } diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 29ce9296c..a67e21bca 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -682,6 +682,7 @@ "back": "Retour", "duplicate": "Dupliquer", "duplicateRiskAssessment": "Dupliquer l’évaluation de risque", + "duplicateAppliedControl": "Dupliquer la mesure appliquée", "size": "Taille", "favicon": "Icône de favori", "logo": "Logo", @@ -895,5 +896,7 @@ "ebiosWs5_3": "Définir les mesures de sécurité", "ebiosWs5_4": "Évaluer et documenter les risques résiduels", "ebiosWs5_5": "Mettre en place le cadre de suivi des risques", - "activity": "Activité" + "activity": "Activité", + "bringTheEvidences": "Apportez les preuves", + "bringTheEvidencesHelpText": "Si désactivé, l'objet sera dupliqué sans ses preuves" } diff --git a/frontend/messages/hi.json b/frontend/messages/hi.json index addacbc67..051d2e76c 100644 --- a/frontend/messages/hi.json +++ b/frontend/messages/hi.json @@ -678,6 +678,7 @@ "back": "वापस", "duplicate": "प्रतिलिपि", "duplicateRiskAssessment": "जोखिम आकलन की प्रतिलिपि बनाएँ", + "duplicateAppliedControl": "लागू नियंत्रण की प्रतिलिपि बनाएँ", "size": "आकार", "favicon": "फ़ेविकॉन", "logo": "प्रतीक चिन्ह", @@ -861,5 +862,7 @@ "tagsHelpText": "टैग का उपयोग आइटम को वर्गीकृत और फ़िल्टर करने के लिए किया जाता है। आप अतिरिक्त अनुभाग में टैग जोड़ सकते हैं", "forgotPassword": "पासवर्ड भूल गए", "scoreSemiColon": "अंक:", - "mappingInferenceHelpText": "ये चर निश्चित हैं और स्रोत के आधार पर परिवर्तित नहीं होंगे।" + "mappingInferenceHelpText": "ये चर निश्चित हैं और स्रोत के आधार पर परिवर्तित नहीं होंगे।", + "bringTheEvidences": "सबूत लाओ", + "bringTheEvidencesHelpText": "यदि अक्षम किया गया है, तो ऑब्जेक्ट को उसके साक्ष्य के बिना डुप्लिकेट किया जाएगा" } diff --git a/frontend/messages/it.json b/frontend/messages/it.json index 66e002154..837ab5dea 100644 --- a/frontend/messages/it.json +++ b/frontend/messages/it.json @@ -678,6 +678,7 @@ "back": "Ripetere", "duplicate": "Duplicare", "duplicateRiskAssessment": "Duplicare la valutazione del rischio", + "duplicateAppliedControl": "Duplica il controllo applicato", "size": "Dimensione", "favicon": "Icona preferita", "logo": "Logo", @@ -861,5 +862,7 @@ "tagsHelpText": "I tag vengono utilizzati per categorizzare e filtrare gli elementi. Puoi aggiungere tag nella sezione Extra", "forgotPassword": "Ha dimenticato la password", "scoreSemiColon": "Punto:", - "mappingInferenceHelpText": "Queste variabili sono fisse e non cambiano a seconda della fonte." + "mappingInferenceHelpText": "Queste variabili sono fisse e non cambiano a seconda della fonte.", + "bringTheEvidences": "Portare le prove", + "bringTheEvidencesHelpText": "Se disabilitato, l'oggetto verrà duplicato senza le sue prove" } diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json index 116bb03f8..fab932bd3 100644 --- a/frontend/messages/nl.json +++ b/frontend/messages/nl.json @@ -678,6 +678,7 @@ "back": "Opbrengst", "duplicate": "Duplicaat", "duplicateRiskAssessment": "Dupliceer de risicobeoordeling", + "duplicateAppliedControl": "Dupliceer de toegepaste controle", "size": "Grootte", "favicon": "Favorieten", "logo": "Logo", @@ -861,5 +862,7 @@ "tagsHelpText": "Tags worden gebruikt om de items te categoriseren en te filteren. U kunt tags toevoegen in de sectie Extra", "forgotPassword": "Wachtwoord vergeten", "scoreSemiColon": "Punt:", - "mappingInferenceHelpText": "Deze variabelen zijn vast en veranderen niet, afhankelijk van de bron." + "mappingInferenceHelpText": "Deze variabelen zijn vast en veranderen niet, afhankelijk van de bron.", + "bringTheEvidences": "Breng de bewijzen", + "bringTheEvidencesHelpText": "Als dit is uitgeschakeld, wordt het object gedupliceerd zonder de bijbehorende bewijzen" } diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index 06e202ddc..1991efea2 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -678,6 +678,7 @@ "back": "Powrót", "duplicate": "Duplikować", "duplicateRiskAssessment": "Powielić ocenę ryzyka", + "duplicateAppliedControl": "Duplikuj zastosowaną kontrolę", "size": "Rozmiar", "favicon": "Favicon", "logo": "Logo", @@ -861,5 +862,7 @@ "tagsHelpText": "Tagi służą do kategoryzowania i filtrowania elementów. Możesz dodać tagi w sekcji Extra", "forgotPassword": "Zapomniałem hasła", "scoreSemiColon": "Wynik:", - "mappingInferenceHelpText": "Te zmienne są stałe i nie zmieniają się w zależności od źródła." + "mappingInferenceHelpText": "Te zmienne są stałe i nie zmieniają się w zależności od źródła.", + "bringTheEvidences": "Przynieś dowody", + "bringTheEvidencesHelpText": "Jeśli wyłączone, obiekt zostanie zduplikowany bez dowodów" } diff --git a/frontend/messages/pt.json b/frontend/messages/pt.json index 139456ede..4ecdb5a0a 100644 --- a/frontend/messages/pt.json +++ b/frontend/messages/pt.json @@ -678,6 +678,7 @@ "back": "Retornar", "duplicate": "Duplicado", "duplicateRiskAssessment": "Duplicar a avaliação de risco", + "duplicateAppliedControl": "Duplicar o controle aplicado", "size": "Tamanho", "favicon": "Favicon", "logo": "Logotipo", @@ -861,5 +862,7 @@ "tagsHelpText": "As tags são usadas para categorizar e filtrar os itens. Você pode adicionar tags na seção Extra", "forgotPassword": "Esqueceu sua senha", "scoreSemiColon": "Pontuação:", - "mappingInferenceHelpText": "Essas variáveis são fixas e não mudarão dependendo da fonte." + "mappingInferenceHelpText": "Essas variáveis são fixas e não mudarão dependendo da fonte.", + "bringTheEvidences": "Traga as evidências", + "bringTheEvidencesHelpText": "Se desabilitado, o objeto será duplicado sem suas evidências" } diff --git a/frontend/messages/ro.json b/frontend/messages/ro.json index 86c4c3212..09b399431 100644 --- a/frontend/messages/ro.json +++ b/frontend/messages/ro.json @@ -678,6 +678,7 @@ "back": "Înapoi", "duplicate": "Dublică", "duplicateRiskAssessment": "Dublarea evaluării riscului", + "duplicateAppliedControl": "Duplicați control aplicat", "size": "Mărime", "favicon": "Favicon", "logo": "Logo", @@ -861,5 +862,7 @@ "tagsHelpText": "Etichetele sunt folosite pentru a clasifica și filtra articolele. Puteți adăuga etichete în secțiunea Extra", "forgotPassword": "Aţi uitat parola", "scoreSemiColon": "Scor:", - "mappingInferenceHelpText": "Aceste variabile sunt fixe și nu se vor modifica în funcție de sursă." + "mappingInferenceHelpText": "Aceste variabile sunt fixe și nu se vor modifica în funcție de sursă.", + "bringTheEvidences": "Aduceți dovezile", + "bringTheEvidencesHelpText": "Dacă este dezactivat, obiectul va fi duplicat fără dovezile sale" } diff --git a/frontend/messages/sv.json b/frontend/messages/sv.json index 69ae0d8d8..aeee7ca16 100644 --- a/frontend/messages/sv.json +++ b/frontend/messages/sv.json @@ -862,5 +862,7 @@ "forgotPassword": "Glömt lösenord", "ssoSettingsUpdated": "SSO-inställningar uppdaterade", "scoreSemiColon": "Göra:", - "mappingInferenceHelpText": "Dessa variabler är fasta och kommer inte att ändras beroende på källan." + "mappingInferenceHelpText": "Dessa variabler är fasta och kommer inte att ändras beroende på källan.", + "bringTheEvidences": "Kom med bevisen", + "bringTheEvidencesHelpText": "Om det är inaktiverat kommer objektet att dupliceras utan dess bevis" } diff --git a/frontend/messages/ur.json b/frontend/messages/ur.json index 13c5db0b6..0d822959d 100644 --- a/frontend/messages/ur.json +++ b/frontend/messages/ur.json @@ -678,6 +678,7 @@ "back": "واپس", "duplicate": "نقل کریں", "duplicateRiskAssessment": "خطرے کی تشخیص کو نقل کریں", + "duplicateAppliedControl": "لاگو کنٹرول کی نقل تیار کریں۔", "size": "سائز", "favicon": "فیویکان", "logo": "لوگو", @@ -861,5 +862,7 @@ "tagsHelpText": "ٹیگز اشیاء کی درجہ بندی اور فلٹر کرنے کے لیے استعمال ہوتے ہیں۔ آپ اضافی سیکشن میں ٹیگ شامل کر سکتے ہیں۔", "forgotPassword": "پاس ورڈ بھول گئے۔", "scoreSemiColon": "سکور:", - "mappingInferenceHelpText": "یہ متغیرات طے شدہ ہیں اور ماخذ کے لحاظ سے تبدیل نہیں ہوں گے۔" + "mappingInferenceHelpText": "یہ متغیرات طے شدہ ہیں اور ماخذ کے لحاظ سے تبدیل نہیں ہوں گے۔", + "bringTheEvidences": "ثبوت لے کر آئیں", + "bringTheEvidencesHelpText": "اگر غیر فعال ہو تو، اعتراض کو اس کے ثبوت کے بغیر نقل کر دیا جائے گا۔" } diff --git a/frontend/src/lib/components/DetailView/DetailView.svelte b/frontend/src/lib/components/DetailView/DetailView.svelte index a40297fe9..4df9af7c4 100644 --- a/frontend/src/lib/components/DetailView/DetailView.svelte +++ b/frontend/src/lib/components/DetailView/DetailView.svelte @@ -125,6 +125,26 @@ modalStore.trigger(modal); } + function modalAppliedControlDuplicateForm(): void { + const modalComponent: ModalComponent = { + ref: CreateModal, + props: { + form: data.duplicateForm, + model: data.model, + debug: false, + duplicate: true, + formAction: '?/duplicate' + } + }; + + const modal: ModalSettings = { + type: 'component', + component: modalComponent, + title: m.duplicateAppliedControl() + }; + modalStore.trigger(modal); + } + function modalMailConfirm(id: string, name: string, action: string): void { const modalComponent: ModalComponent = { ref: ConfirmModal, @@ -329,11 +349,24 @@ {/if} {#if displayEditButton()} - {m.edit()} +