diff --git a/backend/ciso_assistant/settings.py b/backend/ciso_assistant/settings.py index bff8abcd7..61f3528bf 100644 --- a/backend/ciso_assistant/settings.py +++ b/backend/ciso_assistant/settings.py @@ -138,9 +138,9 @@ def set_ciso_assistant_url(_, __, event_dict): "tailwind", "iam", "global_settings", + "ebios_rm", "tprm", "core", - "ebios_rm", "cal", "django_filters", "library", diff --git a/backend/core/serializers.py b/backend/core/serializers.py index c8490d807..e55df2c53 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -5,6 +5,7 @@ from core.models import * from iam.models import * +from ebios_rm.models import EbiosRMStudy from rest_framework import serializers from rest_framework.exceptions import PermissionDenied @@ -600,6 +601,13 @@ class ComplianceAssessmentWriteSerializer(BaseModelSerializer): required=False, allow_null=True, ) + ebios_rm_studies = serializers.PrimaryKeyRelatedField( + many=True, + queryset=EbiosRMStudy.objects.all(), + required=False, + allow_null=True, + write_only=True, + ) create_applied_controls_from_suggestions = serializers.BooleanField( write_only=True, required=False, default=False ) @@ -712,3 +720,13 @@ class FilteringLabelWriteSerializer(BaseModelSerializer): class Meta: model = FilteringLabel exclude = ["folder", "is_published"] + + +class QualificationReadSerializer(ReferentialSerializer): + class Meta: + model = Qualification + exclude = ["translations"] + + +class QualificationWriteSerializer(QualificationReadSerializer): + pass diff --git a/backend/core/startup.py b/backend/core/startup.py index 37c06a1e5..3196379e2 100644 --- a/backend/core/startup.py +++ b/backend/core/startup.py @@ -363,6 +363,10 @@ "view_operationalscenario", "change_operationalscenario", "delete_operationalscenario", + "view_qualification", + "add_qualification", + "change_qualification", + "delete_qualification", ] THIRD_PARTY_RESPONDENT_PERMISSIONS_LIST = [ diff --git a/backend/core/urls.py b/backend/core/urls.py index 57b55901e..0b79a6c3c 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -66,6 +66,11 @@ FilteringLabelViewSet, basename="filtering-labels", ) +router.register( + r"qualifications", + QualificationViewSet, + basename="qualifications", +) ROUTES = settings.ROUTES MODULES = settings.MODULES.values() diff --git a/backend/core/views.py b/backend/core/views.py index d7c825dd7..858cf7118 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -358,7 +358,13 @@ class AssetViewSet(BaseModelViewSet): """ model = Asset - filterset_fields = ["folder", "parent_assets", "type", "risk_scenarios"] + filterset_fields = [ + "folder", + "parent_assets", + "type", + "risk_scenarios", + "ebios_rm_studies", + ] search_fields = ["name", "description", "business_value"] @action(detail=False, name="Get type choices") @@ -2025,13 +2031,22 @@ def post(self, request, *args, **kwargs): return Response(status=status.HTTP_400_BAD_REQUEST) +class QualificationViewSet(BaseModelViewSet): + """ + API endpoint that allows qualifications to be viewed or edited. + """ + + model = Qualification + search_fields = ["name"] + + class ComplianceAssessmentViewSet(BaseModelViewSet): """ API endpoint that allows compliance assessments to be viewed or edited. """ model = ComplianceAssessment - filterset_fields = ["framework", "project", "status"] + filterset_fields = ["framework", "project", "status", "ebios_rm_studies"] search_fields = ["name", "description", "ref_id"] ordering_fields = ["name", "description"] diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py index 66af48c5f..8980b23f0 100644 --- a/backend/ebios_rm/migrations/0001_initial.py +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -163,6 +163,7 @@ class Migration(migrations.Migration): related_name="ebios_rm_studies", to="core.riskmatrix", verbose_name="Risk matrix", + blank=True, ), ), ], diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 99104508d..30543cba9 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -31,6 +31,7 @@ class Status(models.TextChoices): help_text=_( "Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`" ), + blank=True, ) assets = models.ManyToManyField( Asset, @@ -100,6 +101,10 @@ class Meta: verbose_name_plural = _("Ebios RM Studies") ordering = ["created_at"] + @property + def parsed_matrix(self): + return self.risk_matrix.parse_json_translated() + class FearedEvent(NameDescriptionMixin, FolderMixin): ebios_rm_study = models.ForeignKey( @@ -136,6 +141,28 @@ def save(self, *args, **kwargs): self.folder = self.ebios_rm_study.folder super().save(*args, **kwargs) + @property + def risk_matrix(self): + return self.ebios_rm_study.risk_matrix + + @property + def parsed_matrix(self): + return self.risk_matrix.parse_json_translated() + + def get_gravity_display(self): + if self.gravity < 0: + return { + "abbreviation": "--", + "name": "--", + "description": "not rated", + "value": -1, + } + risk_matrix = self.parsed_matrix + return { + **risk_matrix["impact"][self.gravity], + "value": self.gravity, + } + class RoTo(AbstractBaseModel, FolderMixin): class RiskOrigin(models.TextChoices): diff --git a/backend/ebios_rm/serializers.py b/backend/ebios_rm/serializers.py index e77c5c153..15da633fc 100644 --- a/backend/ebios_rm/serializers.py +++ b/backend/ebios_rm/serializers.py @@ -71,8 +71,10 @@ class Meta: class FearedEventReadSerializer(BaseModelSerializer): - str = serializers.CharField(source="__str__") ebios_rm_study = FieldsRelatedField() + qualifications = FieldsRelatedField(["name"], many=True) + assets = FieldsRelatedField(many=True) + gravity = serializers.JSONField(source="get_gravity_display") folder = FieldsRelatedField() class Meta: diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py index ca0d048dd..af0360603 100644 --- a/backend/ebios_rm/views.py +++ b/backend/ebios_rm/views.py @@ -1,3 +1,4 @@ +from core.serializers import RiskMatrixReadSerializer from core.views import BaseModelViewSet as AbstractBaseModelViewSet from .models import ( EbiosRMStudy, @@ -31,10 +32,47 @@ class EbiosRMStudyViewSet(BaseModelViewSet): def status(self, request): return Response(dict(EbiosRMStudy.Status.choices)) + @method_decorator(cache_page(60 * LONG_CACHE_TTL)) + @action(detail=True, name="Get gravity choices") + def gravity(self, request, pk): + study: EbiosRMStudy = self.get_object() + undefined = dict([(-1, "--")]) + _choices = dict( + zip( + list(range(0, 64)), + [x["name"] for x in study.parsed_matrix["impact"]], + ) + ) + choices = undefined | _choices + return Response(choices) + class FearedEventViewSet(BaseModelViewSet): model = FearedEvent + filterset_fields = [ + "ebios_rm_study", + ] + + @action(detail=True, name="Get risk matrix", url_path="risk-matrix") + def risk_matrix(self, request, pk=None): + feared_event = self.get_object() + return Response(RiskMatrixReadSerializer(feared_event.risk_matrix).data) + + @method_decorator(cache_page(60 * LONG_CACHE_TTL)) + @action(detail=True, name="Get gravity choices") + def gravity(self, request, pk): + feared_event: FearedEvent = self.get_object() + undefined = dict([(-1, "--")]) + _choices = dict( + zip( + list(range(0, 64)), + [x["name"] for x in feared_event.parsed_matrix["impact"]], + ) + ) + choices = undefined | _choices + return Response(choices) + class RoToViewSet(BaseModelViewSet): model = RoTo diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 51665084a..11b1f8bf3 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -921,9 +921,21 @@ "ebiosWs5_4": "Assess and document residual risks", "ebiosWs5_5": "Establish risk monitoring framework", "activity": "Activity", + "ebiosRmMatrixHelpText": "Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`", + "activityOne": "Activity 1", + "activityTwo": "Activity 2", + "ebiosRmStudy": "Ebios RM study", + "qualifications": "Qualifications", + "impacts": "Impacts", + "ebiosRmStudies": "Ebios RM studies", "bringTheEvidences": "Bring the evidences", "bringTheEvidencesHelpText": "If disabled, the object will be duplicated without its evidences", + "gravity": "Gravity", "existingControlsHelper": "What do you currently have to manage this risk", "extraControlsHelper": "What will you do to mitigate this risk", - "existingContextHelper": "Description of the existing mitigations (this field will be deprecated soon)" + "existingContextHelper": "Description of the existing mitigations (this field will be deprecated soon)", + "fearedEvent": "Feared event", + "fearedEvents": "Feared events", + "isSelected": "Is selected", + "ebiosRM": "Ebios RM" } diff --git a/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte b/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte index 7ceac4475..ff92026a7 100644 --- a/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte +++ b/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte @@ -17,7 +17,7 @@ let tokenPath = ''; crumbs = tokens.map((t) => { tokenPath += '/' + t; - if (t === $breadcrumbObject.id) { + if (t === $breadcrumbObject?.id) { if ($breadcrumbObject.name) { t = $breadcrumbObject.name; } else if ($breadcrumbObject.first_name && $breadcrumbObject.last_name) { diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte index da9c227a7..7f30a2a71 100644 --- a/frontend/src/lib/components/Forms/ModelForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm.svelte @@ -26,6 +26,8 @@ import SsoSettingsForm from './ModelForm/SsoSettingForm.svelte'; import FolderForm from './ModelForm/FolderForm.svelte'; import GeneralSettingsForm from './ModelForm/GeneralSettingForm.svelte'; + import EbiosRmForm from './ModelForm/EbiosRmForm.svelte'; + import FearedEventForm from './ModelForm/FearedEventForm.svelte'; import AutocompleteSelect from './AutocompleteSelect.svelte'; @@ -255,6 +257,10 @@ {:else if URLModel === 'filtering-labels'} + {:else if URLModel === 'ebios-rm'} + + {:else if URLModel === 'feared-events'} + {/if}
{#if closeModal} diff --git a/frontend/src/lib/components/Forms/ModelForm/ComplianceAssessmentForm.svelte b/frontend/src/lib/components/Forms/ModelForm/ComplianceAssessmentForm.svelte index c53338fa0..0bba0a55c 100644 --- a/frontend/src/lib/components/Forms/ModelForm/ComplianceAssessmentForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm/ComplianceAssessmentForm.svelte @@ -38,6 +38,18 @@ options={getOptions({ objects: model.foreignKeys['baseline'] })} /> {/if} +{#if initialData.ebios_rm_studies} +