Skip to content

Commit

Permalink
Merge branch 'CA-708-workshop-4-UX' into CA-695-Automatically-generat…
Browse files Browse the repository at this point in the history
…e-a-scenario-in-the-risk-analysis-for-each-operational-scenario-created
  • Loading branch information
Mohamed-Hacene committed Dec 16, 2024
2 parents f1b00d8 + 1c81f05 commit 8f035e3
Show file tree
Hide file tree
Showing 18 changed files with 546 additions and 77 deletions.
63 changes: 63 additions & 0 deletions backend/ebios_rm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)
from iam.models import FolderMixin, User
from tprm.models import Entity
import json


class EbiosRMStudy(NameDescriptionMixin, ETADueDateMixin, FolderMixin):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -396,6 +404,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(
Expand Down Expand Up @@ -444,16 +456,67 @@ 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 {
"abbreviation": "--",
"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,
}
7 changes: 6 additions & 1 deletion backend/ebios_rm/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,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
Expand Down
18 changes: 15 additions & 3 deletions backend/ebios_rm/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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):
Expand Down
18 changes: 14 additions & 4 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -997,5 +994,18 @@
"noReviewer": "No reviewer assigned",
"selectAudit": "Select audit",
"errorAssetGraphMustNotContainCycles": "The asset graph must not contain cycles.",
"addStakeholder": "Add stakeholder"
"addStakeholder": "Add stakeholder",
"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"
}
2 changes: 1 addition & 1 deletion frontend/src/lib/components/Forms/ModelForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@
{:else if URLModel === 'attack-paths'}
<AttackPathForm {form} {model} {cacheLocks} {formDataCache} {initialData} />
{:else if URLModel === 'operational-scenarios'}
<OperationalScenarioForm {form} {model} {cacheLocks} {formDataCache} {initialData} />
<OperationalScenarioForm {form} {model} {cacheLocks} {formDataCache} {initialData} {context} />
{/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 @@ -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<any>;
export let model: ModelInfo;
export let cacheLocks: Record<string, CacheLock> = {};
export let formDataCache: Record<string, any> = {};
export let initialData: Record<string, any> = {};
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';
}
});
</script>

<AutocompleteSelect
Expand All @@ -24,42 +39,77 @@
label={m.ebiosRmStudy()}
hidden={initialData.ebios_rm_study}
/>
<AutocompleteSelect
{form}
multiple
options={getOptions({
objects: model.foreignKeys['threats'],
extra_fields: [['folder', 'str']],
label: 'auto'
})}
field="threats"
cacheLock={cacheLocks['threats']}
bind:cachedValue={formDataCache['threats']}
label={m.threats()}
/>
<AutocompleteSelect
multiple
{form}
options={getOptions({
objects: model.foreignKeys['attack_paths'],
label: 'str'
})}
field="attack_paths"
label={m.attackPaths()}
/>
<Select
{form}
options={model.selectOptions['likelihood']}
field="likelihood"
label={m.likelihood()}
cacheLock={cacheLocks['likelihood']}
bind:cachedValue={formDataCache['likelihood']}
/>
<Checkbox {form} field="is_selected" label={m.isSelected()} />
<TextArea
{form}
field="justification"
label={m.justification()}
cacheLock={cacheLocks['justification']}
bind:cachedValue={formDataCache['justification']}
/>
<div
class="relative p-2 space-y-2 rounded-md {activeActivity === 'one'
? 'border-2 border-primary-500'
: 'border-2 border-gray-300 border-dashed'}"
>
<p
class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'one'
? 'text-primary-500'
: 'text-gray-500'}"
>
{m.activityOne()}
</p>
<TextArea
{form}
field="operating_modes_description"
label={m.operatingModesDescription()}
cacheLock={cacheLocks['operating_modes_description']}
bind:cachedValue={formDataCache['operating_modes_description']}
data-focusindex="1"
/>
<AutocompleteSelect
{form}
multiple
options={getOptions({
objects: model.foreignKeys['threats'],
extra_fields: [['folder', 'str']],
label: 'auto'
})}
field="threats"
cacheLock={cacheLocks['threats']}
bind:cachedValue={formDataCache['threats']}
label={m.threats()}
/>
{#if context !== 'edit'}
<AutocompleteSelect
{form}
options={getOptions({
objects: model.foreignKeys['attack_path'],
label: 'str'
})}
field="attack_path"
label={m.attackPath()}
/>
{/if}
</div>
<div
class="relative p-2 space-y-2 rounded-md {activeActivity === 'two'
? 'border-2 border-primary-500'
: 'border-2 border-gray-300 border-dashed'}"
>
<p
class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'two'
? 'text-primary-500'
: 'text-gray-500'}"
>
{m.activityTwo()}
</p>
<Select
{form}
options={model.selectOptions['likelihood']}
field="likelihood"
label={m.likelihood()}
cacheLock={cacheLocks['likelihood']}
bind:cachedValue={formDataCache['likelihood']}
/>
<Checkbox {form} field="is_selected" label={m.isSelected()} />
<TextArea
{form}
field="justification"
label={m.justification()}
cacheLock={cacheLocks['justification']}
bind:cachedValue={formDataCache['justification']}
/>
</div>
18 changes: 8 additions & 10 deletions frontend/src/lib/utils/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,14 +682,6 @@ export const URL_MODEL_MAP: ModelMap = {
{ field: 'folder', urlModel: 'folders', urlParams: 'content_type=DO' }
]
},
attack_paths: {
endpointUrl: 'ebios-rm/attack-paths',
name: 'attackpath',
localName: 'attackPath',
localNamePlural: 'attackPaths',
verboseName: 'Attack path',
verboseNamePlural: 'Attack paths'
},
'operational-scenarios': {
endpointUrl: 'ebios-rm/operational-scenarios',
name: 'operationalscenario',
Expand All @@ -700,9 +692,15 @@ export const URL_MODEL_MAP: ModelMap = {
foreignKeyFields: [
{ field: 'ebios_rm_study', urlModel: 'ebios-rm' },
{ field: 'threats', urlModel: 'threats' },
{ field: 'attack_path', urlModel: 'attack-paths' }
{
field: 'attack_path',
urlModel: 'attack-paths',
endpointUrl: 'ebios-rm/attack-paths',
urlParams: 'is_selected=true&used=false&ebios_rm_study=',
detail: true
}
],
selectFields: [{ field: 'likelihood', valueType: 'number' }]
selectFields: [{ field: 'likelihood', valueType: 'number', detail: true }]
}
};

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ export const operationalScenarioSchema = z.object({
ebios_rm_study: z.string(),
attack_path: z.string().uuid(),
threats: z.string().uuid().optional().array().optional(),
description: z.string(),
operating_modes_description: z.string(),
likelihood: z.number().optional().default(-1),
is_selected: z.boolean().optional().default(false),
justification: z.string().optional()
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/utils/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ export const listViewFields: ListViewFieldsConfig = {
body: ['risk_origin', 'target_objective', 'stakeholders', 'description']
},
'operational-scenarios': {
head: ['description', 'threats', 'likelihood'],
body: ['description', 'threats', 'likelihood']
head: ['operatingModesDescription', 'threats', 'likelihood'],
body: ['operating_modes_description', 'threats', 'likelihood']
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ export const load: LayoutServerLoad = async (event) => {
let url = keyModel.endpointUrl
? `${BASE_API_URL}/${keyModel.endpointUrl}/${queryParams}`
: `${BASE_API_URL}/${keyModel.urlModel}/${queryParams}`;
if (keyModel.urlModel === 'assets' && event.params.model === 'feared-events') {
url = `${BASE_API_URL}/${keyModel.urlModel}/${queryParams}${object.ebios_rm_study}`;
if (
['assets', 'attack-paths'].includes(keyModel.urlModel) &&
['feared-events', 'operational-scenarios'].includes(event.params.model)
) {
url = `${BASE_API_URL}/${keyModel.endpointUrl || keyModel.urlModel}/${queryParams}${object.ebios_rm_study}`;
}
const response = await event.fetch(url);
if (response.ok) {
Expand Down
Loading

0 comments on commit 8f035e3

Please sign in to comment.