From 29b290264f018a27a7cda198ab9941499587dc55 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 2 Dec 2024 11:54:13 +0100 Subject: [PATCH 01/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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