From e9960efb91609c9e0dd3aa18ef1a04477fcc222d Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Mon, 13 May 2024 19:47:01 +0200 Subject: [PATCH] Add Threats Radar --- backend/core/helpers.py | 59 ++++++++++++++----- backend/core/views.py | 4 ++ frontend/messages/en.json | 4 +- frontend/messages/fr.json | 5 +- frontend/messages/pt.json | 4 +- .../lib/components/Chart/RadarChart.svelte | 20 +++---- .../routes/(app)/analytics/+page.server.ts | 3 + .../src/routes/(app)/analytics/+page.svelte | 13 +++- 8 files changed, 79 insertions(+), 33 deletions(-) diff --git a/backend/core/helpers.py b/backend/core/helpers.py index c71476bb1..c6dbdd896 100644 --- a/backend/core/helpers.py +++ b/backend/core/helpers.py @@ -255,22 +255,24 @@ def get_sorted_requirement_nodes_rec( "parent_urn": node.parent_urn, "ref_id": node.ref_id, "name": node.name, - "implementation_groups": node.implementation_groups - if node.implementation_groups - else None, + "implementation_groups": ( + node.implementation_groups if node.implementation_groups else None + ), "ra_id": str(req_as.id) if requirements_assessed else None, "status": req_as.status if requirements_assessed else None, "is_scored": req_as.is_scored if requirements_assessed else None, "score": req_as.score if requirements_assessed else None, - "max_score": req_as.compliance_assessment.framework.max_score - if requirements_assessed - else None, - "status_display": req_as.get_status_display() - if requirements_assessed - else None, - "status_i18n": camel_case(req_as.status) - if requirements_assessed - else None, + "max_score": ( + req_as.compliance_assessment.framework.max_score + if requirements_assessed + else None + ), + "status_display": ( + req_as.get_status_display() if requirements_assessed else None + ), + "status_i18n": ( + camel_case(req_as.status) if requirements_assessed else None + ), "node_content": node.display_long, "style": "node", "assessable": node.assessable, @@ -293,9 +295,11 @@ def get_sorted_requirement_nodes_rec( { "urn": req.urn, "ref_id": req.ref_id, - "implementation_groups": req.implementation_groups - if req.implementation_groups - else None, + "implementation_groups": ( + req.implementation_groups + if req.implementation_groups + else None + ), "name": req.name, "description": req.description, "ra_id": str(req_as.id), @@ -950,3 +954,28 @@ def compile_risk_assessment_for_composer(user, risk_assessment_list: list): }, "colors": get_risk_color_ordered_list(user, risk_assessment_list), } + + +def threats_count_per_name(user: User): + labels = list() + values = list() + ( + object_ids_view, + _, + _, + ) = RoleAssignment.get_accessible_object_ids(Folder.get_root_folder(), user, Threat) + + # expected by echarts to send the threats names in labels and the count of each threat in values + + for threat in Threat.objects.filter(id__in=object_ids_view).order_by("name"): + val = RiskScenario.objects.filter(threats=threat).count() + if val > 0: + labels.append({"name": threat.name}) + values.append(val) + max_offset = max(values, default=0) # we can add x later on to improve visibility + + # update each label to include the max_offset + for label in labels: + label["max"] = max_offset + + return {"labels": labels, "values": values} diff --git a/backend/core/views.py b/backend/core/views.py index e66b2b14d..bc70c8562 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -236,6 +236,10 @@ class ThreatViewSet(BaseModelViewSet): filterset_fields = ["folder", "risk_scenarios"] search_fields = ["name", "provider", "description"] + @action(detail=False, name="Get threats count") + def threats_count(self, request): + return Response({"results": threats_count_per_name(request.user)}) + class AssetViewSet(BaseModelViewSet): """ diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 27a31c89d..abb19b667 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -513,5 +513,7 @@ "userWillBeDisconnected": "The user will be disconnected and will need to log in again", "scoresDefinition": "Scores definition", "selectedImplementationGroups": "Selected implementation groups", - "implementationGroupsDefinition": "Implementation groups definition" + "implementationGroupsDefinition": "Implementation groups definition", + "threatRadarChart": "Threat radar", + "noThreatsMapped": "No threats mapped. Consider attaching threats to your risk scenarios for a better overview." } diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 3a51e832e..9873efef7 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -513,5 +513,8 @@ "userWillBeDisconnected": "L'utilisateur sera déconnecté et devra se reconnecter", "scoresDefinition": "Définition des scores", "selectedImplementationGroups": "Groupes d'implémentation sélectionnés", - "implementationGroupsDefinition": "Définition des groupes d'implémentation" + "implementationGroupsDefinition": "Définition des groupes d'implémentation", + "threatRadarChart": "Radar des menaces", + "noThreatsMapped": "Aucune menace n'a été attachée. Pensez à lier les menaces à vos scénarios de risque pour une meilleure visibilité." + } diff --git a/frontend/messages/pt.json b/frontend/messages/pt.json index 2686372fc..d55fc2e41 100644 --- a/frontend/messages/pt.json +++ b/frontend/messages/pt.json @@ -513,5 +513,7 @@ "userWillBeDisconnected": "O usuário será desconectado e precisará fazer login novamente", "scoresDefinition": "Definição de pontuações", "selectedImplementationGroups": "Grupos de implementação selecionados", - "implementationGroupsDefinition": "Definição de grupos de implementação" + "implementationGroupsDefinition": "Definição de grupos de implementação", + "threatRadarChart": "Radar de ameaças", + "noThreatsMapped": "Nenhuma ameaça mapeada. Considere anexar ameaças aos seus cenários de risco para ter uma visão geral melhor." } diff --git a/frontend/src/lib/components/Chart/RadarChart.svelte b/frontend/src/lib/components/Chart/RadarChart.svelte index 126341236..b0e9bd577 100644 --- a/frontend/src/lib/components/Chart/RadarChart.svelte +++ b/frontend/src/lib/components/Chart/RadarChart.svelte @@ -12,7 +12,8 @@ export let title = ''; export let values: any[]; // Set the types for these variables later on - export let colors: string[] = []; + export let labels: any[]; + for (const index in values) { if (values[index].localName) { @@ -42,24 +43,17 @@ data: ['Allocated Budget', 'Actual Spending'] }, radar: { - //shape: 'circle', - indicator: [ - { name: 'Rogue Admin', }, - { name: 'Unavailability', }, - { name: 'Regulation', }, - { name: 'Data Breach', }, - { name: 'Phishing', }, - { name: 'Ransomware', } - ] + shape: 'circle', + indicator: labels }, series: [ { - name: 'Budget vs spending', + name: s_label, type: 'radar', data: [ { - value: [500, 600, 700, 800, 900, 1000], - name: 'Current' + value: values, + name: 'Radar' }, ] } diff --git a/frontend/src/routes/(app)/analytics/+page.server.ts b/frontend/src/routes/(app)/analytics/+page.server.ts index 66b62b2c4..b0c7339e5 100644 --- a/frontend/src/routes/(app)/analytics/+page.server.ts +++ b/frontend/src/routes/(app)/analytics/+page.server.ts @@ -71,6 +71,8 @@ export const load: PageServerLoad = async ({ locals, fetch }) => { residual: Record[]; } = await req_get_risks_count_per_level.json().then((res) => res.results); + const threats_count = await fetch(`${BASE_API_URL}/threats/threats_count/`).then((res) => res.json()); + const req_get_measures_to_review = await fetch(`${BASE_API_URL}/applied-controls/to_review/`); const measures_to_review = await req_get_measures_to_review.json(); @@ -194,6 +196,7 @@ export const load: PageServerLoad = async ({ locals, fetch }) => { complianceAssessmentsPerStatus, riskScenariosPerStatus, risks_count_per_level, + threats_count, measures_to_review: measures_to_review.results, acceptances_to_review: acceptances_to_review.results, risk_assessments: risk_assessments.results, diff --git a/frontend/src/routes/(app)/analytics/+page.svelte b/frontend/src/routes/(app)/analytics/+page.svelte index 8ddf8a50e..8c7081966 100644 --- a/frontend/src/routes/(app)/analytics/+page.svelte +++ b/frontend/src/routes/(app)/analytics/+page.svelte @@ -245,13 +245,22 @@ {:else if tabSet === 1} +
+ {#if (data.threats_count.results.labels.length > 0)}
+ {:else} +
+

{m.noThreatsMapped()}

+
+ {/if}
{m.currentRiskLevelPerScenario()}