Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add threat radar to risk analytics #423

Merged
merged 3 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 44 additions & 15 deletions backend/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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}
4 changes: 4 additions & 0 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
4 changes: 3 additions & 1 deletion frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
4 changes: 3 additions & 1 deletion frontend/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,5 +513,7 @@
"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é."
}
4 changes: 3 additions & 1 deletion frontend/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
73 changes: 73 additions & 0 deletions frontend/src/lib/components/Chart/RadarChart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script lang="ts">
import { onMount } from 'svelte';
import { localItems } from '$lib/utils/locales';
import { languageTag } from '$paraglide/runtime';

// export let name: string;
export let s_label = '';

export let width = 'w-auto';
export let height = 'h-full';
export let classesContainer = '';
export let title = '';

export let values: any[]; // Set the types for these variables later on
export let labels: any[];

for (const index in values) {
if (values[index].localName) {
values[index].name = localItems(languageTag())[values[index].localName];
}
}

let chart_element: HTMLElement | null = null;
onMount(async () => {
const echarts = await import('echarts');
let chart = echarts.init(chart_element, null, { renderer: 'svg' });

// specify chart configuration item and data
let option = {
title: {
text: title,
textStyle: {
fontWeight: 'bold',
fontSize: 14
}
// show: false
},
tooltip: {
trigger: 'item'
},
legend: {
data: ['Allocated Budget', 'Actual Spending']
},
radar: {
shape: 'circle',
indicator: labels
},
series: [
{
name: s_label,
type: 'radar',
data: [
{
value: values,
name: 'Radar'
}
]
}
]
};

// console.debug(option);

// use configuration item and data specified to show chart
chart.setOption(option);

window.addEventListener('resize', function () {
chart.resize();
});
});
</script>

<div class="{width} {height} {classesContainer}" bind:this={chart_element} />
5 changes: 5 additions & 0 deletions frontend/src/routes/(app)/analytics/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
residual: Record<string, any>[];
} = 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();

Expand Down Expand Up @@ -194,6 +198,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,
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/routes/(app)/analytics/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import DonutChart from '$lib/components/Chart/DonutChart.svelte';
import RadarChart from '$lib/components/Chart/RadarChart.svelte';

import { goto } from '$app/navigation';
import { page } from '$app/stores';
Expand Down Expand Up @@ -243,7 +244,23 @@
</div>
</section>
{:else if tabSet === 1}
<!-- Risk tab -->

<section>
{#if data.threats_count.results.labels.length > 0}
<div class=" h-96 my-2">
<RadarChart
name="threatRadar"
title={m.threatRadarChart()}
labels={data.threats_count.results.labels}
values={data.threats_count.results.values}
/>
</div>
{:else}
<div class="py-4 flex items-center justify-center">
<p class="">{m.noThreatsMapped()}</p>
</div>
{/if}
<div class="flex">
<div class="h-96 flex-1">
<span class="text-sm font-semibold">{m.currentRiskLevelPerScenario()}</span>
Expand Down
Loading