Skip to content

Commit

Permalink
Add eco radar ebios study (#1261)
Browse files Browse the repository at this point in the history
  • Loading branch information
ab-smith authored Dec 26, 2024
2 parents 590eb51 + e3bdd8c commit 4b7d5d2
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 117 deletions.
87 changes: 87 additions & 0 deletions backend/ebios_rm/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from django.db.models.query import QuerySet
import math


def ecosystem_radar_chart_data(stakeholders_queryset: QuerySet):
qs = stakeholders_queryset

def get_exposure_segment_id(value):
if value < 3:
return 1
if value >= 3 and value < 7:
return 2
if value >= 7 and value <= 9:
return 3
if value > 9:
return 4
return 0

def get_reliability_cluster(value):
if value < 4:
return "clst1"
if value >= 4 and value < 6:
return "clst2"
if value >= 6 and value <= 7:
return "clst3"
if value > 7:
return "clst4"
return 1

"""
// data format: f1-f4 (fiabilité cyber = maturité x confiance ) to get the clusters and colors
// x,y, z
// x: criticité calculée avec cap à 5,5
// y: the angle (output of dict to make sure they end up on the right quadrant, min: 45, max:-45) -> done on BE
// z: the size of item (exposition = dependence x penetration) based on a dict, -> done on BE
// label: name of the 3rd party entity
Angles start at 56,25 (45+45/4) and end at -45-45/4 = 303,75
"""

c_data = {"clst1": [], "clst2": [], "clst3": [], "clst4": []}
r_data = {"clst1": [], "clst2": [], "clst3": [], "clst4": []}
angle_offsets = {"client": 135, "partner": 225, "supplier": 45}

cnt_c_not_displayed = 0
cnt_r_not_displayed = 0
for sh in qs:
# current
c_reliability = sh.current_maturity * sh.current_trust
c_exposure = sh.current_dependency * sh.current_penetration
c_exposure_val = get_exposure_segment_id(c_exposure) * 4

c_criticality = (
math.floor(sh.current_criticality * 100) / 100.0
if sh.current_criticality <= 5
else 5.25
)

angle = angle_offsets[sh.category] + (
get_exposure_segment_id(c_exposure) * (45 / 4)
)

vector = [c_criticality, angle, c_exposure_val, str(sh)]

cluser_id = get_reliability_cluster(c_reliability)
c_data[cluser_id].append(vector)

# residual
r_reliability = sh.residual_maturity * sh.residual_trust
r_exposure = sh.residual_dependency * sh.residual_penetration
r_exposure_val = get_exposure_segment_id(r_exposure) * 4

r_criticality = (
math.floor(sh.residual_criticality * 100) / 100.0
if sh.residual_criticality <= 5
else 5.25
)

angle = angle_offsets[sh.category] + (
get_exposure_segment_id(r_exposure) * (45 / 4)
)

vector = [r_criticality, angle, r_exposure_val, str(sh)]

cluser_id = get_reliability_cluster(r_reliability)
r_data[cluser_id].append(vector)

return {"current": c_data, "residual": r_data}
2 changes: 1 addition & 1 deletion backend/ebios_rm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ def get_scope(self):
return self.__class__.objects.filter(ebios_rm_study=self.ebios_rm_study)

def __str__(self):
return f"{self.entity.name} - {self.get_category_display()}"
return f"{self.entity.name}-{self.get_category_display()}"

def save(self, *args, **kwargs):
self.folder = self.ebios_rm_study.folder
Expand Down
94 changes: 8 additions & 86 deletions backend/ebios_rm/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import django_filters as df
from core.serializers import RiskMatrixReadSerializer
from core.views import BaseModelViewSet as AbstractBaseModelViewSet
from core.serializers import RiskMatrixReadSerializer
from .helpers import ecosystem_radar_chart_data
from .models import (
EbiosRMStudy,
FearedEvent,
Expand All @@ -19,8 +19,6 @@

LONG_CACHE_TTL = 60 # mn

import math


class BaseModelViewSet(AbstractBaseModelViewSet):
serializers_module = "ebios_rm.serializers"
Expand Down Expand Up @@ -86,6 +84,12 @@ def update_workshop_step_status(self, request, pk, workshop, step):
)
return Response(EbiosRMStudyReadSerializer(ebios_rm_study).data)

@action(detail=True, name="Get ecosystem radar chart data")
def ecosystem_chart_data(self, request, pk):
return Response(
ecosystem_radar_chart_data(Stakeholder.objects.filter(ebios_rm_study=pk))
)


class FearedEventViewSet(BaseModelViewSet):
model = FearedEvent
Expand Down Expand Up @@ -161,89 +165,7 @@ def category(self, request):

@action(detail=False, name="Get chart data")
def chart_data(self, request):
def get_exposure_segment_id(value):
if value < 3:
return 1
if value >= 3 and value < 7:
return 2
if value >= 7 and value <= 9:
return 3
if value > 9:
return 4
return 0

def get_reliability_cluster(value):
if value < 4:
return "clst1"
if value >= 4 and value < 6:
return "clst2"
if value >= 6 and value <= 7:
return "clst3"
if value > 7:
return "clst4"
return 1

"""
// data format: f1-f4 (fiabilité cyber = maturité x confiance ) to get the clusters and colors
// x,y, z
// x: criticité calculée avec cap à 5,5
// y: the angle (output of dict to make sure they end up on the right quadrant, min: 45, max:-45) -> done on BE
// z: the size of item (exposition = dependence x penetration) based on a dict, -> done on BE
// label: name of the 3rd party entity
Angles start at 56,25 (45+45/4) and end at -45-45/4 = 303,75
"""

# we can add a filter on the Stakeholder concerned by the ebios study here
qs = Stakeholder.objects.all()

c_data = {"clst1": [], "clst2": [], "clst3": [], "clst4": []}
r_data = {"clst1": [], "clst2": [], "clst3": [], "clst4": []}
angle_offsets = {"client": 135, "partner": 225, "supplier": 45}

cnt_c_not_displayed = 0
cnt_r_not_displayed = 0
for sh in qs:
# current
c_reliability = sh.current_maturity * sh.current_trust
c_exposure = sh.current_dependency * sh.current_penetration
c_exposure_val = get_exposure_segment_id(c_exposure) * 4

c_criticality = (
math.floor(sh.current_criticality * 100) / 100.0
if sh.current_criticality <= 5
else 5.25
)

angle = angle_offsets[sh.category] + (
get_exposure_segment_id(c_exposure) * (45 / 4)
)

vector = [c_criticality, angle, c_exposure_val, str(sh)]

cluser_id = get_reliability_cluster(c_reliability)
c_data[cluser_id].append(vector)

# residual
r_reliability = sh.residual_maturity * sh.residual_trust
r_exposure = sh.residual_dependency * sh.residual_penetration
r_exposure_val = get_exposure_segment_id(r_exposure) * 4

r_criticality = (
math.floor(sh.residual_criticality * 100) / 100.0
if sh.residual_criticality <= 5
else 5.25
)

angle = angle_offsets[sh.category] + (
get_exposure_segment_id(r_exposure) * (45 / 4)
)

vector = [r_criticality, angle, r_exposure_val, str(sh)]

cluser_id = get_reliability_cluster(r_reliability)
r_data[cluser_id].append(vector)

return Response({"current": c_data, "residual": r_data})
return Response(ecosystem_radar_chart_data(Stakeholder.objects.all()))


class StrategicScenarioViewSet(BaseModelViewSet):
Expand Down
8 changes: 7 additions & 1 deletion frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1019,8 +1019,11 @@
"markAsInProgress": "Mark as in progress",
"riskAnalyses": "Risk analyses",
"client": "Client",
"clients": "Clients",
"partner": "Partner",
"partners": "Partners",
"supplier": "Supplier",
"suppliers": "Suppliers",
"referenceEntity": "Reference entity",
"referenceEntitySemiColon": "Reference entity:",
"moderate": "Moderate",
Expand Down Expand Up @@ -1067,5 +1070,8 @@
"operationalScenarioThreatsHelpText": "Unitary actions carried out by a risk source on a critical support asset as part of an operational scenario",
"operationalScenarioIsSelectedHelpText": "Selection of the operational scenario for the study",
"attackPathStakeholdersHelpText": "Stakeholders involved in the attack path",
"attackPathIsSelectedHelpText": "Selection of the attack path for the study"
"attackPathIsSelectedHelpText": "Selection of the attack path for the study",
"ecosystemRadar": "Ecosystem radar",
"criticalitySemiColon": "Criticality:",
"cyberFiability": "Cyber fiability"
}
8 changes: 7 additions & 1 deletion frontend/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1019,8 +1019,11 @@
"markAsInProgress": "Reprendre",
"riskAnalyses": "Analyses de risques",
"client": "Client",
"clients": "Clients",
"partner": "Partenaire",
"partners": "Partenaires",
"supplier": "Fournisseur",
"suppliers": "Fournisseurs",
"referenceEntity": "Entité de référence",
"referenceEntitySemiColon": "Entité de référence :",
"moderate": "Modérée",
Expand Down Expand Up @@ -1067,5 +1070,8 @@
"operationalScenarioThreatsHelpText": "Actions élémentaires réalisées par une source de risque sur un bien support critique dans le cadre d'un scénario opérationnel",
"operationalScenarioIsSelectedHelpText": "Sélection du scénario opérationnel pour l'étude",
"attackPathStakeholdersHelpText": "Parties prenantes impactées par le chemin d'attaque",
"attackPathIsSelectedHelpText": "Sélection du chemin d'attaque pour l'étude"
"attackPathIsSelectedHelpText": "Sélection du chemin d'attaque pour l'étude",
"ecosystemRadar": "Radar de l'écosystème",
"criticalitySemiColon": "Criticité :",
"cyberFiability": "Fiabilité cyber"
}
28 changes: 22 additions & 6 deletions frontend/src/lib/components/Chart/EcosystemRadarChart.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { safeTranslate } from '$lib/utils/i18n';
import { symbol } from 'zod';
import { grid } from '@unovis/ts/components/axis/style';
import * as m from '$paraglide/messages.js';
// export let name: string;
Expand Down Expand Up @@ -33,12 +32,23 @@
return [
// Existing text elements
{
type: 'text',
left: 'center',
top: 40,
style: {
text: m.cyberFiability(),
font: 'bold 16px Arial',
fill: '#333',
textAlign: 'center'
}
},
{
type: 'text',
position: [chartWidth / 4, (3 * chartHeight) / 4],
silent: true,
style: {
text: 'Prestataires',
text: m.suppliers(),
font: '18px Arial',
fill: '#666',
textAlign: 'center',
Expand All @@ -50,7 +60,7 @@
position: [(3 * chartWidth) / 4, chartHeight / 4],
silent: true,
style: {
text: 'Partenaires',
text: m.partners(),
font: '18px Arial',
fill: '#666',
textAlign: 'center',
Expand All @@ -62,7 +72,7 @@
position: [chartWidth / 4, chartHeight / 4],
silent: true,
style: {
text: 'Clients',
text: m.clients(),
font: '18px Arial',
fill: '#666',
textAlign: 'center',
Expand All @@ -84,7 +94,13 @@
polar: {},
tooltip: {
formatter: function (params) {
return params.value[3] + '<br/>Criticality: ' + params.value[0];
return (
params.value[3].split('-')[0] +
' - ' +
safeTranslate(params.value[3].split('-')[1]) +
`<br/>${m.criticalitySemiColon()} ` +
params.value[0]
);
}
},
angleAxis: {
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 @@ -230,7 +230,7 @@ const ENTITY_FILTER: ListViewFilterConfig = {

const CURRENT_CRITICALITY_FILTER: ListViewFilterConfig = {
component: SelectFilter,
getColumn: (row) => (console.log(row), row.current_criticality.toString()),
getColumn: (row) => row.current_criticality.toString(),
extraProps: {
defaultOptionName: 'current_criticality'
},
Expand All @@ -239,7 +239,7 @@ const CURRENT_CRITICALITY_FILTER: ListViewFilterConfig = {

const RESIDUAL_CRITICALITY_FILTER: ListViewFilterConfig = {
component: SelectFilter,
getColumn: (row) => (console.log(row), row.residual_criticality.toString()),
getColumn: (row) => row.residual_criticality.toString(),
extraProps: {
defaultOptionName: 'residual_criticality'
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ export const load: PageServerLoad = async ({ params, fetch }) => {
meta: data // metaData
};

return { createForm, deleteForm, model, URLModel, table };
const radarEndpoint = `${BASE_API_URL}/ebios-rm/studies/${params.id}/ecosystem_chart_data/`;

const radarRes = await fetch(radarEndpoint);
const radar = await radarRes.json();

return { createForm, deleteForm, model, URLModel, table, radar };
};

export const actions: Actions = {
Expand Down
Loading

0 comments on commit 4b7d5d2

Please sign in to comment.