diff --git a/backend/core/migrations/0003_alter_riskscenario_strength_of_knowledge.py b/backend/core/migrations/0003_alter_riskscenario_strength_of_knowledge.py new file mode 100644 index 000000000..011b043b6 --- /dev/null +++ b/backend/core/migrations/0003_alter_riskscenario_strength_of_knowledge.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-02-26 14:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='riskscenario', + name='strength_of_knowledge', + field=models.IntegerField(default=-1, help_text='The strength of the knowledge supporting the assessment', verbose_name='Strength of Knowledge'), + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index 5069db3c7..a44996839 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -998,12 +998,35 @@ class RiskScenario(NameDescriptionMixin): ("transfer", _("Transfer")), ] - SOK_OPTIONS = [ - ("--", _("--")), - ("0", _("Low")), - ("1", _("Medium")), - ("2", _("High")), - ] + DEFAULT_SOK_OPTIONS = { + -1: { + "name": _("--"), + "description": _( + "The strength of the knowledge supporting the assessment is undefined" + ), + }, + 0: { + "name": _("Low"), + "description": _( + "The strength of the knowledge supporting the assessment is low" + ), + "symbol": "◇", + }, + 1: { + "name": _("Medium"), + "description": _( + "The strength of the knowledge supporting the assessment is medium" + ), + "symbol": "⬙", + }, + 2: { + "name": _("High"), + "description": _( + "The strength of the knowledge supporting the assessment is high" + ), + "symbol": "◆", + }, + } risk_assessment = models.ForeignKey( RiskAssessment, @@ -1076,11 +1099,10 @@ class RiskScenario(NameDescriptionMixin): verbose_name=_("Treatment status"), ) - strength_of_knowledge = models.CharField( - max_length=20, - choices=SOK_OPTIONS, - default="--", + strength_of_knowledge = models.IntegerField( + default=-1, verbose_name=_("Strength of Knowledge"), + help_text=_("The strength of the knowledge supporting the assessment"), ) justification = models.CharField( max_length=500, blank=True, null=True, verbose_name=_("Justification") @@ -1148,6 +1170,11 @@ def get_residual_proba(self): risk_matrix = self.get_matrix() return risk_matrix["probability"][self.residual_proba] + def get_strength_of_knowledge(self): + if self.strength_of_knowledge < 0: + return self.DEFAULT_SOK_OPTIONS[-1] + return self.DEFAULT_SOK_OPTIONS[self.strength_of_knowledge] + def __str__(self): return ( str(self.parent_project().folder) diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 3981dcc13..c107983c7 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -198,7 +198,10 @@ class RiskScenarioReadSerializer(RiskScenarioWriteSerializer): residual_impact = serializers.CharField(source="get_residual_impact.name") residual_level = serializers.JSONField(source="get_residual_risk") + strength_of_knowledge = serializers.JSONField(source="get_strength_of_knowledge") + security_measures = FieldsRelatedField(many=True) + rid = serializers.CharField() class SecurityMeasureWriteSerializer(BaseModelSerializer): diff --git a/backend/core/views.py b/backend/core/views.py index 968b97076..34f6eb424 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -681,6 +681,29 @@ def impact(self, request, pk): choices = undefined | _choices return Response(choices) + @action(detail=True, name="Get strength of knowledge choices") + def strength_of_knowledge(self, request, pk): + undefined = {-1: RiskScenario.DEFAULT_SOK_OPTIONS[-1]} + _sok_choices = self.get_object().get_matrix().get("strength_of_knowledge") + if _sok_choices is not None: + sok_choices = dict( + zip( + list(range(0, 64)), + [ + { + "name": x["name"], + "description": x.get("description"), + "symbol": x.get("symbol"), + } + for x in _sok_choices + ], + ) + ) + else: + sok_choices = RiskScenario.DEFAULT_SOK_OPTIONS + choices = undefined | sok_choices + return Response(choices) + @action(detail=False, name="Get risk count per level") def count_per_level(self, request): return Response({"results": risks_count_per_level(request.user)}) diff --git a/backend/library/libraries/critical_matrix_3x3.yaml b/backend/library/libraries/critical_matrix_3x3.yaml index 33cc3d15c..ba72f3327 100644 --- a/backend/library/libraries/critical_matrix_3x3.yaml +++ b/backend/library/libraries/critical_matrix_3x3.yaml @@ -8,50 +8,50 @@ provider: intuitem packager: intuitem objects: risk_matrix: - - urn: urn:intuitem:risk:matrix:critical_risk_matrix_3x3 - ref_id: critical_3x3 - name: critical 3x3 - description: critical risk matrix 3x3 - probability: - - abbreviation: L - name: Low - description: Unfrequent event - - abbreviation: M - name: Medium - description: Occasional event - - abbreviation: H - name: High - description: Frequent event - impact: - - abbreviation: L - name: Low - description: Low impact - - abbreviation: M - name: Medium - description: Medium impact - - abbreviation: H - name: High - description: High impact - risk: - - abbreviation: L - name: Low - description: acceptable risk - hexcolor: "#00FF00" - - abbreviation: M - name: Medium - description: risk requiring mitigation within 2 years - hexcolor: "#FFFF00" - - abbreviation: H - name: High - description: unacceptable risk - hexcolor: "#FF0000" - grid: - - - 0 - - 1 - - 1 - - - 1 - - 1 - - 2 - - - 1 - - 2 - - 2 + - urn: urn:intuitem:risk:matrix:critical_risk_matrix_3x3 + ref_id: critical_3x3 + name: critical 3x3 + description: critical risk matrix 3x3 + probability: + - abbreviation: L + name: Low + description: Unfrequent event + - abbreviation: M + name: Medium + description: Occasional event + - abbreviation: H + name: High + description: Frequent event + impact: + - abbreviation: L + name: Low + description: Low impact + - abbreviation: M + name: Medium + description: Medium impact + - abbreviation: H + name: High + description: High impact + risk: + - abbreviation: L + name: Low + description: acceptable risk + hexcolor: "#00FF00" + - abbreviation: M + name: Medium + description: risk requiring mitigation within 2 years + hexcolor: "#FFFF00" + - abbreviation: H + name: High + description: unacceptable risk + hexcolor: "#FF0000" + grid: + - - 0 + - 1 + - 1 + - - 1 + - 1 + - 2 + - - 1 + - 2 + - 2 diff --git a/backend/library/libraries/critical_matrix_5x5.yaml b/backend/library/libraries/critical_matrix_5x5.yaml index c44d2d997..89c3c7f10 100644 --- a/backend/library/libraries/critical_matrix_5x5.yaml +++ b/backend/library/libraries/critical_matrix_5x5.yaml @@ -8,86 +8,86 @@ provider: intuitem packager: intuitem objects: risk_matrix: - - urn: urn:intuitem:risk:matrix:critical_risk_matrix_5x5 - ref_id: critical_5x5 - name: critical 5x5 - description: critical risk matrix 5x5 - probability: - - abbreviation: VL - name: Very Low - description: Very unfrequent event - - abbreviation: L - name: Low - description: Unfrequent event - - abbreviation: M - name: Medium - description: Occasional event - - abbreviation: H - name: High - description: Frequent event - - abbreviation: VH - name: Very High - description: Very frequent event - impact: - - abbreviation: VL - name: Very Low - description: Very low impact - - abbreviation: L - name: Low - description: Low impact - - abbreviation: M - name: Medium - description: Medium impact - - abbreviation: H - name: High - description: High impact - - abbreviation: VH - name: Very High - description: Very high impact - risk: - - abbreviation: VL - name: Very Low - description: negligible risk - hexcolor: "#BBF7D0" - - abbreviation: L - name: Low - description: acceptable risk - hexcolor: "#BEF264" - - abbreviation: M - name: Medium - description: risk requiring mitigation within 2 years - hexcolor: "#FEF08A" - - abbreviation: H - name: High - description: risk requiring mitigation within 6 months - hexcolor: "#FBBF24" - - abbreviation: VH - name: Very High - description: unacceptable risk - hexcolor: "#F87171" - grid: - - - 0 - - 0 - - 1 - - 1 - - 2 - - - 0 - - 1 - - 1 - - 2 - - 2 - - - 1 - - 1 - - 2 - - 2 - - 3 - - - 1 - - 2 - - 2 - - 3 - - 4 - - - 2 - - 2 - - 3 - - 4 - - 4 + - urn: urn:intuitem:risk:matrix:critical_risk_matrix_5x5 + ref_id: critical_5x5 + name: critical 5x5 + description: critical risk matrix 5x5 + probability: + - abbreviation: VL + name: Very Low + description: Very unfrequent event + - abbreviation: L + name: Low + description: Unfrequent event + - abbreviation: M + name: Medium + description: Occasional event + - abbreviation: H + name: High + description: Frequent event + - abbreviation: VH + name: Very High + description: Very frequent event + impact: + - abbreviation: VL + name: Very Low + description: Very low impact + - abbreviation: L + name: Low + description: Low impact + - abbreviation: M + name: Medium + description: Medium impact + - abbreviation: H + name: High + description: High impact + - abbreviation: VH + name: Very High + description: Very high impact + risk: + - abbreviation: VL + name: Very Low + description: negligible risk + hexcolor: "#BBF7D0" + - abbreviation: L + name: Low + description: acceptable risk + hexcolor: "#BEF264" + - abbreviation: M + name: Medium + description: risk requiring mitigation within 2 years + hexcolor: "#FEF08A" + - abbreviation: H + name: High + description: risk requiring mitigation within 6 months + hexcolor: "#FBBF24" + - abbreviation: VH + name: Very High + description: unacceptable risk + hexcolor: "#F87171" + grid: + - - 0 + - 0 + - 1 + - 1 + - 2 + - - 0 + - 1 + - 1 + - 2 + - 2 + - - 1 + - 1 + - 2 + - 2 + - 3 + - - 1 + - 2 + - 2 + - 3 + - 4 + - - 2 + - 2 + - 3 + - 4 + - 4 diff --git a/backend/library/utils.py b/backend/library/utils.py index be7603a03..a02cd557e 100644 --- a/backend/library/utils.py +++ b/backend/library/utils.py @@ -287,7 +287,7 @@ def import_security_function(self, library_object: Library): # The couple (URN, locale) is unique. ===> Check this in the future class RiskMatrixImporter: REQUIRED_FIELDS = {"ref_id", "urn", "json_definition"} - MATRIX_FIELDS = {"probability", "impact", "risk", "grid"} + MATRIX_FIELDS = {"probability", "impact", "risk", "grid", "strength_of_knowledge"} def __init__(self, risk_matrix_data): self.risk_matrix_data = risk_matrix_data diff --git a/frontend/messages/en.json b/frontend/messages/en.json index e5ec8a27e..0be0465df 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -230,12 +230,16 @@ "draft": "Draft", "riskMatrixView": "Risk matrix view", "currentInMatrixView": "Current", - "residualInMatrixView": "Residual", "probability": "Probability", "riskLevels": "Risk levels", "riskLevel": "Risk level", "cancel": "Cancel", "save": "Save", + "assetsImpactedByTheRiskScenario": "Assets impacted by the risk scenario", + "ecistingMeasures": "Existing measures", + "theExistingSecurityMeasuresToManageThisRisk": "The existing security measures to manage this risk", + "currentRiskLevelGivenCurrentMeasures": "The current risk level given the current security measures", + "riskLevelWhenAllExtraMeasuresDone": "The risk level when all extra measures are done", "myUserGroups": "My user groups", "changePasswordText": "You can change your password here. You'll need to log in with your new password after this operation", "oldPassword": "Old password", @@ -340,6 +344,7 @@ "copyright": "Copyright", "uploadYourLibrary": "Upload your own library", "libraryFileInYaml": "Library file in YAML format", + "rid": "RID", "scope": "Scope", "auditor": "Auditor", "lastUpdate": "Last update", diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 90bfcf18b..13778fd5c 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -228,14 +228,21 @@ "asPDF": "en PDF", "asCSV": "en CSV", "draft": "Brouillon", - "riskMatrixView": "Vue de la matrice de risque", + "riskMatrixView": "Vue matricielle", "currentInMatrixView": "Courante", - "residualInMatrixView": "Residuelle", "probability": "Probabilité", "riskLevels": "Niveaux de risque", "riskLevel": "Niveau de risque", "cancel": "Annuler", "save": "Enregistrer", + "currentAssessment": "Évaluation actuelle", + "scope": "Périmètre", + "assetsImpactedByTheRiskScenario": "Biens sensibles impactés par le scénario de risque", + "ecistingMeasures": "Mesures existantes", + "theExistingSecurityMeasuresToManageThisRisk": "Les mesures de sécurité existantes pour gérer ce risque", + "currentRiskLevelGivenCurrentMeasures": "Niveau de risque courant compte tenu des mesures actuelles", + "targetAssessment": "Évaluation cible", + "riskLevelWhenAllExtraMeasuresDone": "Niveau de risque lorsque toutes les mesures supplémentaires sont appliquées", "myUserGroups": "Mes groupes d'utilisateurs", "changePasswordText": "Vous pouvez changer votre mot de passe ici. Vous devrez vous reconnecter avec le nouveau mot de passe après cette opération", "oldPassword": "Ancien mot de passe", diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte index a8440f953..73eb6fa2a 100644 --- a/frontend/src/lib/components/ModelTable/ModelTable.svelte +++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte @@ -229,7 +229,7 @@ {value.str ?? '-'} {/if} {:else if value && value.hexcolor} -
{value.name ?? value.str ?? '-'}
+{value.name ?? value.str ?? '-'}
{:else} {value ?? '-'} {/if} diff --git a/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts b/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts index 532fdc4d4..cfccd5418 100644 --- a/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts +++ b/frontend/src/lib/components/RiskMatrix/RiskMatrix.stories.ts @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/svelte'; import RiskMatrix from './RiskMatrix.svelte'; +import RiskScenarioItem from './RiskScenarioItem.svelte'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories const meta = { @@ -45,12 +46,61 @@ const riskMatrix = { }) }; +const riskScenarios = [ + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS1', + rid: 'R.1', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '◇' + } + }, + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS1', + rid: 'R.2', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '⬙' + } + }, + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS1', + rid: 'R.3', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '◆' + } + }, + { + id: '7059ef1b-7d4f-46bc-b735-ed41b531bb22', + name: 'RS4', + rid: 'R.1', + strength_of_knowledge: { + name: 'Low', + description: 'The strength of the knowledge supporting the assessment is very high', + symbol: '◆' + } + } +}; + const sampleData = [ [['R.1'], ['R.5'], ['R.8']], [['R.7'], [], ['R.9', 'R.6']], [['R.2'], ['R.3'], ['R.4']] ]; +const sampleDataItems = [ + [[riskScenario], [], []], + [[], [], [riskScenario, riskScenario]], + [[], [riskScenario], []] +]; + export default meta; type Story = StoryObj+ {#if data.strength_of_knowledge && data.strength_of_knowledge.symbol !== undefined}{data.strength_of_knowledge.symbol}{/if}{data.rid} +
diff --git a/frontend/src/lib/utils/locales.ts b/frontend/src/lib/utils/locales.ts index 35fc9d758..30bfa75a3 100644 --- a/frontend/src/lib/utils/locales.ts +++ b/frontend/src/lib/utils/locales.ts @@ -12,7 +12,7 @@ export const LOCALE_MAP = { }; export function toCamelCase(str: string) { - return str.replace(/[_-]\w/g, match => match.charAt(1).toUpperCase()); + return str.replace(/[_-]\w/g, (match) => match.charAt(1).toUpperCase()); } export function capitalizeFirstLetter(str: string) { @@ -126,6 +126,7 @@ export function localItems(languageTag: string): LocalItems { dateJoined: m.dateJoined({ languageTag: languageTag }), version: m.version({ languageTag: languageTag }), treatment: m.treatment({ languageTag: languageTag }), + rid: m.rid({ languageTag: languageTag }), currentProba: m.currentProba({ languageTag: languageTag }), currentImpact: m.currentImpact({ languageTag: languageTag }), residualProba: m.residualProba({ languageTag: languageTag }), @@ -163,7 +164,9 @@ export function localItems(languageTag: string): LocalItems { associatedRiskAssessments: m.associatedRiskAssessments({ languageTag: languageTag }), associatedRiskScenarios: m.associatedRiskScenarios({ languageTag: languageTag }), associatedRiskAcceptances: m.associatedRiskAcceptances({ languageTag: languageTag }), - associatedComplianceAssessments: m.associatedComplianceAssessments({ languageTag: languageTag }), + associatedComplianceAssessments: m.associatedComplianceAssessments({ + languageTag: languageTag + }), associatedEvidences: m.associatedEvidences({ languageTag: languageTag }), associatedDomains: m.associatedDomains({ languageTag: languageTag }), associatedProjects: m.associatedProjects({ languageTag: languageTag }), diff --git a/frontend/src/lib/utils/schemas.ts b/frontend/src/lib/utils/schemas.ts index 7a6125ece..9e3d5c20f 100644 --- a/frontend/src/lib/utils/schemas.ts +++ b/frontend/src/lib/utils/schemas.ts @@ -80,6 +80,7 @@ export const RiskScenarioSchema = baseNamedObject({ residual_proba: z.number().optional(), residual_impact: z.number().optional(), treatment: z.string().optional(), + strength_of_knowledge: z.number().default(-1).optional(), justification: z.string().optional().nullable(), risk_assessment: z.string(), threats: z.string().uuid().optional().array(), diff --git a/frontend/src/lib/utils/types.ts b/frontend/src/lib/utils/types.ts index b351301cb..6a3536d21 100644 --- a/frontend/src/lib/utils/types.ts +++ b/frontend/src/lib/utils/types.ts @@ -97,6 +97,12 @@ export interface RiskLevel { residual: string[]; } +export interface StrengthOfKnowledgeEntry { + name: string; + description: string; + symbol: string; +} + export interface AggregatedData { names: string[]; } diff --git a/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte index 3dd9acd6f..67f7e67f3 100644 --- a/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/[model=urlmodel]/[id=uuid]/+page.svelte @@ -62,7 +62,7 @@ type: 'component', component: modalComponent, // Data - title: localItems(languageTag())['add' + capitalizeFirstLetter(data.model.localName)] + title: localItems(languageTag())['add' + capitalizeFirstLetter(model.info.localName)] }; modalStore.trigger(modal); } @@ -256,12 +256,16 @@ {#if tabSet === index}