From 29b290264f018a27a7cda198ab9941499587dc55 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 11:54:13 +0100 Subject: [PATCH 01/34] Initialize ebios_rm app --- backend/ebios_rm/__init__.py | 0 backend/ebios_rm/apps.py | 6 ++++ backend/ebios_rm/migrations/__init__.py | 0 backend/ebios_rm/models.py | 44 +++++++++++++++++++++++++ backend/ebios_rm/views.py | 3 ++ 5 files changed, 53 insertions(+) create mode 100644 backend/ebios_rm/__init__.py create mode 100644 backend/ebios_rm/apps.py create mode 100644 backend/ebios_rm/migrations/__init__.py create mode 100644 backend/ebios_rm/models.py create mode 100644 backend/ebios_rm/views.py diff --git a/backend/ebios_rm/__init__.py b/backend/ebios_rm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/ebios_rm/apps.py b/backend/ebios_rm/apps.py new file mode 100644 index 000000000..818aaacc5 --- /dev/null +++ b/backend/ebios_rm/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EbiosRmConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ebios_rm' diff --git a/backend/ebios_rm/migrations/__init__.py b/backend/ebios_rm/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py new file mode 100644 index 000000000..5cab5f8f5 --- /dev/null +++ b/backend/ebios_rm/models.py @@ -0,0 +1,44 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from core.base_models import NameDescriptionMixin, ETADueDateMixin +from iam.models import FolderMixin, User + + +class EbiosRMStudy(NameDescriptionMixin, ETADueDateMixin, FolderMixin): + class Status(models.TextChoices): + PLANNED = "planned", _("Planned") + IN_PROGRESS = "in_progress", _("In progress") + IN_REVIEW = "in_review", _("In review") + DONE = "done", _("Done") + DEPRECATED = "deprecated", _("Deprecated") + + ref_id = models.CharField(max_length=100, unique=True) + version = models.CharField( + max_length=100, + blank=True, + null=True, + help_text=_("Version of the Ebios RM study (eg. 1.0, 2.0, etc.)"), + verbose_name=_("Version"), + default="1.0", + ) + status = models.CharField( + max_length=100, + choices=Status.choices, + default=Status.PLANNED, + verbose_name=_("Status"), + blank=True, + null=True, + ) + authors = models.ManyToManyField( + User, + blank=True, + verbose_name=_("Authors"), + related_name="%(class)s_authors", + ) + reviewers = models.ManyToManyField( + User, + blank=True, + verbose_name=_("Reviewers"), + related_name="%(class)s_reviewers", + ) + observation = models.TextField(null=True, blank=True, verbose_name=_("Observation")) diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py new file mode 100644 index 000000000..91ea44a21 --- /dev/null +++ b/backend/ebios_rm/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 0d815e09ba0f41543768e23dbcb373e103be2aa8 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 11:56:09 +0100 Subject: [PATCH 02/34] Write EbiosRMStudy model --- backend/ebios_rm/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 5cab5f8f5..d93827ad4 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -42,3 +42,8 @@ class Status(models.TextChoices): related_name="%(class)s_reviewers", ) observation = models.TextField(null=True, blank=True, verbose_name=_("Observation")) + + class Meta: + verbose_name = _("Ebios RM Study") + verbose_name_plural = _("Ebios RM Studies") + ordering = ["created_at"] From ea36b6c789b7d5a3ff9997f87e337b410557716f Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 16:03:17 +0100 Subject: [PATCH 03/34] Write RO/TO model --- backend/ebios_rm/models.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index d93827ad4..fff136960 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -1,6 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from core.base_models import NameDescriptionMixin, ETADueDateMixin +from core.base_models import AbstractBaseModel, NameDescriptionMixin, ETADueDateMixin from iam.models import FolderMixin, User @@ -47,3 +47,34 @@ class Meta: verbose_name = _("Ebios RM Study") verbose_name_plural = _("Ebios RM Studies") ordering = ["created_at"] + + +class ROTO(AbstractBaseModel): + class RiskOrigin(models.TextChoices): + STATE = "state", _("State") + ORGANIZED_CRIME = "organized_crime", _("Organized crime") + TERRORIST = "terrorist", _("Terrorist") + ACTIVIST = "activist", _("Activist") + PROFESSIONAL = "professional", _("Professional") + AMATEUR = "amateur", _("Amateur") + AVENGER = "avenger", _("Avenger") + PATHOLOGICAL = "pathological", _("Pathological") + + study = models.ForeignKey( + EbiosRMStudy, verbose_name=_("EBIOS RM study"), on_delete=models.CASCADE + ) + risk_origin = models.CharField(max_length=200, verbose_name=_("Risk origin")) + target_objective = models.CharField( + max_length=200, verbose_name=_("Target objective") + ) + motivation = models.PositiveSmallIntegerField(verbose_name=_("Motivation")) + resources = models.PositiveSmallIntegerField(verbose_name=_("Resources")) + pertinence = models.PositiveSmallIntegerField(verbose_name=_("Pertinence")) + activity = models.PositiveSmallIntegerField(verbose_name=_("Activity")) + is_selected = models.BooleanField(verbose_name=_("Is selected")) + justification = models.TextField(verbose_name=_("Justification")) + + class Meta: + verbose_name = _("RO/TO couple") + verbose_name_plural = _("RO/TO couples") + ordering = ["created_at"] From 2e9075cfd4fd5bd0e018136ae12dad24bc050699 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 16:23:28 +0100 Subject: [PATCH 04/34] Remove %(class)s_ prefix from authors and reviewers relationships This is only useful on abstract models. --- backend/ebios_rm/models.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index fff136960..eb242dde5 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from core.base_models import AbstractBaseModel, NameDescriptionMixin, ETADueDateMixin +from core.models import Asset, ComplianceAssessment, RiskAssessment, RiskMatrix from iam.models import FolderMixin, User @@ -12,6 +13,29 @@ class Status(models.TextChoices): DONE = "done", _("Done") DEPRECATED = "deprecated", _("Deprecated") + risk_matrix = models.ForeignKey( + RiskMatrix, + on_delete=models.PROTECT, + verbose_name=_("Risk matrix"), + related_name="ebios_rm_studies", + ) + assets = models.ManyToManyField( + Asset, + verbose_name=_("Assets"), + related_name="ebios_rm_studies", + ) + compliance_assessments = models.ManyToManyField( + ComplianceAssessment, + verbose_name=_("Compliance assessments"), + related_name="ebios_rm_studies", + ) + risk_assessments = models.ManyToManyField( + RiskAssessment, + verbose_name=_("Risk assessments"), + help_text=_("Risk assessments generated at the end of workshop 4"), + related_name="ebios_rm_studies", + ) + ref_id = models.CharField(max_length=100, unique=True) version = models.CharField( max_length=100, @@ -33,13 +57,13 @@ class Status(models.TextChoices): User, blank=True, verbose_name=_("Authors"), - related_name="%(class)s_authors", + related_name="authors", ) reviewers = models.ManyToManyField( User, blank=True, verbose_name=_("Reviewers"), - related_name="%(class)s_reviewers", + related_name="reviewers", ) observation = models.TextField(null=True, blank=True, verbose_name=_("Observation")) From 2c37462ea628f3fc9046a762e3efe9a663591533 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 16:29:19 +0100 Subject: [PATCH 05/34] Add help texts as documentation for EbiosRMStudy foreign keys --- backend/ebios_rm/models.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index eb242dde5..42440d0c7 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -18,22 +18,29 @@ class Status(models.TextChoices): on_delete=models.PROTECT, verbose_name=_("Risk matrix"), related_name="ebios_rm_studies", + help_text=_( + "Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`" + ), ) assets = models.ManyToManyField( Asset, verbose_name=_("Assets"), related_name="ebios_rm_studies", + help_text=_("Assets that are pertinent to the study"), ) compliance_assessments = models.ManyToManyField( ComplianceAssessment, verbose_name=_("Compliance assessments"), related_name="ebios_rm_studies", + help_text=_( + "Compliance assessments established as security baseline during workshop 1.4" + ), ) risk_assessments = models.ManyToManyField( RiskAssessment, verbose_name=_("Risk assessments"), - help_text=_("Risk assessments generated at the end of workshop 4"), related_name="ebios_rm_studies", + help_text=_("Risk assessments generated at the end of workshop 4"), ) ref_id = models.CharField(max_length=100, unique=True) From fa2a2be37cd1805a58a97daae9eafcc3c4444b07 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 16:30:38 +0100 Subject: [PATCH 06/34] Remove unique constraint on EbiosRMStudy ref_id field --- backend/ebios_rm/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 42440d0c7..e7ec3ea9a 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -43,7 +43,7 @@ class Status(models.TextChoices): help_text=_("Risk assessments generated at the end of workshop 4"), ) - ref_id = models.CharField(max_length=100, unique=True) + ref_id = models.CharField(max_length=100) version = models.CharField( max_length=100, blank=True, From 432284ae03262182f4ada7b3211435fe974ef5ff Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 16:35:56 +0100 Subject: [PATCH 07/34] Write FearedEvent model --- backend/ebios_rm/models.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index e7ec3ea9a..bba634ef7 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -80,6 +80,30 @@ class Meta: ordering = ["created_at"] +class FearedEvent(NameDescriptionMixin): + ebios_rm_study = models.ForeignKey( + EbiosRMStudy, + verbose_name=_("EBIOS RM study"), + on_delete=models.CASCADE, + ) + assets = models.ManyToManyField( + Asset, + verbose_name=_("Assets"), + related_name="feared_events", + help_text=_("Assets that are affected by the feared event"), + ) + + ref_id = models.CharField(max_length=100) + gravity = models.PositiveSmallIntegerField(verbose_name=_("Gravity")) + is_selected = models.BooleanField(verbose_name=_("Is selected")) + justification = models.TextField(verbose_name=_("Justification")) + + class Meta: + verbose_name = _("Feared event") + verbose_name_plural = _("Feared events") + ordering = ["created_at"] + + class ROTO(AbstractBaseModel): class RiskOrigin(models.TextChoices): STATE = "state", _("State") @@ -91,9 +115,15 @@ class RiskOrigin(models.TextChoices): AVENGER = "avenger", _("Avenger") PATHOLOGICAL = "pathological", _("Pathological") - study = models.ForeignKey( - EbiosRMStudy, verbose_name=_("EBIOS RM study"), on_delete=models.CASCADE + ebios_rm_study = models.ForeignKey( + EbiosRMStudy, + verbose_name=_("EBIOS RM study"), + on_delete=models.CASCADE, ) + feared_events = models.ManyToManyField( + FearedEvent, verbose_name=_("Feared events"), related_name="ro_to_couples" + ) + risk_origin = models.CharField(max_length=200, verbose_name=_("Risk origin")) target_objective = models.CharField( max_length=200, verbose_name=_("Target objective") From aab64268963e8b2f62e98e24411a0083bd57c73a Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:05:45 +0100 Subject: [PATCH 08/34] Create Qualification model --- backend/core/migrations/0044_qualification.py | 44 ++++++++ backend/core/models.py | 102 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 backend/core/migrations/0044_qualification.py diff --git a/backend/core/migrations/0044_qualification.py b/backend/core/migrations/0044_qualification.py new file mode 100644 index 000000000..12bf676c1 --- /dev/null +++ b/backend/core/migrations/0044_qualification.py @@ -0,0 +1,44 @@ +# Generated by Django 5.1.1 on 2024-12-02 17:01 + +import django.db.models.deletion +import iam.models +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0043_historicalmetric'), + ('iam', '0009_create_allauth_emailaddress_objects'), + ] + + operations = [ + migrations.CreateModel( + name='Qualification', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('urn', models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='URN')), + ('ref_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='Reference ID')), + ('provider', models.CharField(blank=True, max_length=200, null=True, verbose_name='Provider')), + ('name', models.CharField(max_length=200, null=True, verbose_name='Name')), + ('description', models.TextField(blank=True, null=True, verbose_name='Description')), + ('annotation', models.TextField(blank=True, null=True, verbose_name='Annotation')), + ('translations', models.JSONField(blank=True, null=True, verbose_name='Translations')), + ('locale', models.CharField(default='en', max_length=100, verbose_name='Locale')), + ('default_locale', models.BooleanField(default=True, verbose_name='Default locale')), + ('abbreviation', models.CharField(blank=True, max_length=20, null=True, verbose_name='Abbreviation')), + ('qualification_ordering', models.PositiveSmallIntegerField(default=0, verbose_name='Ordering')), + ('security_objective_ordering', models.PositiveSmallIntegerField(default=0, verbose_name='Security objective ordering')), + ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), + ], + options={ + 'verbose_name': 'Qualification', + 'verbose_name_plural': 'Qualifications', + 'ordering': ['qualification_ordering'], + }, + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index fa0d57fb9..1a72b87f2 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1191,6 +1191,108 @@ def coverage(self) -> str: return RequirementMapping.Coverage.PARTIAL +class Qualification(ReferentialObjectMixin, I18nObjectMixin): + DEFAULT_QUALIFICATIONS = [ + { + "abbreviation": "C", + "qualification_ordering": 1, + "security_objective_ordering": 1, + "name": "Confidentiality", + "urn": "urn:intuitem:risk:qualification:confidentiality", + }, + { + "abbreviation": "I", + "qualification_ordering": 2, + "security_objective_ordering": 2, + "name": "Integrity", + "urn": "urn:intuitem:risk:qualification:integrity", + }, + { + "abbreviation": "A", + "qualification_ordering": 3, + "security_objective_ordering": 3, + "name": "Availability", + "urn": "urn:intuitem:risk:qualification:availability", + }, + { + "abbreviation": "P", + "qualification_ordering": 4, + "security_objective_ordering": 4, + "name": "Proof", + "urn": "urn:intuitem:risk:qualification:proof", + }, + { + "abbreviation": "Aut", + "qualification_ordering": 5, + "security_objective_ordering": 5, + "name": "Authenticity", + "urn": "urn:intuitem:risk:qualification:authenticity", + }, + { + "abbreviation": "Priv", + "qualification_ordering": 6, + "security_objective_ordering": 6, + "name": "Privacy", + "urn": "urn:intuitem:risk:qualification:privacy", + }, + { + "abbreviation": "Safe", + "qualification_ordering": 7, + "security_objective_ordering": 7, + "name": "Safety", + "urn": "urn:intuitem:risk:qualification:safety", + }, + { + "abbreviation": "Rep", + "qualification_ordering": 8, + "name": "Reputation", + "urn": "urn:intuitem:risk:qualification:reputation", + }, + { + "abbreviation": "Ope", + "qualification_ordering": 9, + "name": "Operational", + "urn": "urn:intuitem:risk:qualification:operational", + }, + { + "abbreviation": "Leg", + "qualification_ordering": 10, + "name": "Legal", + "urn": "urn:intuitem:risk:qualification:legal", + }, + { + "abbreviation": "Fin", + "qualification_ordering": 11, + "name": "Financial", + "urn": "urn:intuitem:risk:qualification:financial", + }, + ] + + abbreviation = models.CharField( + max_length=20, null=True, blank=True, verbose_name=_("Abbreviation") + ) + qualification_ordering = models.PositiveSmallIntegerField( + verbose_name=_("Ordering"), default=0 + ) + security_objective_ordering = models.PositiveSmallIntegerField( + verbose_name=_("Security objective ordering"), default=0 + ) + + class Meta: + verbose_name = _("Qualification") + verbose_name_plural = _("Qualifications") + ordering = ["qualification_ordering"] + + @classmethod + def create_default_qualifications(cls): + for qualification in cls.DEFAULT_QUALIFICATIONS: + Qualification.objects.update_or_create( + urn=qualification["urn"], + defaults=qualification, + create_defaults=qualification, + ) + + ########################### Domain objects ######################### From a68cfe4edef5c7f57d2e2454155d84f0ae3b157f Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:07:40 +0100 Subject: [PATCH 09/34] Create default qualifications on startup --- backend/core/startup.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/core/startup.py b/backend/core/startup.py index f3faad47d..d74419221 100644 --- a/backend/core/startup.py +++ b/backend/core/startup.py @@ -361,6 +361,7 @@ def startup(sender: AppConfig, **kwargs): """ from django.contrib.auth.models import Permission + from core.models import Qualification from iam.models import Folder, Role, RoleAssignment, User, UserGroup from tprm.models import Entity @@ -490,7 +491,13 @@ def startup(sender: AppConfig, **kwargs): email=CISO_ASSISTANT_SUPERUSER_EMAIL, is_superuser=True ) except Exception as e: - print(e) # NOTE: Add this exception in the logger + logger.error("Error creating superuser", exc_info=e) + + # Create default Qualifications + try: + Qualification.create_default_qualifications() + except Exception as e: + logger.error("Error creating default qualifications", exc_info=e) call_command("storelibraries") From 04422b0723fe3b03d71e282390410399bac6e7f0 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:10:05 +0100 Subject: [PATCH 10/34] Add qualifications ManyToMany relationship to FearedEvent model --- backend/ebios_rm/models.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index bba634ef7..4eb1eb775 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -1,7 +1,13 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from core.base_models import AbstractBaseModel, NameDescriptionMixin, ETADueDateMixin -from core.models import Asset, ComplianceAssessment, RiskAssessment, RiskMatrix +from core.models import ( + Asset, + ComplianceAssessment, + Qualification, + RiskAssessment, + RiskMatrix, +) from iam.models import FolderMixin, User @@ -92,6 +98,12 @@ class FearedEvent(NameDescriptionMixin): related_name="feared_events", help_text=_("Assets that are affected by the feared event"), ) + qualifications = models.ManyToManyField( + Qualification, + verbose_name=_("Qualifications"), + related_name="feared_events", + help_text=_("Qualifications carried by the feared event"), + ) ref_id = models.CharField(max_length=100) gravity = models.PositiveSmallIntegerField(verbose_name=_("Gravity")) From 2a291587c8065e771d8f15e79a85e70eef9814b7 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:36:26 +0100 Subject: [PATCH 11/34] Write Stakeholder model --- backend/ebios_rm/models.py | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 4eb1eb775..92c03fc01 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -1,7 +1,9 @@ +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.translation import gettext_lazy as _ from core.base_models import AbstractBaseModel, NameDescriptionMixin, ETADueDateMixin from core.models import ( + AppliedControl, Asset, ComplianceAssessment, Qualification, @@ -9,6 +11,7 @@ RiskMatrix, ) from iam.models import FolderMixin, User +from tprm.models import Entity class EbiosRMStudy(NameDescriptionMixin, ETADueDateMixin, FolderMixin): @@ -151,3 +154,76 @@ class Meta: verbose_name = _("RO/TO couple") verbose_name_plural = _("RO/TO couples") ordering = ["created_at"] + + +class Stakeholder(AbstractBaseModel): + ebios_rm_study = models.ForeignKey( + EbiosRMStudy, + verbose_name=_("EBIOS RM study"), + on_delete=models.CASCADE, + ) + entity = models.ForeignKey( + Entity, + on_delete=models.CASCADE, + verbose_name=_("Entity"), + help_text=_("Entity qualified by the stakeholder"), + ) + applied_controls = models.ManyToManyField( + AppliedControl, + verbose_name=_("Applied controls"), + blank=True, + related_name="stakeholders", + help_text=_("Controls applied to lower stakeholder criticality"), + ) + + category = models.CharField(max_length=128, verbose_name=_("Category")) + + current_dependency = models.PositiveSmallIntegerField( + verbose_name=_("Current dependency"), + default=0, + validators=[MaxValueValidator(4)], + ) + current_penetration = models.PositiveSmallIntegerField( + verbose_name=_("Current penetration"), + default=0, + validators=[MaxValueValidator(4)], + ) + current_maturity = models.PositiveSmallIntegerField( + verbose_name=_("Current maturity"), + default=1, + validators=[MinValueValidator(1), MaxValueValidator(4)], + ) + current_trust = models.PositiveSmallIntegerField( + verbose_name=_("Current trust"), + default=1, + validators=[MinValueValidator(1), MaxValueValidator(4)], + ) + + residual_dependency = models.PositiveSmallIntegerField( + verbose_name=_("Residual dependency"), + default=0, + validators=[MaxValueValidator(4)], + ) + residual_penetration = models.PositiveSmallIntegerField( + verbose_name=_("Residual penetration"), + default=0, + validators=[MaxValueValidator(4)], + ) + residual_maturity = models.PositiveSmallIntegerField( + verbose_name=_("Residual maturity"), + default=1, + validators=[MinValueValidator(1), MaxValueValidator(4)], + ) + residual_trust = models.PositiveSmallIntegerField( + verbose_name=_("Residual trust"), + default=1, + validators=[MinValueValidator(1), MaxValueValidator(4)], + ) + + is_selected = models.BooleanField(verbose_name=_("Is selected")) + justification = models.TextField(verbose_name=_("Justification")) + + class Meta: + verbose_name = _("Stakeholder") + verbose_name_plural = _("Stakeholders") + ordering = ["created_at"] From 2333a91473674533786359b1788138f81d247157 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:38:32 +0100 Subject: [PATCH 12/34] Add Stakeholder.category text choices --- backend/ebios_rm/models.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 92c03fc01..86a73ceee 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -157,6 +157,11 @@ class Meta: class Stakeholder(AbstractBaseModel): + class Category(models.TextChoices): + CLIENT = "client", _("Client") + PARTNER = "partner", _("Partner") + SUPPLIER = "supplier", _("Supplier") + ebios_rm_study = models.ForeignKey( EbiosRMStudy, verbose_name=_("EBIOS RM study"), @@ -176,7 +181,9 @@ class Stakeholder(AbstractBaseModel): help_text=_("Controls applied to lower stakeholder criticality"), ) - category = models.CharField(max_length=128, verbose_name=_("Category")) + category = models.CharField( + max_length=32, verbose_name=_("Category"), choices=Category.choices + ) current_dependency = models.PositiveSmallIntegerField( verbose_name=_("Current dependency"), From ade6ea23a2e34d6995bf082f41bb75c1ddc6d5a5 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:45:05 +0100 Subject: [PATCH 13/34] Write AttackPath model --- backend/ebios_rm/models.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 86a73ceee..8b4802a67 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -234,3 +234,32 @@ class Meta: verbose_name = _("Stakeholder") verbose_name_plural = _("Stakeholders") ordering = ["created_at"] + + +class AttackPath(AbstractBaseModel): + ebios_rm_study = models.ForeignKey( + EbiosRMStudy, + verbose_name=_("EBIOS RM study"), + on_delete=models.CASCADE, + ) + ro_to_couple = models.ForeignKey( + ROTO, + verbose_name=_("RO/TO couple"), + on_delete=models.CASCADE, + help_text=_("RO/TO couple from which the attach path is derived"), + ) + stakeholders = models.ManyToManyField( + Stakeholder, + verbose_name=_("Stakeholders"), + related_name="attack_paths", + help_text=_("Stakeholders leveraged by the attack path"), + ) + + description = models.TextField(verbose_name=_("Description")) + is_selected = models.BooleanField(verbose_name=_("Is selected")) + justification = models.TextField(verbose_name=_("Justification")) + + class Meta: + verbose_name = _("Attack path") + verbose_name_plural = _("Attack paths") + ordering = ["created_at"] From eaae8bde4972dbdb27ff6a731d5696f8cad8fe7e Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:49:41 +0100 Subject: [PATCH 14/34] Add criticality properties --- backend/ebios_rm/models.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 8b4802a67..78766d7df 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -235,6 +235,32 @@ class Meta: verbose_name_plural = _("Stakeholders") ordering = ["created_at"] + @staticmethod + def _compute_criticality( + dependency: int, penetration: int, maturity: int, trust: int + ): + if (maturity * trust) == 0: + return 0 + return (dependency * penetration) / (maturity * trust) + + @property + def current_criticality(self): + return self._compute_criticality( + self.current_dependency, + self.current_penetration, + self.current_maturity, + self.current_trust, + ) + + @property + def residual_criticality(self): + return self._compute_criticality( + self.residual_dependency, + self.residual_penetration, + self.residual_maturity, + self.residual_trust, + ) + class AttackPath(AbstractBaseModel): ebios_rm_study = models.ForeignKey( From 6ef1bcee97c0e4cf4c67b0536b8cbe902ada40c8 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 18:54:43 +0100 Subject: [PATCH 15/34] Write OperationalScenario model --- backend/ebios_rm/models.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 78766d7df..08d47fedc 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -9,6 +9,7 @@ Qualification, RiskAssessment, RiskMatrix, + Threat, ) from iam.models import FolderMixin, User from tprm.models import Entity @@ -289,3 +290,35 @@ class Meta: verbose_name = _("Attack path") verbose_name_plural = _("Attack paths") ordering = ["created_at"] + + +class OperationalScenario(AbstractBaseModel): + ebios_rm_study = models.ForeignKey( + EbiosRMStudy, + verbose_name=_("EBIOS RM study"), + related_name="operational_scenarios", + on_delete=models.CASCADE, + ) + attack_paths = models.ManyToManyField( + AttackPath, + verbose_name=_("Attack paths"), + related_name="operational_scenarios", + help_text=_("Attack paths that are pertinent to the operational scenario"), + ) + threats = models.ManyToManyField( + Threat, + verbose_name=_("Threats"), + blank=True, + related_name="operational_scenarios", + help_text=_("Threats leveraged by the operational scenario"), + ) + + description = models.TextField(verbose_name=_("Description")) + likelihood = models.SmallIntegerField(default=-1, verbose_name=_("Likelihood")) + is_selected = models.BooleanField(verbose_name=_("Is selected")) + justification = models.TextField(verbose_name=_("Justification")) + + class Meta: + verbose_name = _("Operational scenario") + verbose_name_plural = _("Operational scenarios") + ordering = ["created_at"] From ac15c3a74734f754424a1599f530d7d2e5de9bc7 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 19:07:37 +0100 Subject: [PATCH 16/34] Add text choices to RO/TO model --- backend/ebios_rm/models.py | 47 ++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 08d47fedc..cb914b51c 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -131,6 +131,27 @@ class RiskOrigin(models.TextChoices): AVENGER = "avenger", _("Avenger") PATHOLOGICAL = "pathological", _("Pathological") + class Motivation(models.IntegerChoices): + UNDEFINED = 0, "undefined" + VERY_LOW = 1, "very_low" + LOW = 2, "low" + SIGNIFICANT = 3, "significant" + STRONG = 4, "strong" + + class Resources(models.IntegerChoices): + UNDEFINED = 0, "undefined" + LIMITED = 1, "limited" + SIGNIFICANT = 2, "significant" + IMPORTANT = 3, "important" + UNLIMITED = 4, "unlimited" + + class Pertinence(models.IntegerChoices): + UNDEFINED = 0, "undefined" + IRRELAVANT = 1, "irrelevant" + PARTIALLY_RELEVANT = 2, "partially_relevant" + FAIRLY_RELEVANT = 3, "fairly_relevant" + HIGHLY_RELEVANT = 4, "highly_relevant" + ebios_rm_study = models.ForeignKey( EbiosRMStudy, verbose_name=_("EBIOS RM study"), @@ -140,14 +161,30 @@ class RiskOrigin(models.TextChoices): FearedEvent, verbose_name=_("Feared events"), related_name="ro_to_couples" ) - risk_origin = models.CharField(max_length=200, verbose_name=_("Risk origin")) + risk_origin = models.CharField( + max_length=32, verbose_name=_("Risk origin"), choices=RiskOrigin.choices + ) target_objective = models.CharField( max_length=200, verbose_name=_("Target objective") ) - motivation = models.PositiveSmallIntegerField(verbose_name=_("Motivation")) - resources = models.PositiveSmallIntegerField(verbose_name=_("Resources")) - pertinence = models.PositiveSmallIntegerField(verbose_name=_("Pertinence")) - activity = models.PositiveSmallIntegerField(verbose_name=_("Activity")) + motivation = models.PositiveSmallIntegerField( + verbose_name=_("Motivation"), + choices=Motivation.choices, + default=Motivation.UNDEFINED, + ) + resources = models.PositiveSmallIntegerField( + verbose_name=_("Resources"), + choices=Resources.choices, + default=Resources.UNDEFINED, + ) + pertinence = models.PositiveSmallIntegerField( + verbose_name=_("Pertinence"), + choices=Pertinence.choices, + default=Pertinence.UNDEFINED, + ) + activity = models.PositiveSmallIntegerField( + verbose_name=_("Activity"), default=0, validators=[MaxValueValidator(4)] + ) is_selected = models.BooleanField(verbose_name=_("Is selected")) justification = models.TextField(verbose_name=_("Justification")) From 12839a92893ff586099ead754678515fb4242af8 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 19:09:52 +0100 Subject: [PATCH 17/34] Make initial migration for EBIOS RM app --- backend/ciso_assistant/settings.py | 1 + backend/core/migrations/0044_qualification.py | 134 +++- backend/ebios_rm/apps.py | 4 +- backend/ebios_rm/migrations/0001_initial.py | 622 ++++++++++++++++++ 4 files changed, 735 insertions(+), 26 deletions(-) create mode 100644 backend/ebios_rm/migrations/0001_initial.py diff --git a/backend/ciso_assistant/settings.py b/backend/ciso_assistant/settings.py index 32b1a25d3..708220052 100644 --- a/backend/ciso_assistant/settings.py +++ b/backend/ciso_assistant/settings.py @@ -140,6 +140,7 @@ def set_ciso_assistant_url(_, __, event_dict): "global_settings", "tprm", "core", + "ebios_rm", "cal", "django_filters", "library", diff --git a/backend/core/migrations/0044_qualification.py b/backend/core/migrations/0044_qualification.py index 12bf676c1..39592ce97 100644 --- a/backend/core/migrations/0044_qualification.py +++ b/backend/core/migrations/0044_qualification.py @@ -7,38 +7,124 @@ class Migration(migrations.Migration): - dependencies = [ - ('core', '0043_historicalmetric'), - ('iam', '0009_create_allauth_emailaddress_objects'), + ("core", "0043_historicalmetric"), + ("iam", "0009_create_allauth_emailaddress_objects"), ] operations = [ migrations.CreateModel( - name='Qualification', + name="Qualification", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('urn', models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='URN')), - ('ref_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='Reference ID')), - ('provider', models.CharField(blank=True, max_length=200, null=True, verbose_name='Provider')), - ('name', models.CharField(max_length=200, null=True, verbose_name='Name')), - ('description', models.TextField(blank=True, null=True, verbose_name='Description')), - ('annotation', models.TextField(blank=True, null=True, verbose_name='Annotation')), - ('translations', models.JSONField(blank=True, null=True, verbose_name='Translations')), - ('locale', models.CharField(default='en', max_length=100, verbose_name='Locale')), - ('default_locale', models.BooleanField(default=True, verbose_name='Default locale')), - ('abbreviation', models.CharField(blank=True, max_length=20, null=True, verbose_name='Abbreviation')), - ('qualification_ordering', models.PositiveSmallIntegerField(default=0, verbose_name='Ordering')), - ('security_objective_ordering', models.PositiveSmallIntegerField(default=0, verbose_name='Security objective ordering')), - ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ( + "urn", + models.CharField( + blank=True, + max_length=255, + null=True, + unique=True, + verbose_name="URN", + ), + ), + ( + "ref_id", + models.CharField( + blank=True, + max_length=100, + null=True, + verbose_name="Reference ID", + ), + ), + ( + "provider", + models.CharField( + blank=True, max_length=200, null=True, verbose_name="Provider" + ), + ), + ( + "name", + models.CharField(max_length=200, null=True, verbose_name="Name"), + ), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ( + "annotation", + models.TextField(blank=True, null=True, verbose_name="Annotation"), + ), + ( + "translations", + models.JSONField( + blank=True, null=True, verbose_name="Translations" + ), + ), + ( + "locale", + models.CharField( + default="en", max_length=100, verbose_name="Locale" + ), + ), + ( + "default_locale", + models.BooleanField(default=True, verbose_name="Default locale"), + ), + ( + "abbreviation", + models.CharField( + blank=True, + max_length=20, + null=True, + verbose_name="Abbreviation", + ), + ), + ( + "qualification_ordering", + models.PositiveSmallIntegerField( + default=0, verbose_name="Ordering" + ), + ), + ( + "security_objective_ordering", + models.PositiveSmallIntegerField( + default=0, verbose_name="Security objective ordering" + ), + ), + ( + "folder", + models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), ], options={ - 'verbose_name': 'Qualification', - 'verbose_name_plural': 'Qualifications', - 'ordering': ['qualification_ordering'], + "verbose_name": "Qualification", + "verbose_name_plural": "Qualifications", + "ordering": ["qualification_ordering"], }, ), ] diff --git a/backend/ebios_rm/apps.py b/backend/ebios_rm/apps.py index 818aaacc5..2c46fb400 100644 --- a/backend/ebios_rm/apps.py +++ b/backend/ebios_rm/apps.py @@ -2,5 +2,5 @@ class EbiosRmConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'ebios_rm' + default_auto_field = "django.db.models.BigAutoField" + name = "ebios_rm" diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py new file mode 100644 index 000000000..a5ce90ea9 --- /dev/null +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -0,0 +1,622 @@ +# Generated by Django 5.1.1 on 2024-12-02 18:08 + +import django.core.validators +import django.db.models.deletion +import iam.models +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("core", "0044_qualification"), + ("iam", "0009_create_allauth_emailaddress_objects"), + ("tprm", "0003_entityassessment_representatives"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="EbiosRMStudy", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("name", models.CharField(max_length=200, verbose_name="Name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ("eta", models.DateField(blank=True, null=True, verbose_name="ETA")), + ( + "due_date", + models.DateField(blank=True, null=True, verbose_name="Due date"), + ), + ("ref_id", models.CharField(max_length=100)), + ( + "version", + models.CharField( + blank=True, + default="1.0", + help_text="Version of the Ebios RM study (eg. 1.0, 2.0, etc.)", + max_length=100, + null=True, + verbose_name="Version", + ), + ), + ( + "status", + models.CharField( + blank=True, + choices=[ + ("planned", "Planned"), + ("in_progress", "In progress"), + ("in_review", "In review"), + ("done", "Done"), + ("deprecated", "Deprecated"), + ], + default="planned", + max_length=100, + null=True, + verbose_name="Status", + ), + ), + ( + "observation", + models.TextField(blank=True, null=True, verbose_name="Observation"), + ), + ( + "assets", + models.ManyToManyField( + help_text="Assets that are pertinent to the study", + related_name="ebios_rm_studies", + to="core.asset", + verbose_name="Assets", + ), + ), + ( + "authors", + models.ManyToManyField( + blank=True, + related_name="authors", + to=settings.AUTH_USER_MODEL, + verbose_name="Authors", + ), + ), + ( + "compliance_assessments", + models.ManyToManyField( + help_text="Compliance assessments established as security baseline during workshop 1.4", + related_name="ebios_rm_studies", + to="core.complianceassessment", + verbose_name="Compliance assessments", + ), + ), + ( + "folder", + models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + ( + "reviewers", + models.ManyToManyField( + blank=True, + related_name="reviewers", + to=settings.AUTH_USER_MODEL, + verbose_name="Reviewers", + ), + ), + ( + "risk_assessments", + models.ManyToManyField( + help_text="Risk assessments generated at the end of workshop 4", + related_name="ebios_rm_studies", + to="core.riskassessment", + verbose_name="Risk assessments", + ), + ), + ( + "risk_matrix", + models.ForeignKey( + help_text="Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`", + on_delete=django.db.models.deletion.PROTECT, + related_name="ebios_rm_studies", + to="core.riskmatrix", + verbose_name="Risk matrix", + ), + ), + ], + options={ + "verbose_name": "Ebios RM Study", + "verbose_name_plural": "Ebios RM Studies", + "ordering": ["created_at"], + }, + ), + migrations.CreateModel( + name="AttackPath", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("description", models.TextField(verbose_name="Description")), + ("is_selected", models.BooleanField(verbose_name="Is selected")), + ("justification", models.TextField(verbose_name="Justification")), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ], + options={ + "verbose_name": "Attack path", + "verbose_name_plural": "Attack paths", + "ordering": ["created_at"], + }, + ), + migrations.CreateModel( + name="FearedEvent", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("name", models.CharField(max_length=200, verbose_name="Name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ("ref_id", models.CharField(max_length=100)), + ("gravity", models.PositiveSmallIntegerField(verbose_name="Gravity")), + ("is_selected", models.BooleanField(verbose_name="Is selected")), + ("justification", models.TextField(verbose_name="Justification")), + ( + "assets", + models.ManyToManyField( + help_text="Assets that are affected by the feared event", + related_name="feared_events", + to="core.asset", + verbose_name="Assets", + ), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "qualifications", + models.ManyToManyField( + help_text="Qualifications carried by the feared event", + related_name="feared_events", + to="core.qualification", + verbose_name="Qualifications", + ), + ), + ], + options={ + "verbose_name": "Feared event", + "verbose_name_plural": "Feared events", + "ordering": ["created_at"], + }, + ), + migrations.CreateModel( + name="OperationalScenario", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("description", models.TextField(verbose_name="Description")), + ( + "likelihood", + models.SmallIntegerField(default=-1, verbose_name="Likelihood"), + ), + ("is_selected", models.BooleanField(verbose_name="Is selected")), + ("justification", models.TextField(verbose_name="Justification")), + ( + "attack_paths", + models.ManyToManyField( + help_text="Attack paths that are pertinent to the operational scenario", + related_name="operational_scenarios", + to="ebios_rm.attackpath", + verbose_name="Attack paths", + ), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="operational_scenarios", + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "threats", + models.ManyToManyField( + blank=True, + help_text="Threats leveraged by the operational scenario", + related_name="operational_scenarios", + to="core.threat", + verbose_name="Threats", + ), + ), + ], + options={ + "verbose_name": "Operational scenario", + "verbose_name_plural": "Operational scenarios", + "ordering": ["created_at"], + }, + ), + migrations.CreateModel( + name="ROTO", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ( + "risk_origin", + models.CharField( + choices=[ + ("state", "State"), + ("organized_crime", "Organized crime"), + ("terrorist", "Terrorist"), + ("activist", "Activist"), + ("professional", "Professional"), + ("amateur", "Amateur"), + ("avenger", "Avenger"), + ("pathological", "Pathological"), + ], + max_length=32, + verbose_name="Risk origin", + ), + ), + ( + "target_objective", + models.CharField(max_length=200, verbose_name="Target objective"), + ), + ( + "motivation", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "very_low"), + (2, "low"), + (3, "significant"), + (4, "strong"), + ], + default=0, + verbose_name="Motivation", + ), + ), + ( + "resources", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "limited"), + (2, "significant"), + (3, "important"), + (4, "unlimited"), + ], + default=0, + verbose_name="Resources", + ), + ), + ( + "pertinence", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "irrelevant"), + (2, "partially_relevant"), + (3, "fairly_relevant"), + (4, "highly_relevant"), + ], + default=0, + verbose_name="Pertinence", + ), + ), + ( + "activity", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Activity", + ), + ), + ("is_selected", models.BooleanField(verbose_name="Is selected")), + ("justification", models.TextField(verbose_name="Justification")), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "feared_events", + models.ManyToManyField( + related_name="ro_to_couples", + to="ebios_rm.fearedevent", + verbose_name="Feared events", + ), + ), + ], + options={ + "verbose_name": "RO/TO couple", + "verbose_name_plural": "RO/TO couples", + "ordering": ["created_at"], + }, + ), + migrations.AddField( + model_name="attackpath", + name="ro_to_couple", + field=models.ForeignKey( + help_text="RO/TO couple from which the attach path is derived", + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.roto", + verbose_name="RO/TO couple", + ), + ), + migrations.CreateModel( + name="Stakeholder", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ( + "category", + models.CharField( + choices=[ + ("client", "Client"), + ("partner", "Partner"), + ("supplier", "Supplier"), + ], + max_length=32, + verbose_name="Category", + ), + ), + ( + "current_dependency", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Current dependency", + ), + ), + ( + "current_penetration", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Current penetration", + ), + ), + ( + "current_maturity", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Current maturity", + ), + ), + ( + "current_trust", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Current trust", + ), + ), + ( + "residual_dependency", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Residual dependency", + ), + ), + ( + "residual_penetration", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Residual penetration", + ), + ), + ( + "residual_maturity", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Residual maturity", + ), + ), + ( + "residual_trust", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Residual trust", + ), + ), + ("is_selected", models.BooleanField(verbose_name="Is selected")), + ("justification", models.TextField(verbose_name="Justification")), + ( + "applied_controls", + models.ManyToManyField( + blank=True, + help_text="Controls applied to lower stakeholder criticality", + related_name="stakeholders", + to="core.appliedcontrol", + verbose_name="Applied controls", + ), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "entity", + models.ForeignKey( + help_text="Entity qualified by the stakeholder", + on_delete=django.db.models.deletion.CASCADE, + to="tprm.entity", + verbose_name="Entity", + ), + ), + ], + options={ + "verbose_name": "Stakeholder", + "verbose_name_plural": "Stakeholders", + "ordering": ["created_at"], + }, + ), + migrations.AddField( + model_name="attackpath", + name="stakeholders", + field=models.ManyToManyField( + help_text="Stakeholders leveraged by the attack path", + related_name="attack_paths", + to="ebios_rm.stakeholder", + verbose_name="Stakeholders", + ), + ), + ] From eafc8d505f06257b7070e8468757294aee772a56 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 19:12:35 +0100 Subject: [PATCH 18/34] Add ebios_rm app to enterprise INSTALLED_APPS --- enterprise/backend/enterprise_core/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enterprise/backend/enterprise_core/settings.py b/enterprise/backend/enterprise_core/settings.py index 32ef1e7c3..88d783fe1 100644 --- a/enterprise/backend/enterprise_core/settings.py +++ b/enterprise/backend/enterprise_core/settings.py @@ -143,6 +143,7 @@ def set_ciso_assistant_url(_, __, event_dict): "global_settings", "tprm", "core", + "ebios_rm", "cal", "django_filters", "library", From 0ea72156ec4bdd2c80551ee8afd3e4de312eb948 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:10:53 +0100 Subject: [PATCH 19/34] Write basic unit tests for EbiosRMStudy model --- backend/ebios_rm/tests/fixtures.py | 49 +++++++++++++++++++ backend/ebios_rm/tests/test_ebios_rm_study.py | 42 ++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 backend/ebios_rm/tests/fixtures.py create mode 100644 backend/ebios_rm/tests/test_ebios_rm_study.py diff --git a/backend/ebios_rm/tests/fixtures.py b/backend/ebios_rm/tests/fixtures.py new file mode 100644 index 000000000..b166b27b1 --- /dev/null +++ b/backend/ebios_rm/tests/fixtures.py @@ -0,0 +1,49 @@ +import pytest + +from core.models import RiskMatrix, StoredLibrary, Asset +from ebios_rm.models import EbiosRMStudy, FearedEvent + + +@pytest.fixture +def ebios_rm_matrix_fixture(): + library = StoredLibrary.objects.filter( + urn="urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm" + ).last() + assert library is not None + library.load() + return RiskMatrix.objects.get( + urn="urn:intuitem:risk:matrix:risk-matrix-4x4-ebios-rm" + ) + + +@pytest.fixture +def basic_assets_tree_fixture(): + primary_asset_1 = Asset.objects.create(name="Primary Asset 1") + primary_asset_2 = Asset.objects.create(name="Primary Asset 2") + supporting_asset = Asset.objects.create( + name="Supporting Asset 1", type=Asset.Type.SUPPORT + ) + supporting_asset.parent_assets.add(primary_asset_1, primary_asset_2) + return primary_asset_1, primary_asset_2, supporting_asset + + +@pytest.fixture +def basic_ebios_rm_study_fixture(ebios_rm_matrix_fixture, basic_assets_tree_fixture): + study = EbiosRMStudy.objects.create( + name="test study", + description="test study description", + risk_matrix=ebios_rm_matrix_fixture, + ) + study.assets.set(basic_assets_tree_fixture) + return study + + +@pytest.fixture +def basic_feared_event_fixture(basic_ebios_rm_study_fixture): + feared_event = FearedEvent.objects.create( + name="test feared event", + description="test feared event description", + ebios_rm_study=basic_ebios_rm_study_fixture, + ) + asset = Asset.objects.get(name="Primary Asset 1") + feared_event.assets.add(asset) diff --git a/backend/ebios_rm/tests/test_ebios_rm_study.py b/backend/ebios_rm/tests/test_ebios_rm_study.py new file mode 100644 index 000000000..a31b8455e --- /dev/null +++ b/backend/ebios_rm/tests/test_ebios_rm_study.py @@ -0,0 +1,42 @@ +import pytest +from core.models import Asset, RiskMatrix +from ebios_rm.models import EbiosRMStudy + +from ebios_rm.tests.fixtures import * + + +@pytest.mark.django_db +class TestEbiosRMStudy: + @pytest.mark.usefixtures("ebios_rm_matrix_fixture") + def test_create_ebios_rm_study_basic(self): + study = EbiosRMStudy.objects.create( + name="test study", + description="test study description", + risk_matrix=RiskMatrix.objects.get( + urn="urn:intuitem:risk:matrix:risk-matrix-4x4-ebios-rm" + ), + ) + assert study.name == "test study" + assert study.description == "test study description" + assert study.risk_matrix == RiskMatrix.objects.get( + urn="urn:intuitem:risk:matrix:risk-matrix-4x4-ebios-rm" + ) + + @pytest.mark.usefixtures("ebios_rm_matrix_fixture", "basic_assets_tree_fixture") + def test_create_ebios_rm_study_with_assets(self): + study = EbiosRMStudy.objects.create( + name="test study", + description="test study description", + risk_matrix=RiskMatrix.objects.get( + urn="urn:intuitem:risk:matrix:risk-matrix-4x4-ebios-rm" + ), + ) + study.assets.set(Asset.objects.filter(name="Primary Asset 1")) + assert study.name == "test study" + assert study.description == "test study description" + assert study.risk_matrix == RiskMatrix.objects.get( + urn="urn:intuitem:risk:matrix:risk-matrix-4x4-ebios-rm" + ) + + assert study.assets.count() == 1 + assert study.assets.filter(name="Primary Asset 1").exists() From 0c52819aac322969bc95b5cc73d6b71e4d97ab4c Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:10:59 +0100 Subject: [PATCH 20/34] Write basic unit tests for FearedEvent model --- backend/ebios_rm/tests/test_feared_event.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 backend/ebios_rm/tests/test_feared_event.py diff --git a/backend/ebios_rm/tests/test_feared_event.py b/backend/ebios_rm/tests/test_feared_event.py new file mode 100644 index 000000000..55b8d87c7 --- /dev/null +++ b/backend/ebios_rm/tests/test_feared_event.py @@ -0,0 +1,20 @@ +import pytest +from ebios_rm.models import EbiosRMStudy, FearedEvent + +from ebios_rm.tests.fixtures import * + + +@pytest.mark.django_db +class TestFearedEvent: + @pytest.mark.usefixtures("basic_ebios_rm_study_fixture") + def test_create_feared_event_basic(self): + feared_event = FearedEvent.objects.create( + name="test feared event", + description="test feared event description", + ebios_rm_study=EbiosRMStudy.objects.get(name="test study"), + ) + assert feared_event.name == "test feared event" + assert feared_event.description == "test feared event description" + assert feared_event.ebios_rm_study == EbiosRMStudy.objects.get( + name="test study" + ) From 8a98fad72af3dbbe78842aaaebfc681d0a41c399 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:11:21 +0100 Subject: [PATCH 21/34] Set default values for gravity, is_selected, justification fields --- backend/ebios_rm/models.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index cb914b51c..696397f04 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -110,9 +110,9 @@ class FearedEvent(NameDescriptionMixin): ) ref_id = models.CharField(max_length=100) - gravity = models.PositiveSmallIntegerField(verbose_name=_("Gravity")) - is_selected = models.BooleanField(verbose_name=_("Is selected")) - justification = models.TextField(verbose_name=_("Justification")) + gravity = models.PositiveSmallIntegerField(verbose_name=_("Gravity"), default=0) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Feared event") @@ -185,8 +185,8 @@ class Pertinence(models.IntegerChoices): activity = models.PositiveSmallIntegerField( verbose_name=_("Activity"), default=0, validators=[MaxValueValidator(4)] ) - is_selected = models.BooleanField(verbose_name=_("Is selected")) - justification = models.TextField(verbose_name=_("Justification")) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("RO/TO couple") @@ -265,8 +265,8 @@ class Category(models.TextChoices): validators=[MinValueValidator(1), MaxValueValidator(4)], ) - is_selected = models.BooleanField(verbose_name=_("Is selected")) - justification = models.TextField(verbose_name=_("Justification")) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Stakeholder") @@ -320,8 +320,8 @@ class AttackPath(AbstractBaseModel): ) description = models.TextField(verbose_name=_("Description")) - is_selected = models.BooleanField(verbose_name=_("Is selected")) - justification = models.TextField(verbose_name=_("Justification")) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Attack path") @@ -352,8 +352,8 @@ class OperationalScenario(AbstractBaseModel): description = models.TextField(verbose_name=_("Description")) likelihood = models.SmallIntegerField(default=-1, verbose_name=_("Likelihood")) - is_selected = models.BooleanField(verbose_name=_("Is selected")) - justification = models.TextField(verbose_name=_("Justification")) + is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) + justification = models.TextField(verbose_name=_("Justification"), blank=True) class Meta: verbose_name = _("Operational scenario") From d5de5274d7853edd42c17554227666c7ea4bdccb Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:12:29 +0100 Subject: [PATCH 22/34] chore: Make migrations --- backend/ebios_rm/migrations/0001_initial.py | 682 ++++---------------- 1 file changed, 118 insertions(+), 564 deletions(-) diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py index a5ce90ea9..d80b3d6ea 100644 --- a/backend/ebios_rm/migrations/0001_initial.py +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.1 on 2024-12-02 18:08 +# Generated by Django 5.1.1 on 2024-12-02 19:12 import django.core.validators import django.db.models.deletion @@ -9,614 +9,168 @@ class Migration(migrations.Migration): + initial = True dependencies = [ - ("core", "0044_qualification"), - ("iam", "0009_create_allauth_emailaddress_objects"), - ("tprm", "0003_entityassessment_representatives"), + ('core', '0044_qualification'), + ('iam', '0009_create_allauth_emailaddress_objects'), + ('tprm', '0003_entityassessment_representatives'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name="EbiosRMStudy", + name='EbiosRMStudy', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("name", models.CharField(max_length=200, verbose_name="Name")), - ( - "description", - models.TextField(blank=True, null=True, verbose_name="Description"), - ), - ("eta", models.DateField(blank=True, null=True, verbose_name="ETA")), - ( - "due_date", - models.DateField(blank=True, null=True, verbose_name="Due date"), - ), - ("ref_id", models.CharField(max_length=100)), - ( - "version", - models.CharField( - blank=True, - default="1.0", - help_text="Version of the Ebios RM study (eg. 1.0, 2.0, etc.)", - max_length=100, - null=True, - verbose_name="Version", - ), - ), - ( - "status", - models.CharField( - blank=True, - choices=[ - ("planned", "Planned"), - ("in_progress", "In progress"), - ("in_review", "In review"), - ("done", "Done"), - ("deprecated", "Deprecated"), - ], - default="planned", - max_length=100, - null=True, - verbose_name="Status", - ), - ), - ( - "observation", - models.TextField(blank=True, null=True, verbose_name="Observation"), - ), - ( - "assets", - models.ManyToManyField( - help_text="Assets that are pertinent to the study", - related_name="ebios_rm_studies", - to="core.asset", - verbose_name="Assets", - ), - ), - ( - "authors", - models.ManyToManyField( - blank=True, - related_name="authors", - to=settings.AUTH_USER_MODEL, - verbose_name="Authors", - ), - ), - ( - "compliance_assessments", - models.ManyToManyField( - help_text="Compliance assessments established as security baseline during workshop 1.4", - related_name="ebios_rm_studies", - to="core.complianceassessment", - verbose_name="Compliance assessments", - ), - ), - ( - "folder", - models.ForeignKey( - default=iam.models.Folder.get_root_folder_id, - on_delete=django.db.models.deletion.CASCADE, - related_name="%(class)s_folder", - to="iam.folder", - ), - ), - ( - "reviewers", - models.ManyToManyField( - blank=True, - related_name="reviewers", - to=settings.AUTH_USER_MODEL, - verbose_name="Reviewers", - ), - ), - ( - "risk_assessments", - models.ManyToManyField( - help_text="Risk assessments generated at the end of workshop 4", - related_name="ebios_rm_studies", - to="core.riskassessment", - verbose_name="Risk assessments", - ), - ), - ( - "risk_matrix", - models.ForeignKey( - help_text="Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`", - on_delete=django.db.models.deletion.PROTECT, - related_name="ebios_rm_studies", - to="core.riskmatrix", - verbose_name="Risk matrix", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('name', models.CharField(max_length=200, verbose_name='Name')), + ('description', models.TextField(blank=True, null=True, verbose_name='Description')), + ('eta', models.DateField(blank=True, null=True, verbose_name='ETA')), + ('due_date', models.DateField(blank=True, null=True, verbose_name='Due date')), + ('ref_id', models.CharField(max_length=100)), + ('version', models.CharField(blank=True, default='1.0', help_text='Version of the Ebios RM study (eg. 1.0, 2.0, etc.)', max_length=100, null=True, verbose_name='Version')), + ('status', models.CharField(blank=True, choices=[('planned', 'Planned'), ('in_progress', 'In progress'), ('in_review', 'In review'), ('done', 'Done'), ('deprecated', 'Deprecated')], default='planned', max_length=100, null=True, verbose_name='Status')), + ('observation', models.TextField(blank=True, null=True, verbose_name='Observation')), + ('assets', models.ManyToManyField(help_text='Assets that are pertinent to the study', related_name='ebios_rm_studies', to='core.asset', verbose_name='Assets')), + ('authors', models.ManyToManyField(blank=True, related_name='authors', to=settings.AUTH_USER_MODEL, verbose_name='Authors')), + ('compliance_assessments', models.ManyToManyField(help_text='Compliance assessments established as security baseline during workshop 1.4', related_name='ebios_rm_studies', to='core.complianceassessment', verbose_name='Compliance assessments')), + ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), + ('reviewers', models.ManyToManyField(blank=True, related_name='reviewers', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), + ('risk_assessments', models.ManyToManyField(help_text='Risk assessments generated at the end of workshop 4', related_name='ebios_rm_studies', to='core.riskassessment', verbose_name='Risk assessments')), + ('risk_matrix', models.ForeignKey(help_text='Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`', on_delete=django.db.models.deletion.PROTECT, related_name='ebios_rm_studies', to='core.riskmatrix', verbose_name='Risk matrix')), ], options={ - "verbose_name": "Ebios RM Study", - "verbose_name_plural": "Ebios RM Studies", - "ordering": ["created_at"], + 'verbose_name': 'Ebios RM Study', + 'verbose_name_plural': 'Ebios RM Studies', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="AttackPath", + name='AttackPath', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("description", models.TextField(verbose_name="Description")), - ("is_selected", models.BooleanField(verbose_name="Is selected")), - ("justification", models.TextField(verbose_name="Justification")), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('description', models.TextField(verbose_name='Description')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), ], options={ - "verbose_name": "Attack path", - "verbose_name_plural": "Attack paths", - "ordering": ["created_at"], + 'verbose_name': 'Attack path', + 'verbose_name_plural': 'Attack paths', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="FearedEvent", + name='FearedEvent', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("name", models.CharField(max_length=200, verbose_name="Name")), - ( - "description", - models.TextField(blank=True, null=True, verbose_name="Description"), - ), - ("ref_id", models.CharField(max_length=100)), - ("gravity", models.PositiveSmallIntegerField(verbose_name="Gravity")), - ("is_selected", models.BooleanField(verbose_name="Is selected")), - ("justification", models.TextField(verbose_name="Justification")), - ( - "assets", - models.ManyToManyField( - help_text="Assets that are affected by the feared event", - related_name="feared_events", - to="core.asset", - verbose_name="Assets", - ), - ), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), - ( - "qualifications", - models.ManyToManyField( - help_text="Qualifications carried by the feared event", - related_name="feared_events", - to="core.qualification", - verbose_name="Qualifications", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('name', models.CharField(max_length=200, verbose_name='Name')), + ('description', models.TextField(blank=True, null=True, verbose_name='Description')), + ('ref_id', models.CharField(max_length=100)), + ('gravity', models.PositiveSmallIntegerField(default=0, verbose_name='Gravity')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('assets', models.ManyToManyField(help_text='Assets that are affected by the feared event', related_name='feared_events', to='core.asset', verbose_name='Assets')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('qualifications', models.ManyToManyField(help_text='Qualifications carried by the feared event', related_name='feared_events', to='core.qualification', verbose_name='Qualifications')), ], options={ - "verbose_name": "Feared event", - "verbose_name_plural": "Feared events", - "ordering": ["created_at"], + 'verbose_name': 'Feared event', + 'verbose_name_plural': 'Feared events', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="OperationalScenario", + name='OperationalScenario', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("description", models.TextField(verbose_name="Description")), - ( - "likelihood", - models.SmallIntegerField(default=-1, verbose_name="Likelihood"), - ), - ("is_selected", models.BooleanField(verbose_name="Is selected")), - ("justification", models.TextField(verbose_name="Justification")), - ( - "attack_paths", - models.ManyToManyField( - help_text="Attack paths that are pertinent to the operational scenario", - related_name="operational_scenarios", - to="ebios_rm.attackpath", - verbose_name="Attack paths", - ), - ), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="operational_scenarios", - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), - ( - "threats", - models.ManyToManyField( - blank=True, - help_text="Threats leveraged by the operational scenario", - related_name="operational_scenarios", - to="core.threat", - verbose_name="Threats", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('description', models.TextField(verbose_name='Description')), + ('likelihood', models.SmallIntegerField(default=-1, verbose_name='Likelihood')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('attack_paths', models.ManyToManyField(help_text='Attack paths that are pertinent to the operational scenario', related_name='operational_scenarios', to='ebios_rm.attackpath', verbose_name='Attack paths')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='operational_scenarios', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('threats', models.ManyToManyField(blank=True, help_text='Threats leveraged by the operational scenario', related_name='operational_scenarios', to='core.threat', verbose_name='Threats')), ], options={ - "verbose_name": "Operational scenario", - "verbose_name_plural": "Operational scenarios", - "ordering": ["created_at"], + 'verbose_name': 'Operational scenario', + 'verbose_name_plural': 'Operational scenarios', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="ROTO", + name='ROTO', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ( - "risk_origin", - models.CharField( - choices=[ - ("state", "State"), - ("organized_crime", "Organized crime"), - ("terrorist", "Terrorist"), - ("activist", "Activist"), - ("professional", "Professional"), - ("amateur", "Amateur"), - ("avenger", "Avenger"), - ("pathological", "Pathological"), - ], - max_length=32, - verbose_name="Risk origin", - ), - ), - ( - "target_objective", - models.CharField(max_length=200, verbose_name="Target objective"), - ), - ( - "motivation", - models.PositiveSmallIntegerField( - choices=[ - (0, "undefined"), - (1, "very_low"), - (2, "low"), - (3, "significant"), - (4, "strong"), - ], - default=0, - verbose_name="Motivation", - ), - ), - ( - "resources", - models.PositiveSmallIntegerField( - choices=[ - (0, "undefined"), - (1, "limited"), - (2, "significant"), - (3, "important"), - (4, "unlimited"), - ], - default=0, - verbose_name="Resources", - ), - ), - ( - "pertinence", - models.PositiveSmallIntegerField( - choices=[ - (0, "undefined"), - (1, "irrelevant"), - (2, "partially_relevant"), - (3, "fairly_relevant"), - (4, "highly_relevant"), - ], - default=0, - verbose_name="Pertinence", - ), - ), - ( - "activity", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Activity", - ), - ), - ("is_selected", models.BooleanField(verbose_name="Is selected")), - ("justification", models.TextField(verbose_name="Justification")), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), - ( - "feared_events", - models.ManyToManyField( - related_name="ro_to_couples", - to="ebios_rm.fearedevent", - verbose_name="Feared events", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('risk_origin', models.CharField(choices=[('state', 'State'), ('organized_crime', 'Organized crime'), ('terrorist', 'Terrorist'), ('activist', 'Activist'), ('professional', 'Professional'), ('amateur', 'Amateur'), ('avenger', 'Avenger'), ('pathological', 'Pathological')], max_length=32, verbose_name='Risk origin')), + ('target_objective', models.CharField(max_length=200, verbose_name='Target objective')), + ('motivation', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'very_low'), (2, 'low'), (3, 'significant'), (4, 'strong')], default=0, verbose_name='Motivation')), + ('resources', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'limited'), (2, 'significant'), (3, 'important'), (4, 'unlimited')], default=0, verbose_name='Resources')), + ('pertinence', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'irrelevant'), (2, 'partially_relevant'), (3, 'fairly_relevant'), (4, 'highly_relevant')], default=0, verbose_name='Pertinence')), + ('activity', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Activity')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('feared_events', models.ManyToManyField(related_name='ro_to_couples', to='ebios_rm.fearedevent', verbose_name='Feared events')), ], options={ - "verbose_name": "RO/TO couple", - "verbose_name_plural": "RO/TO couples", - "ordering": ["created_at"], + 'verbose_name': 'RO/TO couple', + 'verbose_name_plural': 'RO/TO couples', + 'ordering': ['created_at'], }, ), migrations.AddField( - model_name="attackpath", - name="ro_to_couple", - field=models.ForeignKey( - help_text="RO/TO couple from which the attach path is derived", - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.roto", - verbose_name="RO/TO couple", - ), + model_name='attackpath', + name='ro_to_couple', + field=models.ForeignKey(help_text='RO/TO couple from which the attach path is derived', on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.roto', verbose_name='RO/TO couple'), ), migrations.CreateModel( - name="Stakeholder", + name='Stakeholder', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ( - "category", - models.CharField( - choices=[ - ("client", "Client"), - ("partner", "Partner"), - ("supplier", "Supplier"), - ], - max_length=32, - verbose_name="Category", - ), - ), - ( - "current_dependency", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Current dependency", - ), - ), - ( - "current_penetration", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Current penetration", - ), - ), - ( - "current_maturity", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Current maturity", - ), - ), - ( - "current_trust", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Current trust", - ), - ), - ( - "residual_dependency", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Residual dependency", - ), - ), - ( - "residual_penetration", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Residual penetration", - ), - ), - ( - "residual_maturity", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Residual maturity", - ), - ), - ( - "residual_trust", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Residual trust", - ), - ), - ("is_selected", models.BooleanField(verbose_name="Is selected")), - ("justification", models.TextField(verbose_name="Justification")), - ( - "applied_controls", - models.ManyToManyField( - blank=True, - help_text="Controls applied to lower stakeholder criticality", - related_name="stakeholders", - to="core.appliedcontrol", - verbose_name="Applied controls", - ), - ), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), - ( - "entity", - models.ForeignKey( - help_text="Entity qualified by the stakeholder", - on_delete=django.db.models.deletion.CASCADE, - to="tprm.entity", - verbose_name="Entity", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('category', models.CharField(choices=[('client', 'Client'), ('partner', 'Partner'), ('supplier', 'Supplier')], max_length=32, verbose_name='Category')), + ('current_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current dependency')), + ('current_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current penetration')), + ('current_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current maturity')), + ('current_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current trust')), + ('residual_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual dependency')), + ('residual_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual penetration')), + ('residual_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual maturity')), + ('residual_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual trust')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('applied_controls', models.ManyToManyField(blank=True, help_text='Controls applied to lower stakeholder criticality', related_name='stakeholders', to='core.appliedcontrol', verbose_name='Applied controls')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('entity', models.ForeignKey(help_text='Entity qualified by the stakeholder', on_delete=django.db.models.deletion.CASCADE, to='tprm.entity', verbose_name='Entity')), ], options={ - "verbose_name": "Stakeholder", - "verbose_name_plural": "Stakeholders", - "ordering": ["created_at"], + 'verbose_name': 'Stakeholder', + 'verbose_name_plural': 'Stakeholders', + 'ordering': ['created_at'], }, ), migrations.AddField( - model_name="attackpath", - name="stakeholders", - field=models.ManyToManyField( - help_text="Stakeholders leveraged by the attack path", - related_name="attack_paths", - to="ebios_rm.stakeholder", - verbose_name="Stakeholders", - ), + model_name='attackpath', + name='stakeholders', + field=models.ManyToManyField(help_text='Stakeholders leveraged by the attack path', related_name='attack_paths', to='ebios_rm.stakeholder', verbose_name='Stakeholders'), ), ] From 7b87017759053e8158fc162fe437f3aa376455e4 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:44:03 +0100 Subject: [PATCH 23/34] Write basic unit tests for RO/TO model --- backend/ebios_rm/tests/test_ro_to.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backend/ebios_rm/tests/test_ro_to.py diff --git a/backend/ebios_rm/tests/test_ro_to.py b/backend/ebios_rm/tests/test_ro_to.py new file mode 100644 index 000000000..295888700 --- /dev/null +++ b/backend/ebios_rm/tests/test_ro_to.py @@ -0,0 +1,28 @@ +import pytest +from ebios_rm.models import EbiosRMStudy, FearedEvent, ROTO + +from ebios_rm.tests.fixtures import * + + +@pytest.mark.django_db +class TestROTO: + @pytest.mark.usefixtures( + "basic_ebios_rm_study_fixture", "basic_feared_event_fixture" + ) + def test_create_roto_basic(self): + roto = ROTO.objects.create( + risk_origin=ROTO.RiskOrigin.STATE, + target_objective="test target objectives", + ebios_rm_study=EbiosRMStudy.objects.get(name="test study"), + ) + roto.feared_events.set(FearedEvent.objects.filter(name="test feared event")) + + assert roto.risk_origin == "state" + assert roto.target_objective == "test target objectives" + + assert roto.feared_events.count() == 1 + assert roto.feared_events.filter(name="test feared event").exists() + assert ( + roto.ebios_rm_study + == FearedEvent.objects.get(name="test feared event").ebios_rm_study + ) From 40b17215e1344cf8618fe3f28d760a0bbb5f31a8 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:45:34 +0100 Subject: [PATCH 24/34] Make Stakeholder.ebios_rm_studies a ManyToMany field --- backend/ebios_rm/migrations/0001_initial.py | 4 ++-- backend/ebios_rm/models.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py index d80b3d6ea..87eca392b 100644 --- a/backend/ebios_rm/migrations/0001_initial.py +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.1 on 2024-12-02 19:12 +# Generated by Django 5.1.1 on 2024-12-02 19:45 import django.core.validators import django.db.models.deletion @@ -159,7 +159,7 @@ class Migration(migrations.Migration): ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), ('justification', models.TextField(blank=True, verbose_name='Justification')), ('applied_controls', models.ManyToManyField(blank=True, help_text='Controls applied to lower stakeholder criticality', related_name='stakeholders', to='core.appliedcontrol', verbose_name='Applied controls')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('ebios_rm_studies', models.ManyToManyField(help_text='EBIOS RM studies in which the stakeholder is involved', related_name='stakeholders', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM studies')), ('entity', models.ForeignKey(help_text='Entity qualified by the stakeholder', on_delete=django.db.models.deletion.CASCADE, to='tprm.entity', verbose_name='Entity')), ], options={ diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index 696397f04..e30a9a2c7 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -200,10 +200,11 @@ class Category(models.TextChoices): PARTNER = "partner", _("Partner") SUPPLIER = "supplier", _("Supplier") - ebios_rm_study = models.ForeignKey( + ebios_rm_studies = models.ManyToManyField( EbiosRMStudy, - verbose_name=_("EBIOS RM study"), - on_delete=models.CASCADE, + verbose_name=_("EBIOS RM studies"), + related_name="stakeholders", + help_text=_("EBIOS RM studies in which the stakeholder is involved"), ) entity = models.ForeignKey( Entity, From 4cf70f015099bfe1124fb72e691d89d066b4bc43 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:54:07 +0100 Subject: [PATCH 25/34] Write basic unit tests for Stakeholder model --- backend/ebios_rm/tests/fixtures.py | 13 +++++++++- backend/ebios_rm/tests/test_stakeholder.py | 28 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 backend/ebios_rm/tests/test_stakeholder.py diff --git a/backend/ebios_rm/tests/fixtures.py b/backend/ebios_rm/tests/fixtures.py index b166b27b1..5098f0667 100644 --- a/backend/ebios_rm/tests/fixtures.py +++ b/backend/ebios_rm/tests/fixtures.py @@ -1,7 +1,7 @@ import pytest from core.models import RiskMatrix, StoredLibrary, Asset -from ebios_rm.models import EbiosRMStudy, FearedEvent +from ebios_rm.models import ROTO, EbiosRMStudy, FearedEvent @pytest.fixture @@ -47,3 +47,14 @@ def basic_feared_event_fixture(basic_ebios_rm_study_fixture): ) asset = Asset.objects.get(name="Primary Asset 1") feared_event.assets.add(asset) + + +@pytest.fixture +def basic_roto_fixture(basic_ebios_rm_study_fixture, basic_feared_event_fixture): + roto = ROTO.objects.create( + risk_origin=ROTO.RiskOrigin.STATE, + target_objective="test target objectives", + ebios_rm_study=basic_ebios_rm_study_fixture, + ) + roto.feared_events.set(FearedEvent.objects.filter(name="test feared event")) + return roto diff --git a/backend/ebios_rm/tests/test_stakeholder.py b/backend/ebios_rm/tests/test_stakeholder.py new file mode 100644 index 000000000..e77c30415 --- /dev/null +++ b/backend/ebios_rm/tests/test_stakeholder.py @@ -0,0 +1,28 @@ +import pytest +from ebios_rm.models import EbiosRMStudy, FearedEvent, ROTO, Stakeholder + +from tprm.models import Entity + +from ebios_rm.tests.fixtures import * + + +@pytest.mark.django_db +class TestStakeholder: + @pytest.mark.usefixtures( + "basic_ebios_rm_study_fixture", + ) + def test_create_stakeholder_basic(self): + study = EbiosRMStudy.objects.get(name="test study") + entity = Entity.objects.create(name="Entity") + stakeholder = Stakeholder.objects.create( + entity=entity, + category=Stakeholder.Category.SUPPLIER, + ) + stakeholder.ebios_rm_studies.add(study) + + assert stakeholder in study.stakeholders.all() + assert stakeholder.entity == entity + assert stakeholder.category == Stakeholder.Category.SUPPLIER + + assert stakeholder.current_criticality == 0 + assert stakeholder.residual_criticality == 0 From 5e48634a0def3eac8808c3c6a294eca0e1ee51c5 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 20:56:04 +0100 Subject: [PATCH 26/34] chore: ruff format --- backend/ebios_rm/migrations/0001_initial.py | 714 ++++++++++++++++---- 1 file changed, 597 insertions(+), 117 deletions(-) diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py index 87eca392b..21643211d 100644 --- a/backend/ebios_rm/migrations/0001_initial.py +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -9,168 +9,648 @@ class Migration(migrations.Migration): - initial = True dependencies = [ - ('core', '0044_qualification'), - ('iam', '0009_create_allauth_emailaddress_objects'), - ('tprm', '0003_entityassessment_representatives'), + ("core", "0044_qualification"), + ("iam", "0009_create_allauth_emailaddress_objects"), + ("tprm", "0003_entityassessment_representatives"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='EbiosRMStudy', + name="EbiosRMStudy", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('name', models.CharField(max_length=200, verbose_name='Name')), - ('description', models.TextField(blank=True, null=True, verbose_name='Description')), - ('eta', models.DateField(blank=True, null=True, verbose_name='ETA')), - ('due_date', models.DateField(blank=True, null=True, verbose_name='Due date')), - ('ref_id', models.CharField(max_length=100)), - ('version', models.CharField(blank=True, default='1.0', help_text='Version of the Ebios RM study (eg. 1.0, 2.0, etc.)', max_length=100, null=True, verbose_name='Version')), - ('status', models.CharField(blank=True, choices=[('planned', 'Planned'), ('in_progress', 'In progress'), ('in_review', 'In review'), ('done', 'Done'), ('deprecated', 'Deprecated')], default='planned', max_length=100, null=True, verbose_name='Status')), - ('observation', models.TextField(blank=True, null=True, verbose_name='Observation')), - ('assets', models.ManyToManyField(help_text='Assets that are pertinent to the study', related_name='ebios_rm_studies', to='core.asset', verbose_name='Assets')), - ('authors', models.ManyToManyField(blank=True, related_name='authors', to=settings.AUTH_USER_MODEL, verbose_name='Authors')), - ('compliance_assessments', models.ManyToManyField(help_text='Compliance assessments established as security baseline during workshop 1.4', related_name='ebios_rm_studies', to='core.complianceassessment', verbose_name='Compliance assessments')), - ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), - ('reviewers', models.ManyToManyField(blank=True, related_name='reviewers', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), - ('risk_assessments', models.ManyToManyField(help_text='Risk assessments generated at the end of workshop 4', related_name='ebios_rm_studies', to='core.riskassessment', verbose_name='Risk assessments')), - ('risk_matrix', models.ForeignKey(help_text='Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`', on_delete=django.db.models.deletion.PROTECT, related_name='ebios_rm_studies', to='core.riskmatrix', verbose_name='Risk matrix')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("name", models.CharField(max_length=200, verbose_name="Name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ("eta", models.DateField(blank=True, null=True, verbose_name="ETA")), + ( + "due_date", + models.DateField(blank=True, null=True, verbose_name="Due date"), + ), + ("ref_id", models.CharField(max_length=100)), + ( + "version", + models.CharField( + blank=True, + default="1.0", + help_text="Version of the Ebios RM study (eg. 1.0, 2.0, etc.)", + max_length=100, + null=True, + verbose_name="Version", + ), + ), + ( + "status", + models.CharField( + blank=True, + choices=[ + ("planned", "Planned"), + ("in_progress", "In progress"), + ("in_review", "In review"), + ("done", "Done"), + ("deprecated", "Deprecated"), + ], + default="planned", + max_length=100, + null=True, + verbose_name="Status", + ), + ), + ( + "observation", + models.TextField(blank=True, null=True, verbose_name="Observation"), + ), + ( + "assets", + models.ManyToManyField( + help_text="Assets that are pertinent to the study", + related_name="ebios_rm_studies", + to="core.asset", + verbose_name="Assets", + ), + ), + ( + "authors", + models.ManyToManyField( + blank=True, + related_name="authors", + to=settings.AUTH_USER_MODEL, + verbose_name="Authors", + ), + ), + ( + "compliance_assessments", + models.ManyToManyField( + help_text="Compliance assessments established as security baseline during workshop 1.4", + related_name="ebios_rm_studies", + to="core.complianceassessment", + verbose_name="Compliance assessments", + ), + ), + ( + "folder", + models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + ( + "reviewers", + models.ManyToManyField( + blank=True, + related_name="reviewers", + to=settings.AUTH_USER_MODEL, + verbose_name="Reviewers", + ), + ), + ( + "risk_assessments", + models.ManyToManyField( + help_text="Risk assessments generated at the end of workshop 4", + related_name="ebios_rm_studies", + to="core.riskassessment", + verbose_name="Risk assessments", + ), + ), + ( + "risk_matrix", + models.ForeignKey( + help_text="Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`", + on_delete=django.db.models.deletion.PROTECT, + related_name="ebios_rm_studies", + to="core.riskmatrix", + verbose_name="Risk matrix", + ), + ), ], options={ - 'verbose_name': 'Ebios RM Study', - 'verbose_name_plural': 'Ebios RM Studies', - 'ordering': ['created_at'], + "verbose_name": "Ebios RM Study", + "verbose_name_plural": "Ebios RM Studies", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='AttackPath', + name="AttackPath", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('description', models.TextField(verbose_name='Description')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("description", models.TextField(verbose_name="Description")), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), ], options={ - 'verbose_name': 'Attack path', - 'verbose_name_plural': 'Attack paths', - 'ordering': ['created_at'], + "verbose_name": "Attack path", + "verbose_name_plural": "Attack paths", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='FearedEvent', + name="FearedEvent", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('name', models.CharField(max_length=200, verbose_name='Name')), - ('description', models.TextField(blank=True, null=True, verbose_name='Description')), - ('ref_id', models.CharField(max_length=100)), - ('gravity', models.PositiveSmallIntegerField(default=0, verbose_name='Gravity')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('assets', models.ManyToManyField(help_text='Assets that are affected by the feared event', related_name='feared_events', to='core.asset', verbose_name='Assets')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), - ('qualifications', models.ManyToManyField(help_text='Qualifications carried by the feared event', related_name='feared_events', to='core.qualification', verbose_name='Qualifications')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("name", models.CharField(max_length=200, verbose_name="Name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ("ref_id", models.CharField(max_length=100)), + ( + "gravity", + models.PositiveSmallIntegerField(default=0, verbose_name="Gravity"), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "assets", + models.ManyToManyField( + help_text="Assets that are affected by the feared event", + related_name="feared_events", + to="core.asset", + verbose_name="Assets", + ), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "qualifications", + models.ManyToManyField( + help_text="Qualifications carried by the feared event", + related_name="feared_events", + to="core.qualification", + verbose_name="Qualifications", + ), + ), ], options={ - 'verbose_name': 'Feared event', - 'verbose_name_plural': 'Feared events', - 'ordering': ['created_at'], + "verbose_name": "Feared event", + "verbose_name_plural": "Feared events", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='OperationalScenario', + name="OperationalScenario", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('description', models.TextField(verbose_name='Description')), - ('likelihood', models.SmallIntegerField(default=-1, verbose_name='Likelihood')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('attack_paths', models.ManyToManyField(help_text='Attack paths that are pertinent to the operational scenario', related_name='operational_scenarios', to='ebios_rm.attackpath', verbose_name='Attack paths')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='operational_scenarios', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), - ('threats', models.ManyToManyField(blank=True, help_text='Threats leveraged by the operational scenario', related_name='operational_scenarios', to='core.threat', verbose_name='Threats')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("description", models.TextField(verbose_name="Description")), + ( + "likelihood", + models.SmallIntegerField(default=-1, verbose_name="Likelihood"), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "attack_paths", + models.ManyToManyField( + help_text="Attack paths that are pertinent to the operational scenario", + related_name="operational_scenarios", + to="ebios_rm.attackpath", + verbose_name="Attack paths", + ), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="operational_scenarios", + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "threats", + models.ManyToManyField( + blank=True, + help_text="Threats leveraged by the operational scenario", + related_name="operational_scenarios", + to="core.threat", + verbose_name="Threats", + ), + ), ], options={ - 'verbose_name': 'Operational scenario', - 'verbose_name_plural': 'Operational scenarios', - 'ordering': ['created_at'], + "verbose_name": "Operational scenario", + "verbose_name_plural": "Operational scenarios", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='ROTO', + name="ROTO", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('risk_origin', models.CharField(choices=[('state', 'State'), ('organized_crime', 'Organized crime'), ('terrorist', 'Terrorist'), ('activist', 'Activist'), ('professional', 'Professional'), ('amateur', 'Amateur'), ('avenger', 'Avenger'), ('pathological', 'Pathological')], max_length=32, verbose_name='Risk origin')), - ('target_objective', models.CharField(max_length=200, verbose_name='Target objective')), - ('motivation', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'very_low'), (2, 'low'), (3, 'significant'), (4, 'strong')], default=0, verbose_name='Motivation')), - ('resources', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'limited'), (2, 'significant'), (3, 'important'), (4, 'unlimited')], default=0, verbose_name='Resources')), - ('pertinence', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'irrelevant'), (2, 'partially_relevant'), (3, 'fairly_relevant'), (4, 'highly_relevant')], default=0, verbose_name='Pertinence')), - ('activity', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Activity')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), - ('feared_events', models.ManyToManyField(related_name='ro_to_couples', to='ebios_rm.fearedevent', verbose_name='Feared events')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ( + "risk_origin", + models.CharField( + choices=[ + ("state", "State"), + ("organized_crime", "Organized crime"), + ("terrorist", "Terrorist"), + ("activist", "Activist"), + ("professional", "Professional"), + ("amateur", "Amateur"), + ("avenger", "Avenger"), + ("pathological", "Pathological"), + ], + max_length=32, + verbose_name="Risk origin", + ), + ), + ( + "target_objective", + models.CharField(max_length=200, verbose_name="Target objective"), + ), + ( + "motivation", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "very_low"), + (2, "low"), + (3, "significant"), + (4, "strong"), + ], + default=0, + verbose_name="Motivation", + ), + ), + ( + "resources", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "limited"), + (2, "significant"), + (3, "important"), + (4, "unlimited"), + ], + default=0, + verbose_name="Resources", + ), + ), + ( + "pertinence", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "irrelevant"), + (2, "partially_relevant"), + (3, "fairly_relevant"), + (4, "highly_relevant"), + ], + default=0, + verbose_name="Pertinence", + ), + ), + ( + "activity", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Activity", + ), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "feared_events", + models.ManyToManyField( + related_name="ro_to_couples", + to="ebios_rm.fearedevent", + verbose_name="Feared events", + ), + ), ], options={ - 'verbose_name': 'RO/TO couple', - 'verbose_name_plural': 'RO/TO couples', - 'ordering': ['created_at'], + "verbose_name": "RO/TO couple", + "verbose_name_plural": "RO/TO couples", + "ordering": ["created_at"], }, ), migrations.AddField( - model_name='attackpath', - name='ro_to_couple', - field=models.ForeignKey(help_text='RO/TO couple from which the attach path is derived', on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.roto', verbose_name='RO/TO couple'), + model_name="attackpath", + name="ro_to_couple", + field=models.ForeignKey( + help_text="RO/TO couple from which the attach path is derived", + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.roto", + verbose_name="RO/TO couple", + ), ), migrations.CreateModel( - name='Stakeholder', + name="Stakeholder", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('category', models.CharField(choices=[('client', 'Client'), ('partner', 'Partner'), ('supplier', 'Supplier')], max_length=32, verbose_name='Category')), - ('current_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current dependency')), - ('current_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current penetration')), - ('current_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current maturity')), - ('current_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current trust')), - ('residual_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual dependency')), - ('residual_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual penetration')), - ('residual_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual maturity')), - ('residual_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual trust')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('applied_controls', models.ManyToManyField(blank=True, help_text='Controls applied to lower stakeholder criticality', related_name='stakeholders', to='core.appliedcontrol', verbose_name='Applied controls')), - ('ebios_rm_studies', models.ManyToManyField(help_text='EBIOS RM studies in which the stakeholder is involved', related_name='stakeholders', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM studies')), - ('entity', models.ForeignKey(help_text='Entity qualified by the stakeholder', on_delete=django.db.models.deletion.CASCADE, to='tprm.entity', verbose_name='Entity')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ( + "category", + models.CharField( + choices=[ + ("client", "Client"), + ("partner", "Partner"), + ("supplier", "Supplier"), + ], + max_length=32, + verbose_name="Category", + ), + ), + ( + "current_dependency", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Current dependency", + ), + ), + ( + "current_penetration", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Current penetration", + ), + ), + ( + "current_maturity", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Current maturity", + ), + ), + ( + "current_trust", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Current trust", + ), + ), + ( + "residual_dependency", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Residual dependency", + ), + ), + ( + "residual_penetration", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Residual penetration", + ), + ), + ( + "residual_maturity", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Residual maturity", + ), + ), + ( + "residual_trust", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Residual trust", + ), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "applied_controls", + models.ManyToManyField( + blank=True, + help_text="Controls applied to lower stakeholder criticality", + related_name="stakeholders", + to="core.appliedcontrol", + verbose_name="Applied controls", + ), + ), + ( + "ebios_rm_studies", + models.ManyToManyField( + help_text="EBIOS RM studies in which the stakeholder is involved", + related_name="stakeholders", + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM studies", + ), + ), + ( + "entity", + models.ForeignKey( + help_text="Entity qualified by the stakeholder", + on_delete=django.db.models.deletion.CASCADE, + to="tprm.entity", + verbose_name="Entity", + ), + ), ], options={ - 'verbose_name': 'Stakeholder', - 'verbose_name_plural': 'Stakeholders', - 'ordering': ['created_at'], + "verbose_name": "Stakeholder", + "verbose_name_plural": "Stakeholders", + "ordering": ["created_at"], }, ), migrations.AddField( - model_name='attackpath', - name='stakeholders', - field=models.ManyToManyField(help_text='Stakeholders leveraged by the attack path', related_name='attack_paths', to='ebios_rm.stakeholder', verbose_name='Stakeholders'), + model_name="attackpath", + name="stakeholders", + field=models.ManyToManyField( + help_text="Stakeholders leveraged by the attack path", + related_name="attack_paths", + to="ebios_rm.stakeholder", + verbose_name="Stakeholders", + ), ), ] From a9044995867b6a39ef6fecf671b5db1bded1f088 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 3 Dec 2024 11:16:50 +0100 Subject: [PATCH 27/34] Add reference_entity field to EbiosRMStudy model --- backend/ebios_rm/models.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index e30a9a2c7..de83194c6 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -52,6 +52,14 @@ class Status(models.TextChoices): related_name="ebios_rm_studies", help_text=_("Risk assessments generated at the end of workshop 4"), ) + reference_entity = models.ForeignKey( + Entity, + on_delete=models.PROTECT, + verbose_name=_("Reference entity"), + related_name="ebios_rm_studies", + help_text=_("Entity that is the focus of the study"), + default=Entity.get_main_entity, + ) ref_id = models.CharField(max_length=100) version = models.CharField( @@ -120,7 +128,7 @@ class Meta: ordering = ["created_at"] -class ROTO(AbstractBaseModel): +class RoTo(AbstractBaseModel): class RiskOrigin(models.TextChoices): STATE = "state", _("State") ORGANIZED_CRIME = "organized_crime", _("Organized crime") @@ -308,7 +316,7 @@ class AttackPath(AbstractBaseModel): on_delete=models.CASCADE, ) ro_to_couple = models.ForeignKey( - ROTO, + RoTo, verbose_name=_("RO/TO couple"), on_delete=models.CASCADE, help_text=_("RO/TO couple from which the attach path is derived"), From 379e7e72913ae4446ac169e577caf8b665d5781c Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 3 Dec 2024 11:49:12 +0100 Subject: [PATCH 28/34] Rename ROTO model to RoTo --- backend/ebios_rm/migrations/0001_initial.py | 718 ++++---------------- backend/ebios_rm/tests/fixtures.py | 6 +- backend/ebios_rm/tests/test_ro_to.py | 8 +- backend/ebios_rm/tests/test_stakeholder.py | 2 +- 4 files changed, 128 insertions(+), 606 deletions(-) diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py index 21643211d..8df3d1762 100644 --- a/backend/ebios_rm/migrations/0001_initial.py +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -1,656 +1,178 @@ -# Generated by Django 5.1.1 on 2024-12-02 19:45 +# Generated by Django 5.1.1 on 2024-12-03 10:46 import django.core.validators import django.db.models.deletion import iam.models +import tprm.models import uuid from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): + initial = True dependencies = [ - ("core", "0044_qualification"), - ("iam", "0009_create_allauth_emailaddress_objects"), - ("tprm", "0003_entityassessment_representatives"), + ('core', '0044_qualification'), + ('iam', '0009_create_allauth_emailaddress_objects'), + ('tprm', '0003_entityassessment_representatives'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name="EbiosRMStudy", + name='EbiosRMStudy', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("name", models.CharField(max_length=200, verbose_name="Name")), - ( - "description", - models.TextField(blank=True, null=True, verbose_name="Description"), - ), - ("eta", models.DateField(blank=True, null=True, verbose_name="ETA")), - ( - "due_date", - models.DateField(blank=True, null=True, verbose_name="Due date"), - ), - ("ref_id", models.CharField(max_length=100)), - ( - "version", - models.CharField( - blank=True, - default="1.0", - help_text="Version of the Ebios RM study (eg. 1.0, 2.0, etc.)", - max_length=100, - null=True, - verbose_name="Version", - ), - ), - ( - "status", - models.CharField( - blank=True, - choices=[ - ("planned", "Planned"), - ("in_progress", "In progress"), - ("in_review", "In review"), - ("done", "Done"), - ("deprecated", "Deprecated"), - ], - default="planned", - max_length=100, - null=True, - verbose_name="Status", - ), - ), - ( - "observation", - models.TextField(blank=True, null=True, verbose_name="Observation"), - ), - ( - "assets", - models.ManyToManyField( - help_text="Assets that are pertinent to the study", - related_name="ebios_rm_studies", - to="core.asset", - verbose_name="Assets", - ), - ), - ( - "authors", - models.ManyToManyField( - blank=True, - related_name="authors", - to=settings.AUTH_USER_MODEL, - verbose_name="Authors", - ), - ), - ( - "compliance_assessments", - models.ManyToManyField( - help_text="Compliance assessments established as security baseline during workshop 1.4", - related_name="ebios_rm_studies", - to="core.complianceassessment", - verbose_name="Compliance assessments", - ), - ), - ( - "folder", - models.ForeignKey( - default=iam.models.Folder.get_root_folder_id, - on_delete=django.db.models.deletion.CASCADE, - related_name="%(class)s_folder", - to="iam.folder", - ), - ), - ( - "reviewers", - models.ManyToManyField( - blank=True, - related_name="reviewers", - to=settings.AUTH_USER_MODEL, - verbose_name="Reviewers", - ), - ), - ( - "risk_assessments", - models.ManyToManyField( - help_text="Risk assessments generated at the end of workshop 4", - related_name="ebios_rm_studies", - to="core.riskassessment", - verbose_name="Risk assessments", - ), - ), - ( - "risk_matrix", - models.ForeignKey( - help_text="Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`", - on_delete=django.db.models.deletion.PROTECT, - related_name="ebios_rm_studies", - to="core.riskmatrix", - verbose_name="Risk matrix", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('name', models.CharField(max_length=200, verbose_name='Name')), + ('description', models.TextField(blank=True, null=True, verbose_name='Description')), + ('eta', models.DateField(blank=True, null=True, verbose_name='ETA')), + ('due_date', models.DateField(blank=True, null=True, verbose_name='Due date')), + ('ref_id', models.CharField(max_length=100)), + ('version', models.CharField(blank=True, default='1.0', help_text='Version of the Ebios RM study (eg. 1.0, 2.0, etc.)', max_length=100, null=True, verbose_name='Version')), + ('status', models.CharField(blank=True, choices=[('planned', 'Planned'), ('in_progress', 'In progress'), ('in_review', 'In review'), ('done', 'Done'), ('deprecated', 'Deprecated')], default='planned', max_length=100, null=True, verbose_name='Status')), + ('observation', models.TextField(blank=True, null=True, verbose_name='Observation')), + ('assets', models.ManyToManyField(help_text='Assets that are pertinent to the study', related_name='ebios_rm_studies', to='core.asset', verbose_name='Assets')), + ('authors', models.ManyToManyField(blank=True, related_name='authors', to=settings.AUTH_USER_MODEL, verbose_name='Authors')), + ('compliance_assessments', models.ManyToManyField(help_text='Compliance assessments established as security baseline during workshop 1.4', related_name='ebios_rm_studies', to='core.complianceassessment', verbose_name='Compliance assessments')), + ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), + ('reference_entity', models.ForeignKey(default=tprm.models.Entity.get_main_entity, help_text='Entity that is the focus of the study', on_delete=django.db.models.deletion.PROTECT, related_name='ebios_rm_studies', to='tprm.entity', verbose_name='Reference entity')), + ('reviewers', models.ManyToManyField(blank=True, related_name='reviewers', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), + ('risk_assessments', models.ManyToManyField(help_text='Risk assessments generated at the end of workshop 4', related_name='ebios_rm_studies', to='core.riskassessment', verbose_name='Risk assessments')), + ('risk_matrix', models.ForeignKey(help_text='Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`', on_delete=django.db.models.deletion.PROTECT, related_name='ebios_rm_studies', to='core.riskmatrix', verbose_name='Risk matrix')), ], options={ - "verbose_name": "Ebios RM Study", - "verbose_name_plural": "Ebios RM Studies", - "ordering": ["created_at"], + 'verbose_name': 'Ebios RM Study', + 'verbose_name_plural': 'Ebios RM Studies', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="AttackPath", + name='AttackPath', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("description", models.TextField(verbose_name="Description")), - ( - "is_selected", - models.BooleanField(default=False, verbose_name="Is selected"), - ), - ( - "justification", - models.TextField(blank=True, verbose_name="Justification"), - ), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('description', models.TextField(verbose_name='Description')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), ], options={ - "verbose_name": "Attack path", - "verbose_name_plural": "Attack paths", - "ordering": ["created_at"], + 'verbose_name': 'Attack path', + 'verbose_name_plural': 'Attack paths', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="FearedEvent", + name='FearedEvent', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("name", models.CharField(max_length=200, verbose_name="Name")), - ( - "description", - models.TextField(blank=True, null=True, verbose_name="Description"), - ), - ("ref_id", models.CharField(max_length=100)), - ( - "gravity", - models.PositiveSmallIntegerField(default=0, verbose_name="Gravity"), - ), - ( - "is_selected", - models.BooleanField(default=False, verbose_name="Is selected"), - ), - ( - "justification", - models.TextField(blank=True, verbose_name="Justification"), - ), - ( - "assets", - models.ManyToManyField( - help_text="Assets that are affected by the feared event", - related_name="feared_events", - to="core.asset", - verbose_name="Assets", - ), - ), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), - ( - "qualifications", - models.ManyToManyField( - help_text="Qualifications carried by the feared event", - related_name="feared_events", - to="core.qualification", - verbose_name="Qualifications", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('name', models.CharField(max_length=200, verbose_name='Name')), + ('description', models.TextField(blank=True, null=True, verbose_name='Description')), + ('ref_id', models.CharField(max_length=100)), + ('gravity', models.PositiveSmallIntegerField(default=0, verbose_name='Gravity')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('assets', models.ManyToManyField(help_text='Assets that are affected by the feared event', related_name='feared_events', to='core.asset', verbose_name='Assets')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('qualifications', models.ManyToManyField(help_text='Qualifications carried by the feared event', related_name='feared_events', to='core.qualification', verbose_name='Qualifications')), ], options={ - "verbose_name": "Feared event", - "verbose_name_plural": "Feared events", - "ordering": ["created_at"], + 'verbose_name': 'Feared event', + 'verbose_name_plural': 'Feared events', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="OperationalScenario", + name='OperationalScenario', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ("description", models.TextField(verbose_name="Description")), - ( - "likelihood", - models.SmallIntegerField(default=-1, verbose_name="Likelihood"), - ), - ( - "is_selected", - models.BooleanField(default=False, verbose_name="Is selected"), - ), - ( - "justification", - models.TextField(blank=True, verbose_name="Justification"), - ), - ( - "attack_paths", - models.ManyToManyField( - help_text="Attack paths that are pertinent to the operational scenario", - related_name="operational_scenarios", - to="ebios_rm.attackpath", - verbose_name="Attack paths", - ), - ), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="operational_scenarios", - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), - ( - "threats", - models.ManyToManyField( - blank=True, - help_text="Threats leveraged by the operational scenario", - related_name="operational_scenarios", - to="core.threat", - verbose_name="Threats", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('description', models.TextField(verbose_name='Description')), + ('likelihood', models.SmallIntegerField(default=-1, verbose_name='Likelihood')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('attack_paths', models.ManyToManyField(help_text='Attack paths that are pertinent to the operational scenario', related_name='operational_scenarios', to='ebios_rm.attackpath', verbose_name='Attack paths')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='operational_scenarios', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('threats', models.ManyToManyField(blank=True, help_text='Threats leveraged by the operational scenario', related_name='operational_scenarios', to='core.threat', verbose_name='Threats')), ], options={ - "verbose_name": "Operational scenario", - "verbose_name_plural": "Operational scenarios", - "ordering": ["created_at"], + 'verbose_name': 'Operational scenario', + 'verbose_name_plural': 'Operational scenarios', + 'ordering': ['created_at'], }, ), migrations.CreateModel( - name="ROTO", + name='RoTo', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ( - "risk_origin", - models.CharField( - choices=[ - ("state", "State"), - ("organized_crime", "Organized crime"), - ("terrorist", "Terrorist"), - ("activist", "Activist"), - ("professional", "Professional"), - ("amateur", "Amateur"), - ("avenger", "Avenger"), - ("pathological", "Pathological"), - ], - max_length=32, - verbose_name="Risk origin", - ), - ), - ( - "target_objective", - models.CharField(max_length=200, verbose_name="Target objective"), - ), - ( - "motivation", - models.PositiveSmallIntegerField( - choices=[ - (0, "undefined"), - (1, "very_low"), - (2, "low"), - (3, "significant"), - (4, "strong"), - ], - default=0, - verbose_name="Motivation", - ), - ), - ( - "resources", - models.PositiveSmallIntegerField( - choices=[ - (0, "undefined"), - (1, "limited"), - (2, "significant"), - (3, "important"), - (4, "unlimited"), - ], - default=0, - verbose_name="Resources", - ), - ), - ( - "pertinence", - models.PositiveSmallIntegerField( - choices=[ - (0, "undefined"), - (1, "irrelevant"), - (2, "partially_relevant"), - (3, "fairly_relevant"), - (4, "highly_relevant"), - ], - default=0, - verbose_name="Pertinence", - ), - ), - ( - "activity", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Activity", - ), - ), - ( - "is_selected", - models.BooleanField(default=False, verbose_name="Is selected"), - ), - ( - "justification", - models.TextField(blank=True, verbose_name="Justification"), - ), - ( - "ebios_rm_study", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM study", - ), - ), - ( - "feared_events", - models.ManyToManyField( - related_name="ro_to_couples", - to="ebios_rm.fearedevent", - verbose_name="Feared events", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('risk_origin', models.CharField(choices=[('state', 'State'), ('organized_crime', 'Organized crime'), ('terrorist', 'Terrorist'), ('activist', 'Activist'), ('professional', 'Professional'), ('amateur', 'Amateur'), ('avenger', 'Avenger'), ('pathological', 'Pathological')], max_length=32, verbose_name='Risk origin')), + ('target_objective', models.CharField(max_length=200, verbose_name='Target objective')), + ('motivation', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'very_low'), (2, 'low'), (3, 'significant'), (4, 'strong')], default=0, verbose_name='Motivation')), + ('resources', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'limited'), (2, 'significant'), (3, 'important'), (4, 'unlimited')], default=0, verbose_name='Resources')), + ('pertinence', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'irrelevant'), (2, 'partially_relevant'), (3, 'fairly_relevant'), (4, 'highly_relevant')], default=0, verbose_name='Pertinence')), + ('activity', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Activity')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ('feared_events', models.ManyToManyField(related_name='ro_to_couples', to='ebios_rm.fearedevent', verbose_name='Feared events')), ], options={ - "verbose_name": "RO/TO couple", - "verbose_name_plural": "RO/TO couples", - "ordering": ["created_at"], + 'verbose_name': 'RO/TO couple', + 'verbose_name_plural': 'RO/TO couples', + 'ordering': ['created_at'], }, ), migrations.AddField( - model_name="attackpath", - name="ro_to_couple", - field=models.ForeignKey( - help_text="RO/TO couple from which the attach path is derived", - on_delete=django.db.models.deletion.CASCADE, - to="ebios_rm.roto", - verbose_name="RO/TO couple", - ), + model_name='attackpath', + name='ro_to_couple', + field=models.ForeignKey(help_text='RO/TO couple from which the attach path is derived', on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.roto', verbose_name='RO/TO couple'), ), migrations.CreateModel( - name="Stakeholder", + name='Stakeholder', fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ( - "created_at", - models.DateTimeField(auto_now_add=True, verbose_name="Created at"), - ), - ( - "updated_at", - models.DateTimeField(auto_now=True, verbose_name="Updated at"), - ), - ( - "is_published", - models.BooleanField(default=False, verbose_name="published"), - ), - ( - "category", - models.CharField( - choices=[ - ("client", "Client"), - ("partner", "Partner"), - ("supplier", "Supplier"), - ], - max_length=32, - verbose_name="Category", - ), - ), - ( - "current_dependency", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Current dependency", - ), - ), - ( - "current_penetration", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Current penetration", - ), - ), - ( - "current_maturity", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Current maturity", - ), - ), - ( - "current_trust", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Current trust", - ), - ), - ( - "residual_dependency", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Residual dependency", - ), - ), - ( - "residual_penetration", - models.PositiveSmallIntegerField( - default=0, - validators=[django.core.validators.MaxValueValidator(4)], - verbose_name="Residual penetration", - ), - ), - ( - "residual_maturity", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Residual maturity", - ), - ), - ( - "residual_trust", - models.PositiveSmallIntegerField( - default=1, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(4), - ], - verbose_name="Residual trust", - ), - ), - ( - "is_selected", - models.BooleanField(default=False, verbose_name="Is selected"), - ), - ( - "justification", - models.TextField(blank=True, verbose_name="Justification"), - ), - ( - "applied_controls", - models.ManyToManyField( - blank=True, - help_text="Controls applied to lower stakeholder criticality", - related_name="stakeholders", - to="core.appliedcontrol", - verbose_name="Applied controls", - ), - ), - ( - "ebios_rm_studies", - models.ManyToManyField( - help_text="EBIOS RM studies in which the stakeholder is involved", - related_name="stakeholders", - to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM studies", - ), - ), - ( - "entity", - models.ForeignKey( - help_text="Entity qualified by the stakeholder", - on_delete=django.db.models.deletion.CASCADE, - to="tprm.entity", - verbose_name="Entity", - ), - ), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('is_published', models.BooleanField(default=False, verbose_name='published')), + ('category', models.CharField(choices=[('client', 'Client'), ('partner', 'Partner'), ('supplier', 'Supplier')], max_length=32, verbose_name='Category')), + ('current_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current dependency')), + ('current_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current penetration')), + ('current_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current maturity')), + ('current_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current trust')), + ('residual_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual dependency')), + ('residual_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual penetration')), + ('residual_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual maturity')), + ('residual_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual trust')), + ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), + ('justification', models.TextField(blank=True, verbose_name='Justification')), + ('applied_controls', models.ManyToManyField(blank=True, help_text='Controls applied to lower stakeholder criticality', related_name='stakeholders', to='core.appliedcontrol', verbose_name='Applied controls')), + ('ebios_rm_studies', models.ManyToManyField(help_text='EBIOS RM studies in which the stakeholder is involved', related_name='stakeholders', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM studies')), + ('entity', models.ForeignKey(help_text='Entity qualified by the stakeholder', on_delete=django.db.models.deletion.CASCADE, to='tprm.entity', verbose_name='Entity')), ], options={ - "verbose_name": "Stakeholder", - "verbose_name_plural": "Stakeholders", - "ordering": ["created_at"], + 'verbose_name': 'Stakeholder', + 'verbose_name_plural': 'Stakeholders', + 'ordering': ['created_at'], }, ), migrations.AddField( - model_name="attackpath", - name="stakeholders", - field=models.ManyToManyField( - help_text="Stakeholders leveraged by the attack path", - related_name="attack_paths", - to="ebios_rm.stakeholder", - verbose_name="Stakeholders", - ), + model_name='attackpath', + name='stakeholders', + field=models.ManyToManyField(help_text='Stakeholders leveraged by the attack path', related_name='attack_paths', to='ebios_rm.stakeholder', verbose_name='Stakeholders'), ), ] diff --git a/backend/ebios_rm/tests/fixtures.py b/backend/ebios_rm/tests/fixtures.py index 5098f0667..a25d19dde 100644 --- a/backend/ebios_rm/tests/fixtures.py +++ b/backend/ebios_rm/tests/fixtures.py @@ -1,7 +1,7 @@ import pytest from core.models import RiskMatrix, StoredLibrary, Asset -from ebios_rm.models import ROTO, EbiosRMStudy, FearedEvent +from ebios_rm.models import RoTo, EbiosRMStudy, FearedEvent @pytest.fixture @@ -51,8 +51,8 @@ def basic_feared_event_fixture(basic_ebios_rm_study_fixture): @pytest.fixture def basic_roto_fixture(basic_ebios_rm_study_fixture, basic_feared_event_fixture): - roto = ROTO.objects.create( - risk_origin=ROTO.RiskOrigin.STATE, + roto = RoTo.objects.create( + risk_origin=RoTo.RiskOrigin.STATE, target_objective="test target objectives", ebios_rm_study=basic_ebios_rm_study_fixture, ) diff --git a/backend/ebios_rm/tests/test_ro_to.py b/backend/ebios_rm/tests/test_ro_to.py index 295888700..9a0f23695 100644 --- a/backend/ebios_rm/tests/test_ro_to.py +++ b/backend/ebios_rm/tests/test_ro_to.py @@ -1,17 +1,17 @@ import pytest -from ebios_rm.models import EbiosRMStudy, FearedEvent, ROTO +from ebios_rm.models import EbiosRMStudy, FearedEvent, RoTo from ebios_rm.tests.fixtures import * @pytest.mark.django_db -class TestROTO: +class TestRoTo: @pytest.mark.usefixtures( "basic_ebios_rm_study_fixture", "basic_feared_event_fixture" ) def test_create_roto_basic(self): - roto = ROTO.objects.create( - risk_origin=ROTO.RiskOrigin.STATE, + roto = RoTo.objects.create( + risk_origin=RoTo.RiskOrigin.STATE, target_objective="test target objectives", ebios_rm_study=EbiosRMStudy.objects.get(name="test study"), ) diff --git a/backend/ebios_rm/tests/test_stakeholder.py b/backend/ebios_rm/tests/test_stakeholder.py index e77c30415..fee5bf018 100644 --- a/backend/ebios_rm/tests/test_stakeholder.py +++ b/backend/ebios_rm/tests/test_stakeholder.py @@ -1,5 +1,5 @@ import pytest -from ebios_rm.models import EbiosRMStudy, FearedEvent, ROTO, Stakeholder +from ebios_rm.models import EbiosRMStudy, FearedEvent, RoTo, Stakeholder from tprm.models import Entity From 568bd19bebded9ec6d7f67fe18682747a48bac0f Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 3 Dec 2024 11:49:30 +0100 Subject: [PATCH 29/34] Add unit test to check default reference entity --- backend/ebios_rm/tests/test_ebios_rm_study.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/ebios_rm/tests/test_ebios_rm_study.py b/backend/ebios_rm/tests/test_ebios_rm_study.py index a31b8455e..13fbf0d3a 100644 --- a/backend/ebios_rm/tests/test_ebios_rm_study.py +++ b/backend/ebios_rm/tests/test_ebios_rm_study.py @@ -3,6 +3,7 @@ from ebios_rm.models import EbiosRMStudy from ebios_rm.tests.fixtures import * +from tprm.models import Entity @pytest.mark.django_db @@ -21,6 +22,8 @@ def test_create_ebios_rm_study_basic(self): assert study.risk_matrix == RiskMatrix.objects.get( urn="urn:intuitem:risk:matrix:risk-matrix-4x4-ebios-rm" ) + assert study.assets.count() == 0 + assert study.reference_entity == Entity.get_main_entity() @pytest.mark.usefixtures("ebios_rm_matrix_fixture", "basic_assets_tree_fixture") def test_create_ebios_rm_study_with_assets(self): From 01827e44da4eec373cd48135a67dd26e49877360 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 3 Dec 2024 12:06:49 +0100 Subject: [PATCH 30/34] chore: ruff format --- backend/ebios_rm/migrations/0001_initial.py | 726 ++++++++++++++++---- 1 file changed, 608 insertions(+), 118 deletions(-) diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py index 8df3d1762..974eb542a 100644 --- a/backend/ebios_rm/migrations/0001_initial.py +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -10,169 +10,659 @@ class Migration(migrations.Migration): - initial = True dependencies = [ - ('core', '0044_qualification'), - ('iam', '0009_create_allauth_emailaddress_objects'), - ('tprm', '0003_entityassessment_representatives'), + ("core", "0044_qualification"), + ("iam", "0009_create_allauth_emailaddress_objects"), + ("tprm", "0003_entityassessment_representatives"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='EbiosRMStudy', + name="EbiosRMStudy", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('name', models.CharField(max_length=200, verbose_name='Name')), - ('description', models.TextField(blank=True, null=True, verbose_name='Description')), - ('eta', models.DateField(blank=True, null=True, verbose_name='ETA')), - ('due_date', models.DateField(blank=True, null=True, verbose_name='Due date')), - ('ref_id', models.CharField(max_length=100)), - ('version', models.CharField(blank=True, default='1.0', help_text='Version of the Ebios RM study (eg. 1.0, 2.0, etc.)', max_length=100, null=True, verbose_name='Version')), - ('status', models.CharField(blank=True, choices=[('planned', 'Planned'), ('in_progress', 'In progress'), ('in_review', 'In review'), ('done', 'Done'), ('deprecated', 'Deprecated')], default='planned', max_length=100, null=True, verbose_name='Status')), - ('observation', models.TextField(blank=True, null=True, verbose_name='Observation')), - ('assets', models.ManyToManyField(help_text='Assets that are pertinent to the study', related_name='ebios_rm_studies', to='core.asset', verbose_name='Assets')), - ('authors', models.ManyToManyField(blank=True, related_name='authors', to=settings.AUTH_USER_MODEL, verbose_name='Authors')), - ('compliance_assessments', models.ManyToManyField(help_text='Compliance assessments established as security baseline during workshop 1.4', related_name='ebios_rm_studies', to='core.complianceassessment', verbose_name='Compliance assessments')), - ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), - ('reference_entity', models.ForeignKey(default=tprm.models.Entity.get_main_entity, help_text='Entity that is the focus of the study', on_delete=django.db.models.deletion.PROTECT, related_name='ebios_rm_studies', to='tprm.entity', verbose_name='Reference entity')), - ('reviewers', models.ManyToManyField(blank=True, related_name='reviewers', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), - ('risk_assessments', models.ManyToManyField(help_text='Risk assessments generated at the end of workshop 4', related_name='ebios_rm_studies', to='core.riskassessment', verbose_name='Risk assessments')), - ('risk_matrix', models.ForeignKey(help_text='Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`', on_delete=django.db.models.deletion.PROTECT, related_name='ebios_rm_studies', to='core.riskmatrix', verbose_name='Risk matrix')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("name", models.CharField(max_length=200, verbose_name="Name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ("eta", models.DateField(blank=True, null=True, verbose_name="ETA")), + ( + "due_date", + models.DateField(blank=True, null=True, verbose_name="Due date"), + ), + ("ref_id", models.CharField(max_length=100)), + ( + "version", + models.CharField( + blank=True, + default="1.0", + help_text="Version of the Ebios RM study (eg. 1.0, 2.0, etc.)", + max_length=100, + null=True, + verbose_name="Version", + ), + ), + ( + "status", + models.CharField( + blank=True, + choices=[ + ("planned", "Planned"), + ("in_progress", "In progress"), + ("in_review", "In review"), + ("done", "Done"), + ("deprecated", "Deprecated"), + ], + default="planned", + max_length=100, + null=True, + verbose_name="Status", + ), + ), + ( + "observation", + models.TextField(blank=True, null=True, verbose_name="Observation"), + ), + ( + "assets", + models.ManyToManyField( + help_text="Assets that are pertinent to the study", + related_name="ebios_rm_studies", + to="core.asset", + verbose_name="Assets", + ), + ), + ( + "authors", + models.ManyToManyField( + blank=True, + related_name="authors", + to=settings.AUTH_USER_MODEL, + verbose_name="Authors", + ), + ), + ( + "compliance_assessments", + models.ManyToManyField( + help_text="Compliance assessments established as security baseline during workshop 1.4", + related_name="ebios_rm_studies", + to="core.complianceassessment", + verbose_name="Compliance assessments", + ), + ), + ( + "folder", + models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + ( + "reference_entity", + models.ForeignKey( + default=tprm.models.Entity.get_main_entity, + help_text="Entity that is the focus of the study", + on_delete=django.db.models.deletion.PROTECT, + related_name="ebios_rm_studies", + to="tprm.entity", + verbose_name="Reference entity", + ), + ), + ( + "reviewers", + models.ManyToManyField( + blank=True, + related_name="reviewers", + to=settings.AUTH_USER_MODEL, + verbose_name="Reviewers", + ), + ), + ( + "risk_assessments", + models.ManyToManyField( + help_text="Risk assessments generated at the end of workshop 4", + related_name="ebios_rm_studies", + to="core.riskassessment", + verbose_name="Risk assessments", + ), + ), + ( + "risk_matrix", + models.ForeignKey( + help_text="Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`", + on_delete=django.db.models.deletion.PROTECT, + related_name="ebios_rm_studies", + to="core.riskmatrix", + verbose_name="Risk matrix", + ), + ), ], options={ - 'verbose_name': 'Ebios RM Study', - 'verbose_name_plural': 'Ebios RM Studies', - 'ordering': ['created_at'], + "verbose_name": "Ebios RM Study", + "verbose_name_plural": "Ebios RM Studies", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='AttackPath', + name="AttackPath", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('description', models.TextField(verbose_name='Description')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("description", models.TextField(verbose_name="Description")), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), ], options={ - 'verbose_name': 'Attack path', - 'verbose_name_plural': 'Attack paths', - 'ordering': ['created_at'], + "verbose_name": "Attack path", + "verbose_name_plural": "Attack paths", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='FearedEvent', + name="FearedEvent", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('name', models.CharField(max_length=200, verbose_name='Name')), - ('description', models.TextField(blank=True, null=True, verbose_name='Description')), - ('ref_id', models.CharField(max_length=100)), - ('gravity', models.PositiveSmallIntegerField(default=0, verbose_name='Gravity')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('assets', models.ManyToManyField(help_text='Assets that are affected by the feared event', related_name='feared_events', to='core.asset', verbose_name='Assets')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), - ('qualifications', models.ManyToManyField(help_text='Qualifications carried by the feared event', related_name='feared_events', to='core.qualification', verbose_name='Qualifications')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("name", models.CharField(max_length=200, verbose_name="Name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ("ref_id", models.CharField(max_length=100)), + ( + "gravity", + models.PositiveSmallIntegerField(default=0, verbose_name="Gravity"), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "assets", + models.ManyToManyField( + help_text="Assets that are affected by the feared event", + related_name="feared_events", + to="core.asset", + verbose_name="Assets", + ), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "qualifications", + models.ManyToManyField( + help_text="Qualifications carried by the feared event", + related_name="feared_events", + to="core.qualification", + verbose_name="Qualifications", + ), + ), ], options={ - 'verbose_name': 'Feared event', - 'verbose_name_plural': 'Feared events', - 'ordering': ['created_at'], + "verbose_name": "Feared event", + "verbose_name_plural": "Feared events", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='OperationalScenario', + name="OperationalScenario", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('description', models.TextField(verbose_name='Description')), - ('likelihood', models.SmallIntegerField(default=-1, verbose_name='Likelihood')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('attack_paths', models.ManyToManyField(help_text='Attack paths that are pertinent to the operational scenario', related_name='operational_scenarios', to='ebios_rm.attackpath', verbose_name='Attack paths')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='operational_scenarios', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), - ('threats', models.ManyToManyField(blank=True, help_text='Threats leveraged by the operational scenario', related_name='operational_scenarios', to='core.threat', verbose_name='Threats')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ("description", models.TextField(verbose_name="Description")), + ( + "likelihood", + models.SmallIntegerField(default=-1, verbose_name="Likelihood"), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "attack_paths", + models.ManyToManyField( + help_text="Attack paths that are pertinent to the operational scenario", + related_name="operational_scenarios", + to="ebios_rm.attackpath", + verbose_name="Attack paths", + ), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="operational_scenarios", + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "threats", + models.ManyToManyField( + blank=True, + help_text="Threats leveraged by the operational scenario", + related_name="operational_scenarios", + to="core.threat", + verbose_name="Threats", + ), + ), ], options={ - 'verbose_name': 'Operational scenario', - 'verbose_name_plural': 'Operational scenarios', - 'ordering': ['created_at'], + "verbose_name": "Operational scenario", + "verbose_name_plural": "Operational scenarios", + "ordering": ["created_at"], }, ), migrations.CreateModel( - name='RoTo', + name="RoTo", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('risk_origin', models.CharField(choices=[('state', 'State'), ('organized_crime', 'Organized crime'), ('terrorist', 'Terrorist'), ('activist', 'Activist'), ('professional', 'Professional'), ('amateur', 'Amateur'), ('avenger', 'Avenger'), ('pathological', 'Pathological')], max_length=32, verbose_name='Risk origin')), - ('target_objective', models.CharField(max_length=200, verbose_name='Target objective')), - ('motivation', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'very_low'), (2, 'low'), (3, 'significant'), (4, 'strong')], default=0, verbose_name='Motivation')), - ('resources', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'limited'), (2, 'significant'), (3, 'important'), (4, 'unlimited')], default=0, verbose_name='Resources')), - ('pertinence', models.PositiveSmallIntegerField(choices=[(0, 'undefined'), (1, 'irrelevant'), (2, 'partially_relevant'), (3, 'fairly_relevant'), (4, 'highly_relevant')], default=0, verbose_name='Pertinence')), - ('activity', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Activity')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('ebios_rm_study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM study')), - ('feared_events', models.ManyToManyField(related_name='ro_to_couples', to='ebios_rm.fearedevent', verbose_name='Feared events')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ( + "risk_origin", + models.CharField( + choices=[ + ("state", "State"), + ("organized_crime", "Organized crime"), + ("terrorist", "Terrorist"), + ("activist", "Activist"), + ("professional", "Professional"), + ("amateur", "Amateur"), + ("avenger", "Avenger"), + ("pathological", "Pathological"), + ], + max_length=32, + verbose_name="Risk origin", + ), + ), + ( + "target_objective", + models.CharField(max_length=200, verbose_name="Target objective"), + ), + ( + "motivation", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "very_low"), + (2, "low"), + (3, "significant"), + (4, "strong"), + ], + default=0, + verbose_name="Motivation", + ), + ), + ( + "resources", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "limited"), + (2, "significant"), + (3, "important"), + (4, "unlimited"), + ], + default=0, + verbose_name="Resources", + ), + ), + ( + "pertinence", + models.PositiveSmallIntegerField( + choices=[ + (0, "undefined"), + (1, "irrelevant"), + (2, "partially_relevant"), + (3, "fairly_relevant"), + (4, "highly_relevant"), + ], + default=0, + verbose_name="Pertinence", + ), + ), + ( + "activity", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Activity", + ), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "ebios_rm_study", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM study", + ), + ), + ( + "feared_events", + models.ManyToManyField( + related_name="ro_to_couples", + to="ebios_rm.fearedevent", + verbose_name="Feared events", + ), + ), ], options={ - 'verbose_name': 'RO/TO couple', - 'verbose_name_plural': 'RO/TO couples', - 'ordering': ['created_at'], + "verbose_name": "RO/TO couple", + "verbose_name_plural": "RO/TO couples", + "ordering": ["created_at"], }, ), migrations.AddField( - model_name='attackpath', - name='ro_to_couple', - field=models.ForeignKey(help_text='RO/TO couple from which the attach path is derived', on_delete=django.db.models.deletion.CASCADE, to='ebios_rm.roto', verbose_name='RO/TO couple'), + model_name="attackpath", + name="ro_to_couple", + field=models.ForeignKey( + help_text="RO/TO couple from which the attach path is derived", + on_delete=django.db.models.deletion.CASCADE, + to="ebios_rm.roto", + verbose_name="RO/TO couple", + ), ), migrations.CreateModel( - name='Stakeholder', + name="Stakeholder", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('is_published', models.BooleanField(default=False, verbose_name='published')), - ('category', models.CharField(choices=[('client', 'Client'), ('partner', 'Partner'), ('supplier', 'Supplier')], max_length=32, verbose_name='Category')), - ('current_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current dependency')), - ('current_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Current penetration')), - ('current_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current maturity')), - ('current_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Current trust')), - ('residual_dependency', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual dependency')), - ('residual_penetration', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(4)], verbose_name='Residual penetration')), - ('residual_maturity', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual maturity')), - ('residual_trust', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4)], verbose_name='Residual trust')), - ('is_selected', models.BooleanField(default=False, verbose_name='Is selected')), - ('justification', models.TextField(blank=True, verbose_name='Justification')), - ('applied_controls', models.ManyToManyField(blank=True, help_text='Controls applied to lower stakeholder criticality', related_name='stakeholders', to='core.appliedcontrol', verbose_name='Applied controls')), - ('ebios_rm_studies', models.ManyToManyField(help_text='EBIOS RM studies in which the stakeholder is involved', related_name='stakeholders', to='ebios_rm.ebiosrmstudy', verbose_name='EBIOS RM studies')), - ('entity', models.ForeignKey(help_text='Entity qualified by the stakeholder', on_delete=django.db.models.deletion.CASCADE, to='tprm.entity', verbose_name='Entity')), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ( + "is_published", + models.BooleanField(default=False, verbose_name="published"), + ), + ( + "category", + models.CharField( + choices=[ + ("client", "Client"), + ("partner", "Partner"), + ("supplier", "Supplier"), + ], + max_length=32, + verbose_name="Category", + ), + ), + ( + "current_dependency", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Current dependency", + ), + ), + ( + "current_penetration", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Current penetration", + ), + ), + ( + "current_maturity", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Current maturity", + ), + ), + ( + "current_trust", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Current trust", + ), + ), + ( + "residual_dependency", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Residual dependency", + ), + ), + ( + "residual_penetration", + models.PositiveSmallIntegerField( + default=0, + validators=[django.core.validators.MaxValueValidator(4)], + verbose_name="Residual penetration", + ), + ), + ( + "residual_maturity", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Residual maturity", + ), + ), + ( + "residual_trust", + models.PositiveSmallIntegerField( + default=1, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4), + ], + verbose_name="Residual trust", + ), + ), + ( + "is_selected", + models.BooleanField(default=False, verbose_name="Is selected"), + ), + ( + "justification", + models.TextField(blank=True, verbose_name="Justification"), + ), + ( + "applied_controls", + models.ManyToManyField( + blank=True, + help_text="Controls applied to lower stakeholder criticality", + related_name="stakeholders", + to="core.appliedcontrol", + verbose_name="Applied controls", + ), + ), + ( + "ebios_rm_studies", + models.ManyToManyField( + help_text="EBIOS RM studies in which the stakeholder is involved", + related_name="stakeholders", + to="ebios_rm.ebiosrmstudy", + verbose_name="EBIOS RM studies", + ), + ), + ( + "entity", + models.ForeignKey( + help_text="Entity qualified by the stakeholder", + on_delete=django.db.models.deletion.CASCADE, + to="tprm.entity", + verbose_name="Entity", + ), + ), ], options={ - 'verbose_name': 'Stakeholder', - 'verbose_name_plural': 'Stakeholders', - 'ordering': ['created_at'], + "verbose_name": "Stakeholder", + "verbose_name_plural": "Stakeholders", + "ordering": ["created_at"], }, ), migrations.AddField( - model_name='attackpath', - name='stakeholders', - field=models.ManyToManyField(help_text='Stakeholders leveraged by the attack path', related_name='attack_paths', to='ebios_rm.stakeholder', verbose_name='Stakeholders'), + model_name="attackpath", + name="stakeholders", + field=models.ManyToManyField( + help_text="Stakeholders leveraged by the attack path", + related_name="attack_paths", + to="ebios_rm.stakeholder", + verbose_name="Stakeholders", + ), ), ] From 6ec512f2ca917c4705d36fe9dd3a69b2897d11a3 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 3 Dec 2024 13:57:52 +0100 Subject: [PATCH 31/34] Adjust models --- backend/ebios_rm/migrations/0001_initial.py | 19 +++++++++++++------ backend/ebios_rm/models.py | 15 +++++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/backend/ebios_rm/migrations/0001_initial.py b/backend/ebios_rm/migrations/0001_initial.py index 974eb542a..b0fb9dc58 100644 --- a/backend/ebios_rm/migrations/0001_initial.py +++ b/backend/ebios_rm/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.1 on 2024-12-03 10:46 +# Generated by Django 5.1.1 on 2024-12-03 12:57 import django.core.validators import django.db.models.deletion @@ -90,6 +90,7 @@ class Migration(migrations.Migration): ( "assets", models.ManyToManyField( + blank=True, help_text="Assets that are pertinent to the study", related_name="ebios_rm_studies", to="core.asset", @@ -108,6 +109,7 @@ class Migration(migrations.Migration): ( "compliance_assessments", models.ManyToManyField( + blank=True, help_text="Compliance assessments established as security baseline during workshop 1.4", related_name="ebios_rm_studies", to="core.complianceassessment", @@ -146,6 +148,7 @@ class Migration(migrations.Migration): ( "risk_assessments", models.ManyToManyField( + blank=True, help_text="Risk assessments generated at the end of workshop 4", related_name="ebios_rm_studies", to="core.riskassessment", @@ -249,7 +252,7 @@ class Migration(migrations.Migration): ("ref_id", models.CharField(max_length=100)), ( "gravity", - models.PositiveSmallIntegerField(default=0, verbose_name="Gravity"), + models.SmallIntegerField(default=-1, verbose_name="Gravity"), ), ( "is_selected", @@ -262,6 +265,7 @@ class Migration(migrations.Migration): ( "assets", models.ManyToManyField( + blank=True, help_text="Assets that are affected by the feared event", related_name="feared_events", to="core.asset", @@ -279,6 +283,7 @@ class Migration(migrations.Migration): ( "qualifications", models.ManyToManyField( + blank=True, help_text="Qualifications carried by the feared event", related_name="feared_events", to="core.qualification", @@ -631,12 +636,13 @@ class Migration(migrations.Migration): ), ), ( - "ebios_rm_studies", - models.ManyToManyField( - help_text="EBIOS RM studies in which the stakeholder is involved", + "ebios_rm_study", + models.ForeignKey( + help_text="EBIOS RM study that the stakeholder is part of", + on_delete=django.db.models.deletion.CASCADE, related_name="stakeholders", to="ebios_rm.ebiosrmstudy", - verbose_name="EBIOS RM studies", + verbose_name="EBIOS RM study", ), ), ( @@ -644,6 +650,7 @@ class Migration(migrations.Migration): models.ForeignKey( help_text="Entity qualified by the stakeholder", on_delete=django.db.models.deletion.CASCADE, + related_name="stakeholders", to="tprm.entity", verbose_name="Entity", ), diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py index de83194c6..c800aa705 100644 --- a/backend/ebios_rm/models.py +++ b/backend/ebios_rm/models.py @@ -37,9 +37,11 @@ class Status(models.TextChoices): verbose_name=_("Assets"), related_name="ebios_rm_studies", help_text=_("Assets that are pertinent to the study"), + blank=True, ) compliance_assessments = models.ManyToManyField( ComplianceAssessment, + blank=True, verbose_name=_("Compliance assessments"), related_name="ebios_rm_studies", help_text=_( @@ -48,6 +50,7 @@ class Status(models.TextChoices): ) risk_assessments = models.ManyToManyField( RiskAssessment, + blank=True, verbose_name=_("Risk assessments"), related_name="ebios_rm_studies", help_text=_("Risk assessments generated at the end of workshop 4"), @@ -106,19 +109,21 @@ class FearedEvent(NameDescriptionMixin): ) assets = models.ManyToManyField( Asset, + blank=True, verbose_name=_("Assets"), related_name="feared_events", help_text=_("Assets that are affected by the feared event"), ) qualifications = models.ManyToManyField( Qualification, + blank=True, verbose_name=_("Qualifications"), related_name="feared_events", help_text=_("Qualifications carried by the feared event"), ) ref_id = models.CharField(max_length=100) - gravity = models.PositiveSmallIntegerField(verbose_name=_("Gravity"), default=0) + gravity = models.SmallIntegerField(default=-1, verbose_name=_("Gravity")) is_selected = models.BooleanField(verbose_name=_("Is selected"), default=False) justification = models.TextField(verbose_name=_("Justification"), blank=True) @@ -208,16 +213,18 @@ class Category(models.TextChoices): PARTNER = "partner", _("Partner") SUPPLIER = "supplier", _("Supplier") - ebios_rm_studies = models.ManyToManyField( + ebios_rm_study = models.ForeignKey( EbiosRMStudy, - verbose_name=_("EBIOS RM studies"), + verbose_name=_("EBIOS RM study"), + help_text=_("EBIOS RM study that the stakeholder is part of"), related_name="stakeholders", - help_text=_("EBIOS RM studies in which the stakeholder is involved"), + on_delete=models.CASCADE, ) entity = models.ForeignKey( Entity, on_delete=models.CASCADE, verbose_name=_("Entity"), + related_name="stakeholders", help_text=_("Entity qualified by the stakeholder"), ) applied_controls = models.ManyToManyField( From e534ddebc6761f8783036b5406f7a400307f7a8a Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 3 Dec 2024 13:59:09 +0100 Subject: [PATCH 32/34] Update Stakeholder unit tests --- backend/ebios_rm/tests/test_stakeholder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ebios_rm/tests/test_stakeholder.py b/backend/ebios_rm/tests/test_stakeholder.py index fee5bf018..cc034af2d 100644 --- a/backend/ebios_rm/tests/test_stakeholder.py +++ b/backend/ebios_rm/tests/test_stakeholder.py @@ -17,8 +17,8 @@ def test_create_stakeholder_basic(self): stakeholder = Stakeholder.objects.create( entity=entity, category=Stakeholder.Category.SUPPLIER, + ebios_rm_study=study, ) - stakeholder.ebios_rm_studies.add(study) assert stakeholder in study.stakeholders.all() assert stakeholder.entity == entity From 5eb59c474f0a4553a557a26c157e5226b5c27afb Mon Sep 17 00:00:00 2001 From: Abder Date: Tue, 3 Dec 2024 17:57:58 +0100 Subject: [PATCH 33/34] Add files via upload --- features.png | Bin 149475 -> 149437 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/features.png b/features.png index 036cf36356cfdd52c641379999bcf2924a0e3043..fac5e5142a82b5ae8a1899bd9901059aca7b0245 100644 GIT binary patch delta 89379 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 Ic<1Q<0~kl;g8%>k delta 89391 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 From 427ceedd4439e5a6ecbed08315f0aaec2227387f Mon Sep 17 00:00:00 2001 From: eric-intuitem <71850047+eric-intuitem@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:20:57 +0100 Subject: [PATCH 34/34] Update data-model.md for ebios-rm (v3) (#1105) --- documentation/architecture/data-model.md | 165 +++++++++++++---------- 1 file changed, 96 insertions(+), 69 deletions(-) diff --git a/documentation/architecture/data-model.md b/documentation/architecture/data-model.md index b94205a92..20766a703 100644 --- a/documentation/architecture/data-model.md +++ b/documentation/architecture/data-model.md @@ -100,19 +100,20 @@ erDiagram COMPLIANCE_ASSESSMENT }o--|| FRAMEWORK : is_based_on PROJECT |o--o{ COMPLIANCE_ASSESSMENT : contains COMPLIANCE_ASSESSMENT ||--o{ REQUIREMENT_ASSESSMENT: contains + APPLIED_CONTROL }o--o{ EVIDENCE : is_proved_by + FRAMEWORK ||--o{ REQUIREMENT_NODE : contains REQUIREMENT_ASSESSMENT }o--|| REQUIREMENT_NODE : implements REQUIREMENT_ASSESSMENT }o--o{ APPLIED_CONTROL : is_answered_by REQUIREMENT_ASSESSMENT }o--o{ EVIDENCE : is_proved_by APPLIED_CONTROL }o--o| REFERENCE_CONTROL : implements REQUIREMENT_NODE }o--o{ THREAT : addresses - FRAMEWORK ||--o{ REQUIREMENT_NODE : contains - APPLIED_CONTROL }o--o{ EVIDENCE : is_proved_by RISK_ASSESSMENT }o--|| RISK_MATRIX : applies PROJECT |o--o{ RISK_ASSESSMENT : contains RISK_ASSESSMENT ||--o{ RISK_SCENARIO : contains RISK_SCENARIO }o--o{ APPLIED_CONTROL : is_mitigated_by RISK_SCENARIO }o--o{ THREAT : derives_from RISK_SCENARIO }o--o{ ASSET : threatens + RISK_SCENARIO }o--o{ QUALIFICATION : bears RISK_ACCEPTANCE }o--o{ RISK_SCENARIO : covers RISK_ASSESSMENT_REVIEW }o--|| RISK_ASSESSMENT : reviews RISK_SCENARIO }o--o{ VULNERABILITY : exploits @@ -120,6 +121,8 @@ erDiagram USER }o--o{ RISK_SCENARIO : owns USER }o--o{ APPLIED_CONTROL : owns USER }o--o{ ASSET : owns + ASSET ||--o{ SECURITY_OBJECTIVE : has + SECURITY_OBJECTIVE }o--|| QUALIFICATION : implements PROJECT { string ref_id @@ -289,8 +292,9 @@ erDiagram string type asset parent_asset url reference_link - json security_objectives - json disaster_recovery_objectives + int rto + int rpo + int mtd } RISK_SCENARIO { @@ -307,7 +311,6 @@ erDiagram json target_risk_vector string strength_of_knowledge string justification - json qualifications string threat_actor } @@ -337,6 +340,19 @@ erDiagram string reviewer } + QUALIFICATION { + string ref_id + string name + string description + json translations + int order + bool is_objective + } + + SECURITY_OBJECTIVE { + int value + } + ``` ### Requirement mappings @@ -407,37 +423,47 @@ Projects have the following fields: - Description - Status: --/Design/Development/Production/End of life/Dropped -## Assets, security and disaster recovery objectives +## Qualifications -Assets are context objects defined by the entity using CISO Assistant. They are optional, assessments can be done without using them. +Qualifications are qualities/objectives that can be used to qualify risk scenarios or to set security objectives to primary assets. Some of them are hardcoded, but in the PRO version the administrator can define additional values and rename existing ones. -Assets are of type primary or supporting. A primary asset has no parent, a supporting asset can have parent assets (primary or supporting), but not itself. +The following values are preloaded: + +abbreviation | q_order | so_order | name | description | translations | urn +-------------|---------|----------|------------------|-------------|--------------|------------------------------------------------ +C | 1 | 1 | confidentiality | | ... | urn:intuitem:risk:qualification:confidentiality +I | 2 | 2 | integrity | | ... | urn:intuitem:risk:qualification:integrity +A | 3 | 3 | availability | | ... | urn:intuitem:risk:qualification:availability +P | 4 | 4 | proof | | ... | urn:intuitem:risk:qualification:proof +Aut | 5 | 5 | authenticity | | ... | urn:intuitem:risk:qualification:authenticity +Priv | 6 | 6 | privacy | | ... | urn:intuitem:risk:qualification:privacy +Safe | 7 | 7 | safety | | ... | urn:intuitem:risk:qualification:safety +Rep | 8 | | reputation | | ... | urn:intuitem:risk:qualification:safety +Ope | 9 | | operational | | ... | urn:intuitem:risk:qualification:operational +Leg | 10 | | legal | | ... | urn:intuitem:risk:qualification:legal +Fin | 11 | | financial | | ... | urn:intuitem:risk:qualification:financial -Primary assets have security objectives that are evolutive, so they are catched in a json field. +Qualifications that have so_order defined can be used to set security objectives to primary assets. -Security objectives are specific goals or requirements that an organization, system, or process aims to achieve in order to ensure its security and protect its primary assets. +The role of urn is to enable updates with a library, and to facilitate export/import between instances (not in MVP). -There is a global parameter that defines a list of security objectives with a corresponding scale and a corresponding boolean allowing to select or hide a security objective. The following security objectives are pre-defined: +Note: the order can be changed in a translation. This makes easy to transform CIAP (English) in DICP (French) (not in MVP). - ref_id | Name | Description | default scale | default select value ---------|----------------------------|-------------|---------------|--------------------- - C | Confidentiality | ... | 1-4 | True - I | Integrity | ... | 1-4 | True - A | Availability | ... | 1-4 | True - P | Proof | ... | 1-4 | True - Auth | Authenticity | ... | 1-4 | False - Priv | Privacy | ... | 1-4 | False - Safe | Safety | ... | 1-4 | False +## Assets, security and disaster recovery objectives + +Assets are context objects defined by the entity using CISO Assistant. They are optional, assessments can be done without using them. + +Assets are of type primary or supporting. A primary asset has no parent, a supporting asset can have parent assets (primary or supporting), but not itself. -The following disaster recovery objectives (measured in seconds) are pre-defined: +The following disaster recovery objectives (measured in seconds) can be defined on assets: - ref_id | Name | Description ---------|----------------------------|------------ - RTO | Recovery Time Objective | ... - RPO | Recovery Point Objetive | ... - MTD | Maximum Tolerable Downtime | ... + Abbreviation | Name | Description +--------------|----------------------------|------------ + RTO | Recovery Time Objective | ... + RPO | Recovery Point Objetive | ... + MTD | Maximum Tolerable Downtime | ... -In a future version, users will be able to define custom security objectives. +Assets have security objectives. Security objectives are specific goals or requirements that an organization, system, or process aims to achieve in order to ensure its security and protect its primary assets. They are a subset of qualifications. Security objectives are measured using a specifc scale. For now, the following scales are defined: - 0-3: coded as 0-3 @@ -461,9 +487,7 @@ FIPS-199 | 1 | moderate FIPS-199 | 2 | moderate FIPS-199 | 3 | high -Security objectives can be evaluated for each asset. The default value is Null. The corresponding json field is composed of a list of tuples {security_objective_ref_id, value}. - -When a security objective is hidden in the global parameters, it is simply not proposed for new edition. However, a security objective that is already used in an asset is kept and editable even if it is hidden globally. Thus, when selecting or hiding a security objective, no value is changed in asset. +THe scale to use is a global parameter. It has no impact on the encoding in the database, which always uses the internal value. ## Frameworks @@ -623,7 +647,6 @@ The following inference rules are used: A risk assessment is based on scenarios, covered by Applied controls. Gathering the risk scenarios constitutes the "risk identification" phase. - The risk matrix cannot be changed once the risk assessment is created. A risk assessment has an _risk_assessment_method_ field that can take the following values: 0 (risk matrix)/1 (Open FAIR). This cannot be changed once the risk assessment is created. Similarly, the risk matrix cannot be changed once the risk assessment is created. @@ -634,9 +657,7 @@ A risk scenario contains a treatment option with the values --/open/mitigate/acc A risk scenario also contains a "strength of knowledge", within the values --/0 (Low)/1 (Medium)/2 (High). This can be used to represent a third dimension of risk, as recommended by the Society for Risk Analysis. The field "justification" can be used to expose the knowledge. -A risk scenario also contains a "qualification" field, containing an array with the following possible values: Confidentiality, Integrity, Availability, Proof, Authenticity, Privacy, Safety, Reputation, Operational, Legal, Financial. The qualification can cover none, one or several of the values. - -Note: the list of qualifications is a superset of security objectives. +A risk scenario also contains qualifications. The risk evaluation is automatically done based on the selected risk matrix. @@ -1141,16 +1162,16 @@ Each of these objects will have its specific datamodel. Factoring will be done a EBIOS-RM (english) | EBIOS-RM (french) | CISO Assistant ----------------------|-------------------------|---------------- -Study | Etude | Bundle -Studied object | Objet de l'étude | Description of the bundle -Mission | Mission | Mission of the reference entity added to the bundle +Study | Etude | Study +Studied object | Objet de l'étude | Description of the Study +Mission | Mission | Mission of the reference entity added to the Study Business asset | Valeurs métier | Primary asset Supporting asset | Bien support | Supporting asset Feared event | Evénement redouté | Risk analysis at asset level Impact | Impact | Impact in a risk analysis Security baseline | Socle de sécurité | Compliance frameworks and audits -Risk origins | Sources de risque | TBD -Target objectives | Objectifs visés | TBD +Risk origins | Sources de risque | RoTo +Target objectives | Objectifs visés | RoTo Ecosystem | Ecosystème | Third Party Risk Management Strategic scenarios | Scénarios stratégiques | Risk analysis at strategic level (focus on impact) Security controls | Mesures de sécurité | Reference/applied controls @@ -1169,7 +1190,7 @@ The type EBIOS-RM study is a sort of assessment. It contains the following speci - a list of audits for the security baseline (workshop 1) - a list of feared events (workshop 1) - a list of risk_origin_target_objective (workshop 2) -- a list of ecosystem entities (workshop 3) +- a list of stakeholders (workshop 3) - a list of strategic scenarios/attack paths (workshop 3) - a list of opeating scenarios (workshop 4) - a resulting risk assessment (workshop 5) @@ -1207,7 +1228,7 @@ The object ecosystem entity (workshop 3) links to a TPRM entity, and contains th The object strategic attack path (workshop 3) contains the following fields: - risk_origin_target_objective - description -- affected ecosystem entities +- affected stakeholders - intial threat level - Controls - residual threat level @@ -1233,12 +1254,12 @@ The frontend for risk study shall propose the following steps: - workshop 2: risk origin/target objectives (sources de risque) - define risk_origin_target_objective objects - workshop 3: - - list of ecosystem entities + - list of stakeholders - list of strategic scenarios/attack paths - workshop 4: operational scenarios - list of operational scenarios + - The risk assessment is generated automatically, thanks to a dedicated button. When the risk assessment is generated again, automatic versioning is applied, and mitigations can be copied on demand (based on ref_id of operational scenarios). - workshop 5: risk treatment - - The risk assessment is generated from workshop 4, thanks to a dedicated button. When the risk assessment is generated again, automatic versioning is applied, and mitigations can be copied on demand (based on ref_id of operational scenarios). - After generation, a risk assessment is fully editable, to allow customisation, and the risk assessment can be managed normally as any other risk assessment. - risk treatment is based on the risk assessment. @@ -1246,7 +1267,7 @@ The frontend for risk study shall propose the following steps: ```mermaid erDiagram DOMAIN ||--o{ EBIOS_RM_STUDY : contains - DOMAIN ||--o{ ECOSYSTEM_ENTITY : contains + DOMAIN ||--o{ STAKEHOLDER : contains DOMAIN ||--o{ OPERATIONAL_SCENARIO : contains DOMAIN ||--o{ FEARED_EVENT : contains DOMAIN ||--o{ RO_TO : contains @@ -1257,22 +1278,25 @@ erDiagram ```mermaid erDiagram - ATTACK_PATH }o--|| RO_TO : derives - RO_TO }o--|{ FEARED_EVENT : corresponds_to - EBIOS_RM_STUDY }o--o{ RO_TO : contains - EBIOS_RM_STUDY }o--o{ ECOSYSTEM_ENTITY : contains - EBIOS_RM_STUDY }o--o{ OPERATIONAL_SCENARIO : contains - EBIOS_RM_STUDY }o--o{ FEARED_EVENT : contains - EBIOS_RM_STUDY }o--o{ ATTACK_PATH : contains + FEARED_EVENT }o--o{ ASSET : affects + STAKEHOLDER }o--|| ENTITY : qualifies + EBIOS_RM_STUDY ||--o{ RO_TO : contains + EBIOS_RM_STUDY ||--o{ STAKEHOLDER : contains + EBIOS_RM_STUDY ||--o{ OPERATIONAL_SCENARIO : contains + EBIOS_RM_STUDY ||--o{ FEARED_EVENT : contains + EBIOS_RM_STUDY ||--o{ ATTACK_PATH : contains + EBIOS_RM_STUDY }o--o{ ASSET : contains EBIOS_RM_STUDY }o--o| ENTITY : studies EBIOS_RM_STUDY }o--o{ COMPLIANCE_ASSESSMENT: leverages EBIOS_RM_STUDY }o--|| RISK_MATRIX : leverages EBIOS_RM_STUDY }o--o{ RISK_ASSESSMENT : generates - OPERATIONAL_SCENARIO }o--|| ATTACK_PATH : derives + ATTACK_PATH }o--|| RO_TO : derives + RO_TO }o--o{ FEARED_EVENT : corresponds_to + OPERATIONAL_SCENARIO }o--|{ ATTACK_PATH : derives OPERATIONAL_SCENARIO }o--o{ THREAT : leverages - ATTACK_PATH }o--o{ ECOSYSTEM_ENTITY : uses - ATTACK_PATH }o--o{ APPLIED_CONTROL : mitigated_by - ECOSYSTEM_ENTITY }o--|| ENTITY : qualifies + ATTACK_PATH }o--o{ STAKEHOLDER : leverages + STAKEHOLDER }o--o{ APPLIED_CONTROL : reinforces + FEARED_EVENT }o--o{ QUALIFICATION : bears EBIOS_RM_STUDY { string ref_id @@ -1292,10 +1316,9 @@ erDiagram string ref_id string name string description - json qualifications int gravity bool selected - bool justification + string justification } RO_TO { @@ -1306,25 +1329,27 @@ erDiagram int pertinence int activity bool selected - bool justification + string justification } - ECOSYSTEM_ENTITY { + STAKEHOLDER { string category - int dependence - int penetration - int maturity - int trust + int current_dependence + int current_penetration + int current_maturity + int current_trust + int residual_dependence + int residual_penetration + int residual_maturity + int residual_trust bool selected - bool justification + string justification } ATTACK_PATH { string description - int intial_threat_level - int residual_threat_level bool selected - bool justification + string justification } OPERATIONAL_SCENARIO { @@ -1338,5 +1363,7 @@ erDiagram ### Implementation -EBIOS-RM objects are defined within a dedicated Django "application" ebios_rm. - +- EBIOS-RM objects are defined within a dedicated Django "application" ebios_rm. +- There is no object for "strategic scenarios", as they result directly from attack paths and corresponding feared event (which is the title of the strategic scenario). +- the current and residual "criticity" are calculated on stakeholders, so they are not seen as fields. +- \ No newline at end of file