diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 9fd9c0cd3..b7c8a7081 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -12,6 +12,7 @@ ) from iam.models import FolderMixin, User from tprm.models import Entity +import json class EbiosRMStudy(NameDescriptionMixin, ETADueDateMixin, FolderMixin): @@ -246,6 +247,13 @@ def get_pertinence(self): PERTINENCE_MATRIX[self.motivation - 1][self.resources - 1] ).label + def get_gravity(self): + gravity = -1 + for feared_event in self.feared_events.all(): + if feared_event.gravity > gravity: + gravity = feared_event.gravity + return gravity + class Stakeholder(AbstractBaseModel, FolderMixin): class Category(models.TextChoices): @@ -410,6 +418,10 @@ def save(self, *args, **kwargs): self.folder = self.ebios_rm_study.folder super().save(*args, **kwargs) + @property + def gravity(self): + return self.ro_to_couple.get_gravity() + class OperationalScenario(AbstractBaseModel, FolderMixin): ebios_rm_study = models.ForeignKey( @@ -458,6 +470,27 @@ def risk_matrix(self): def parsed_matrix(self): return self.risk_matrix.parse_json_translated() + @property + def ref_id(self): + sorted_operational_scenarios = list( + OperationalScenario.objects.filter( + ebios_rm_study=self.ebios_rm_study + ).order_by("created_at") + ) + return sorted_operational_scenarios.index(self) + 1 + + @property + def gravity(self): + return self.attack_path.gravity + + @property + def stakeholders(self): + return self.attack_path.stakeholders.all() + + @property + def ro_to(self): + return self.attack_path.ro_to_couple + def get_likelihood_display(self): if self.likelihood < 0: return { @@ -465,9 +498,39 @@ def get_likelihood_display(self): "name": "--", "description": "not rated", "value": -1, + "hexcolor": "#f9fafb", } risk_matrix = self.parsed_matrix return { **risk_matrix["probability"][self.likelihood], "value": self.likelihood, } + + 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, + } + + def get_risk_level_display(self): + if self.likelihood < 0 or self.gravity < 0: + return { + "abbreviation": "--", + "name": "--", + "description": "not rated", + "value": -1, + } + risk_matrix = self.parsed_matrix + risk_index = risk_matrix["grid"][self.likelihood][self.gravity] + return { + **risk_matrix["risk"][risk_index], + "value": risk_index, + } diff --git a/backend/ebios_rm/serializers.py b/backend/ebios_rm/serializers.py index 041b5904f..0b0ec8a72 100644 --- a/backend/ebios_rm/serializers.py +++ b/backend/ebios_rm/serializers.py @@ -165,9 +165,14 @@ class OperationalScenarioReadSerializer(BaseModelSerializer): str = serializers.CharField(source="__str__") ebios_rm_study = FieldsRelatedField() folder = FieldsRelatedField() - attack_path = FieldsRelatedField() + attack_path = FieldsRelatedField(["id", "name", "description"]) + stakeholders = FieldsRelatedField(many=True) + ro_to = FieldsRelatedField(["risk_origin", "target_objective"]) threats = FieldsRelatedField(many=True) likelihood = serializers.JSONField(source="get_likelihood_display") + gravity = serializers.JSONField(source="get_gravity_display") + risk_level = serializers.JSONField(source="get_risk_level_display") + ref_id = serializers.CharField() class Meta: model = OperationalScenario diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py index 6d35d48a1..5c9515d04 100644 --- a/backend/ebios_rm/views.py +++ b/backend/ebios_rm/views.py @@ -1,3 +1,4 @@ +import django_filters as df from core.serializers import RiskMatrixReadSerializer from core.views import BaseModelViewSet as AbstractBaseModelViewSet from core.serializers import RiskMatrixReadSerializer @@ -123,12 +124,23 @@ def category(self, request): return Response(dict(Stakeholder.Category.choices)) +class AttackPathFilter(df.FilterSet): + used = df.BooleanFilter(method="is_used", label="Used") + + def is_used(self, queryset, name, value): + if value: + return queryset.filter(operational_scenario__isnull=False) + return queryset.filter(operational_scenario__isnull=True) + + class Meta: + model = AttackPath + fields = ["ebios_rm_study", "is_selected", "used"] + + class AttackPathViewSet(BaseModelViewSet): model = AttackPath - filterset_fields = [ - "ebios_rm_study", - ] + filterset_class = AttackPathFilter class OperationalScenarioViewSet(BaseModelViewSet): diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 659e7ba99..30c486f51 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -983,9 +983,6 @@ "currentCriticality": "Current criticality", "residualCriticality": "Residual criticality", "notSelected": "Not selected", - "identifyRoTo": "Identify RO/TO", - "evaluateRoTo": "Evaluate RO/TO", - "selectRoTo": "Select RO/TO", "resetPasswordHere": "You can reset your password here.", "resetPassword": "Reset password", "ebiosRm": "Ebios RM", @@ -996,6 +993,19 @@ "noAuthor": "No author assigned", "noReviewer": "No reviewer assigned", "selectAudit": "Select audit", + "operationalScenario": "Operational scenario", + "operationalScenarioRefId": "Operational scenario {refId}", + "operationalScenarios": "Operational scenarios", + "addOperationalScenario": "Add operational scenario", + "workshopFour": "Workshop 4", + "noThreat": "No threat", + "likely": "Likely", + "unlikely": "Unlikely", + "veryLikely": "Very likely", + "certain": "Certain", + "minor": "Minor", + "operatingModesDescription": "Operating modes description", + "noStakeholders": "No stakeholders", "errorAssetGraphMustNotContainCycles": "The asset graph must not contain cycles.", "addStakeholder": "Add stakeholder" } diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte index 8ca71675b..939955bce 100644 --- a/frontend/src/lib/components/Forms/ModelForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm.svelte @@ -273,7 +273,7 @@ {:else if URLModel === 'attack-paths'} {:else if URLModel === 'operational-scenarios'} - + {/if}
{#if closeModal} diff --git a/frontend/src/lib/components/Forms/ModelForm/OperationalScenarioForm.svelte b/frontend/src/lib/components/Forms/ModelForm/OperationalScenarioForm.svelte index f1eaffd9a..116a1205c 100644 --- a/frontend/src/lib/components/Forms/ModelForm/OperationalScenarioForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm/OperationalScenarioForm.svelte @@ -7,12 +7,27 @@ import { getOptions } from '$lib/utils/crud'; import TextArea from '../TextArea.svelte'; import Select from '../Select.svelte'; + import { page } from '$app/stores'; export let form: SuperValidated; export let model: ModelInfo; export let cacheLocks: Record = {}; export let formDataCache: Record = {}; export let initialData: Record = {}; + export let context: string; + + const activityBackground = context === 'edit' ? 'bg-white' : 'bg-surface-100-800-token'; + + let activeActivity: string | null = null; + $page.url.searchParams.forEach((value, key) => { + if (key === 'activity' && value === 'one') { + activeActivity = 'one'; + } else if (key === 'activity' && value === 'two') { + activeActivity = 'two'; + } else if (key === 'activity' && value === 'three') { + activeActivity = 'three'; + } + });