From 3ff35f6f97660b4d34d3887dc7438acd3cafdb41 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Tue, 20 Aug 2024 19:02:15 +0200 Subject: [PATCH 001/142] feat: start tprm app and create Entity object --- backend/ciso_assistant/settings.py | 1 + backend/core/apps.py | 3 ++ backend/core/urls.py | 4 +++ backend/tprm/__init__.py | 0 backend/tprm/apps.py | 6 ++++ backend/tprm/migrations/0001_initial.py | 37 +++++++++++++++++++++++++ backend/tprm/migrations/__init__.py | 0 backend/tprm/models.py | 25 +++++++++++++++++ backend/tprm/serializers.py | 35 +++++++++++++++++++++++ backend/tprm/tests.py | 3 ++ backend/tprm/views.py | 13 +++++++++ 11 files changed, 127 insertions(+) create mode 100644 backend/tprm/__init__.py create mode 100644 backend/tprm/apps.py create mode 100644 backend/tprm/migrations/0001_initial.py create mode 100644 backend/tprm/migrations/__init__.py create mode 100644 backend/tprm/models.py create mode 100644 backend/tprm/serializers.py create mode 100644 backend/tprm/tests.py create mode 100644 backend/tprm/views.py diff --git a/backend/ciso_assistant/settings.py b/backend/ciso_assistant/settings.py index e97e8257d..2e6aab826 100644 --- a/backend/ciso_assistant/settings.py +++ b/backend/ciso_assistant/settings.py @@ -125,6 +125,7 @@ def set_ciso_assistant_url(_, __, event_dict): "global_settings", "core", "cal", + "tprm", "django_filters", # "debug_toolbar", "library", diff --git a/backend/core/apps.py b/backend/core/apps.py index 20c023035..86c9f476f 100644 --- a/backend/core/apps.py +++ b/backend/core/apps.py @@ -260,6 +260,9 @@ "change_globalsettings", "view_requirementmappingset", "view_requirementmapping", + "add_entity", + "view_entity", + "delete_entity", ] diff --git a/backend/core/urls.py b/backend/core/urls.py index eee2be01e..073bde470 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -1,5 +1,8 @@ from iam.sso.views import SSOSettingsViewSet from .views import * +from tprm.views import ( + EntityViewSet, +) from library.views import StoredLibraryViewSet, LoadedLibraryViewSet from iam.sso.saml.views import FinishACSView @@ -11,6 +14,7 @@ router = routers.DefaultRouter() router.register(r"folders", FolderViewSet, basename="folders") +router.register(r"entities", EntityViewSet, basename="entities") router.register(r"projects", ProjectViewSet, basename="projects") router.register(r"risk-matrices", RiskMatrixViewSet, basename="risk-matrices") router.register(r"risk-assessments", RiskAssessmentViewSet, basename="risk-assessments") diff --git a/backend/tprm/__init__.py b/backend/tprm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tprm/apps.py b/backend/tprm/apps.py new file mode 100644 index 000000000..08bce33e2 --- /dev/null +++ b/backend/tprm/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TprmConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'tprm' diff --git a/backend/tprm/migrations/0001_initial.py b/backend/tprm/migrations/0001_initial.py new file mode 100644 index 000000000..583bd8964 --- /dev/null +++ b/backend/tprm/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 5.0.4 on 2024-08-20 15:43 + +import django.db.models.deletion +import iam.models +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('iam', '0005_alter_user_managers'), + ] + + operations = [ + migrations.CreateModel( + name='Entity', + 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')), + ('mission', models.TextField(blank=True, null=True)), + ('reference_link', models.URLField(blank=True, null=True)), + ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), + ('owned_folders', models.ManyToManyField(blank=True, related_name='owner', to='iam.folder', verbose_name='Owned folders')), + ], + options={ + 'verbose_name': 'Entity', + 'verbose_name_plural': 'Entities', + }, + ), + ] diff --git a/backend/tprm/migrations/__init__.py b/backend/tprm/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tprm/models.py b/backend/tprm/models.py new file mode 100644 index 000000000..adeac0b4f --- /dev/null +++ b/backend/tprm/models.py @@ -0,0 +1,25 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from core.base_models import NameDescriptionMixin +from iam.models import FolderMixin, PublishInRootFolderMixin + + +class Entity(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): + """ + An entity represents a legal entity, a corporate body, an administrative body, an association + """ + + mission = models.TextField(blank=True, null=True) + reference_link = models.URLField(blank=True, null=True) + owned_folders = models.ManyToManyField( + "iam.Folder", + related_name="owner", + blank=True, + verbose_name=_("Owned folders"), + ) + + fields_to_check = ["name"] + + class Meta: + verbose_name = _("Entity") + verbose_name_plural = _("Entities") diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py new file mode 100644 index 000000000..3b54d1905 --- /dev/null +++ b/backend/tprm/serializers.py @@ -0,0 +1,35 @@ +from core.serializer_fields import FieldsRelatedField +from core.serializers import BaseModelSerializer +from iam.models import Folder +from tprm.models import Entity +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ + + +class EntityReadSerializer(BaseModelSerializer): + folder = FieldsRelatedField() + owned_folders = FieldsRelatedField(many=True) + + class Meta: + model = Entity + exclude = [] + + +class EntityWriteSerializer(BaseModelSerializer): + owned_folders = serializers.PrimaryKeyRelatedField( + queryset=Folder.objects.filter(owner=None), + many=True, + required=False, + ) + + def validate_owned_folders(self, owned_folders): + for folder in owned_folders: + if folder.owner.exists(): + raise serializers.ValidationError( + _("Folder {} is already owned by another entity").format(folder) + ) + return owned_folders + + class Meta: + model = Entity + exclude = [] \ No newline at end of file diff --git a/backend/tprm/tests.py b/backend/tprm/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/backend/tprm/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/tprm/views.py b/backend/tprm/views.py new file mode 100644 index 000000000..4c621375a --- /dev/null +++ b/backend/tprm/views.py @@ -0,0 +1,13 @@ +from core.views import BaseModelViewSet as AbstractBaseModelViewSet +from tprm.models import Entity + +class BaseModelViewSet(AbstractBaseModelViewSet): + serializers_module = "tprm.serializers" + +# Create your views here. +class EntityViewSet(BaseModelViewSet): + """ + API endpoint that allows folders to be viewed or edited. + """ + + model = Entity \ No newline at end of file From c1bf57d85468cb7fc25146749eaa890126210a08 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 21 Aug 2024 12:29:33 +0200 Subject: [PATCH 002/142] feat: add representative model --- backend/core/apps.py | 5 +++++ backend/core/urls.py | 2 ++ backend/iam/models.py | 1 + backend/tprm/migrations/0001_initial.py | 21 ++++++++++++++++++++- backend/tprm/models.py | 24 +++++++++++++++++++++++- backend/tprm/serializers.py | 23 +++++++++++++++++++++-- backend/tprm/views.py | 15 +++++++++++++-- 7 files changed, 85 insertions(+), 6 deletions(-) diff --git a/backend/core/apps.py b/backend/core/apps.py index 86c9f476f..293c1b9e4 100644 --- a/backend/core/apps.py +++ b/backend/core/apps.py @@ -261,8 +261,13 @@ "view_requirementmappingset", "view_requirementmapping", "add_entity", + "change_entity", "view_entity", "delete_entity", + "add_representative", + "change_representative", + "view_representative", + "delete_representative", ] diff --git a/backend/core/urls.py b/backend/core/urls.py index 073bde470..22cb9c1f1 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -2,6 +2,7 @@ from .views import * from tprm.views import ( EntityViewSet, + RepresentativeViewSet, ) from library.views import StoredLibraryViewSet, LoadedLibraryViewSet from iam.sso.saml.views import FinishACSView @@ -15,6 +16,7 @@ router = routers.DefaultRouter() router.register(r"folders", FolderViewSet, basename="folders") router.register(r"entities", EntityViewSet, basename="entities") +router.register(r"representatives", RepresentativeViewSet, basename="representatives") router.register(r"projects", ProjectViewSet, basename="projects") router.register(r"risk-matrices", RiskMatrixViewSet, basename="risk-matrices") router.register(r"risk-assessments", RiskAssessmentViewSet, basename="risk-assessments") diff --git a/backend/iam/models.py b/backend/iam/models.py index 0d46c8f20..b1db1087b 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -149,6 +149,7 @@ def get_folder(obj: Any): ["folder"], ["parent_folder"], ["project", "folder"], + ["entity", "folder"], ["risk_assessment", "project", "folder"], ["risk_scenario", "risk_assessment", "project", "folder"], ["compliance_assessment", "project", "folder"], diff --git a/backend/tprm/migrations/0001_initial.py b/backend/tprm/migrations/0001_initial.py index 583bd8964..d1a2645f5 100644 --- a/backend/tprm/migrations/0001_initial.py +++ b/backend/tprm/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.4 on 2024-08-20 15:43 +# Generated by Django 5.0.4 on 2024-08-21 10:29 import django.db.models.deletion import iam.models @@ -34,4 +34,23 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Entities', }, ), + migrations.CreateModel( + name='Representative', + 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')), + ('email', models.EmailField(max_length=254, unique=True)), + ('first_name', models.CharField(blank=True, max_length=255)), + ('last_name', models.CharField(blank=True, max_length=255)), + ('phone', models.CharField(blank=True, max_length=255)), + ('role', models.CharField(blank=True, max_length=255)), + ('description', models.TextField(blank=True)), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='tprm.entity', verbose_name='Entity')), + ], + options={ + 'abstract': False, + }, + ), ] diff --git a/backend/tprm/models.py b/backend/tprm/models.py index adeac0b4f..f39583a8b 100644 --- a/backend/tprm/models.py +++ b/backend/tprm/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 +from core.base_models import NameDescriptionMixin, AbstractBaseModel from iam.models import FolderMixin, PublishInRootFolderMixin @@ -23,3 +23,25 @@ class Entity(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): class Meta: verbose_name = _("Entity") verbose_name_plural = _("Entities") + + +class Representative(AbstractBaseModel): + """ + This represents a person that is linked to an entity (typically an employee), + and that is relevant for the main entity, like a contact person for an assessment + """ + + entity = models.ForeignKey( + Entity, + on_delete=models.CASCADE, + related_name="representatives", + verbose_name=_("Entity"), + ) + email = models.EmailField(unique=True) + first_name = models.CharField(max_length=255, blank=True) + last_name = models.CharField(max_length=255, blank=True) + phone = models.CharField(max_length=255, blank=True) + role = models.CharField(max_length=255, blank=True) + description = models.TextField(blank=True) + + fields_to_check = ["name"] diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py index 3b54d1905..9bdb7b91c 100644 --- a/backend/tprm/serializers.py +++ b/backend/tprm/serializers.py @@ -1,7 +1,7 @@ from core.serializer_fields import FieldsRelatedField from core.serializers import BaseModelSerializer from iam.models import Folder -from tprm.models import Entity +from tprm.models import Entity, Representative from rest_framework import serializers from django.utils.translation import gettext_lazy as _ @@ -32,4 +32,23 @@ def validate_owned_folders(self, owned_folders): class Meta: model = Entity - exclude = [] \ No newline at end of file + exclude = [] + + +class RepresentativeReadSerializer(BaseModelSerializer): + entity = FieldsRelatedField() + + class Meta: + model = Representative + exclude = [] + + +class RepresentativeWriteSerializer(BaseModelSerializer): + entity = serializers.PrimaryKeyRelatedField( + queryset=Entity.objects.all(), + required=True, + ) + + class Meta: + model = Representative + exclude = [] diff --git a/backend/tprm/views.py b/backend/tprm/views.py index 4c621375a..89596923e 100644 --- a/backend/tprm/views.py +++ b/backend/tprm/views.py @@ -1,5 +1,5 @@ from core.views import BaseModelViewSet as AbstractBaseModelViewSet -from tprm.models import Entity +from tprm.models import Entity, Representative class BaseModelViewSet(AbstractBaseModelViewSet): serializers_module = "tprm.serializers" @@ -10,4 +10,15 @@ class EntityViewSet(BaseModelViewSet): API endpoint that allows folders to be viewed or edited. """ - model = Entity \ No newline at end of file + model = Entity + + +class RepresentativeViewSet(BaseModelViewSet): + """ + API endpoint that allows folders to be viewed or edited. + """ + + model = Representative + filterset_fields = [ + "entity" + ] \ No newline at end of file From 43f0d25aa354aaa39def2dffd0db4b185078992f Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 21 Aug 2024 12:57:02 +0200 Subject: [PATCH 003/142] feat: create solution and product objects --- backend/core/apps.py | 8 +++ backend/core/urls.py | 4 ++ backend/iam/models.py | 1 + .../tprm/migrations/0002_solution_product.py | 49 +++++++++++++++++++ backend/tprm/models.py | 42 ++++++++++++++++ backend/tprm/serializers.py | 41 +++++++++++++++- backend/tprm/views.py | 24 +++++++-- 7 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 backend/tprm/migrations/0002_solution_product.py diff --git a/backend/core/apps.py b/backend/core/apps.py index 293c1b9e4..ade819d20 100644 --- a/backend/core/apps.py +++ b/backend/core/apps.py @@ -268,6 +268,14 @@ "change_representative", "view_representative", "delete_representative", + "add_solution", + "change_solution", + "view_solution", + "delete_solution", + "add_product", + "change_product", + "view_product", + "delete_product", ] diff --git a/backend/core/urls.py b/backend/core/urls.py index 22cb9c1f1..95924df4b 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -3,6 +3,8 @@ from tprm.views import ( EntityViewSet, RepresentativeViewSet, + SolutionViewSet, + ProductViewSet, ) from library.views import StoredLibraryViewSet, LoadedLibraryViewSet from iam.sso.saml.views import FinishACSView @@ -16,6 +18,8 @@ router = routers.DefaultRouter() router.register(r"folders", FolderViewSet, basename="folders") router.register(r"entities", EntityViewSet, basename="entities") +router.register(r"solutions", SolutionViewSet, basename="solutions") +router.register(r"products", ProductViewSet, basename="products") router.register(r"representatives", RepresentativeViewSet, basename="representatives") router.register(r"projects", ProjectViewSet, basename="projects") router.register(r"risk-matrices", RiskMatrixViewSet, basename="risk-matrices") diff --git a/backend/iam/models.py b/backend/iam/models.py index b1db1087b..d18779f63 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -150,6 +150,7 @@ def get_folder(obj: Any): ["parent_folder"], ["project", "folder"], ["entity", "folder"], + ["solution", "entity", "folder"], ["risk_assessment", "project", "folder"], ["risk_scenario", "risk_assessment", "project", "folder"], ["compliance_assessment", "project", "folder"], diff --git a/backend/tprm/migrations/0002_solution_product.py b/backend/tprm/migrations/0002_solution_product.py new file mode 100644 index 000000000..e5b0c66e1 --- /dev/null +++ b/backend/tprm/migrations/0002_solution_product.py @@ -0,0 +1,49 @@ +# Generated by Django 5.0.4 on 2024-08-21 10:52 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tprm', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Solution', + 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(blank=True, max_length=255)), + ('criticality', models.IntegerField(default=0, verbose_name='Criticality')), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='tprm.entity', verbose_name='Entity')), + ], + options={ + 'verbose_name': 'Solution', + 'verbose_name_plural': 'Solutions', + }, + ), + migrations.CreateModel( + name='Product', + 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')), + ('solution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='tprm.solution', verbose_name='Solution')), + ], + options={ + 'verbose_name': 'Product', + 'verbose_name_plural': 'Products', + }, + ), + ] diff --git a/backend/tprm/models.py b/backend/tprm/models.py index f39583a8b..3f19aa9e8 100644 --- a/backend/tprm/models.py +++ b/backend/tprm/models.py @@ -45,3 +45,45 @@ class Representative(AbstractBaseModel): description = models.TextField(blank=True) fields_to_check = ["name"] + +class Solution(NameDescriptionMixin): + """ + A solution represents a product or service that is offered by an entity + """ + + entity = models.ForeignKey( + Entity, + on_delete=models.CASCADE, + related_name="solutions", + verbose_name=_("Entity"), + ) + ref_id = models.CharField(max_length=255, blank=True) + criticality = models.IntegerField( + default=0, + verbose_name=_("Criticality") + ) + + fields_to_check = ["name"] + + class Meta: + verbose_name = _("Solution") + verbose_name_plural = _("Solutions") + + +class Product(NameDescriptionMixin): + """ + Product offered in a solution + """ + + solution = models.ForeignKey( + Solution, + on_delete=models.CASCADE, + related_name="products", + verbose_name=_("Solution"), + ) + + fields_to_check = ["name"] + + class Meta: + verbose_name = _("Product") + verbose_name_plural = _("Products") \ No newline at end of file diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py index 9bdb7b91c..60768e38c 100644 --- a/backend/tprm/serializers.py +++ b/backend/tprm/serializers.py @@ -1,7 +1,7 @@ from core.serializer_fields import FieldsRelatedField from core.serializers import BaseModelSerializer from iam.models import Folder -from tprm.models import Entity, Representative +from tprm.models import Entity, Representative, Solution, Product from rest_framework import serializers from django.utils.translation import gettext_lazy as _ @@ -52,3 +52,42 @@ class RepresentativeWriteSerializer(BaseModelSerializer): class Meta: model = Representative exclude = [] + + +class SolutionReadSerializer(BaseModelSerializer): + entity = FieldsRelatedField() + products = FieldsRelatedField(many=True) + + class Meta: + model = Solution + exclude = [] + + +class SolutionWriteSerializer(BaseModelSerializer): + entity = serializers.PrimaryKeyRelatedField( + queryset=Entity.objects.all(), + required=True, + ) + + class Meta: + model = Solution + exclude = [] + + +class ProductReadSerializer(BaseModelSerializer): + solution = FieldsRelatedField() + + class Meta: + model = Product + exclude = [] + + +class ProductWriteSerializer(BaseModelSerializer): + solution = serializers.PrimaryKeyRelatedField( + queryset=Solution.objects.all(), + required=True, + ) + + class Meta: + model = Product + exclude = [] diff --git a/backend/tprm/views.py b/backend/tprm/views.py index 89596923e..c7bae8840 100644 --- a/backend/tprm/views.py +++ b/backend/tprm/views.py @@ -1,5 +1,5 @@ from core.views import BaseModelViewSet as AbstractBaseModelViewSet -from tprm.models import Entity, Representative +from tprm.models import Entity, Representative, Solution, Product class BaseModelViewSet(AbstractBaseModelViewSet): serializers_module = "tprm.serializers" @@ -7,7 +7,7 @@ class BaseModelViewSet(AbstractBaseModelViewSet): # Create your views here. class EntityViewSet(BaseModelViewSet): """ - API endpoint that allows folders to be viewed or edited. + API endpoint that allows entities to be viewed or edited. """ model = Entity @@ -15,10 +15,26 @@ class EntityViewSet(BaseModelViewSet): class RepresentativeViewSet(BaseModelViewSet): """ - API endpoint that allows folders to be viewed or edited. + API endpoint that allows representatives to be viewed or edited. """ model = Representative filterset_fields = [ "entity" - ] \ No newline at end of file + ] + + +class SolutionViewSet(BaseModelViewSet): + """ + API endpoint that allows solutions to be viewed or edited. + """ + + model = Solution + + +class ProductViewSet(BaseModelViewSet): + """ + API endpoint that allows products to be viewed or edited. + """ + + model = Product \ No newline at end of file From 55a7784d242af798765ed68d95171b5d497563b6 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 21 Aug 2024 13:31:56 +0200 Subject: [PATCH 004/142] feat: update entity/solution relationship --- backend/iam/models.py | 3 +- backend/tprm/migrations/0001_initial.py | 37 +++++++++++++- .../tprm/migrations/0002_solution_product.py | 49 ------------------- backend/tprm/models.py | 12 +++-- backend/tprm/serializers.py | 9 +++- 5 files changed, 54 insertions(+), 56 deletions(-) delete mode 100644 backend/tprm/migrations/0002_solution_product.py diff --git a/backend/iam/models.py b/backend/iam/models.py index d18779f63..c52d32cae 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -150,7 +150,8 @@ def get_folder(obj: Any): ["parent_folder"], ["project", "folder"], ["entity", "folder"], - ["solution", "entity", "folder"], + ["provider_entity", "folder"], + ["solution", "provider_entity", "folder"], ["risk_assessment", "project", "folder"], ["risk_scenario", "risk_assessment", "project", "folder"], ["compliance_assessment", "project", "folder"], diff --git a/backend/tprm/migrations/0001_initial.py b/backend/tprm/migrations/0001_initial.py index d1a2645f5..6fe64226c 100644 --- a/backend/tprm/migrations/0001_initial.py +++ b/backend/tprm/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.4 on 2024-08-21 10:29 +# Generated by Django 5.0.4 on 2024-08-21 11:31 import django.db.models.deletion import iam.models @@ -53,4 +53,39 @@ class Migration(migrations.Migration): 'abstract': False, }, ), + migrations.CreateModel( + name='Solution', + 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(blank=True, max_length=255)), + ('criticality', models.IntegerField(default=0, verbose_name='Criticality')), + ('provider_entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='provided_solutions', to='tprm.entity', verbose_name='Provider entity')), + ('recipient_entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_solutions', to='tprm.entity', verbose_name='Recipient entity')), + ], + options={ + 'verbose_name': 'Solution', + 'verbose_name_plural': 'Solutions', + }, + ), + migrations.CreateModel( + name='Product', + 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')), + ('solution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='tprm.solution', verbose_name='Solution')), + ], + options={ + 'verbose_name': 'Product', + 'verbose_name_plural': 'Products', + }, + ), ] diff --git a/backend/tprm/migrations/0002_solution_product.py b/backend/tprm/migrations/0002_solution_product.py deleted file mode 100644 index e5b0c66e1..000000000 --- a/backend/tprm/migrations/0002_solution_product.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 5.0.4 on 2024-08-21 10:52 - -import django.db.models.deletion -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tprm', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Solution', - 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(blank=True, max_length=255)), - ('criticality', models.IntegerField(default=0, verbose_name='Criticality')), - ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='tprm.entity', verbose_name='Entity')), - ], - options={ - 'verbose_name': 'Solution', - 'verbose_name_plural': 'Solutions', - }, - ), - migrations.CreateModel( - name='Product', - 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')), - ('solution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='tprm.solution', verbose_name='Solution')), - ], - options={ - 'verbose_name': 'Product', - 'verbose_name_plural': 'Products', - }, - ), - ] diff --git a/backend/tprm/models.py b/backend/tprm/models.py index 3f19aa9e8..bfce72500 100644 --- a/backend/tprm/models.py +++ b/backend/tprm/models.py @@ -51,11 +51,17 @@ class Solution(NameDescriptionMixin): A solution represents a product or service that is offered by an entity """ - entity = models.ForeignKey( + provider_entity = models.ForeignKey( Entity, on_delete=models.CASCADE, - related_name="solutions", - verbose_name=_("Entity"), + related_name="provided_solutions", + verbose_name=_("Provider entity"), + ) + recipient_entity = models.ForeignKey( + Entity, + on_delete=models.CASCADE, + related_name="received_solutions", + verbose_name=_("Recipient entity"), ) ref_id = models.CharField(max_length=255, blank=True) criticality = models.IntegerField( diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py index 60768e38c..05bc5cf0d 100644 --- a/backend/tprm/serializers.py +++ b/backend/tprm/serializers.py @@ -55,7 +55,8 @@ class Meta: class SolutionReadSerializer(BaseModelSerializer): - entity = FieldsRelatedField() + provider_entity = FieldsRelatedField() + recipient_entity = FieldsRelatedField() products = FieldsRelatedField(many=True) class Meta: @@ -64,7 +65,11 @@ class Meta: class SolutionWriteSerializer(BaseModelSerializer): - entity = serializers.PrimaryKeyRelatedField( + provider_entity = serializers.PrimaryKeyRelatedField( + queryset=Entity.objects.all(), + required=True, + ) + recipient_entity = serializers.PrimaryKeyRelatedField( queryset=Entity.objects.all(), required=True, ) From 0828315eaf6e874fe45ec85460959edd718d8da8 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 21 Aug 2024 15:11:25 +0200 Subject: [PATCH 005/142] feat: add entity assessment object --- backend/core/apps.py | 4 +++ backend/core/models.py | 2 +- backend/core/urls.py | 4 +-- backend/tprm/migrations/0001_initial.py | 35 +++++++++++++++++++- backend/tprm/models.py | 44 +++++++++++++++++++++++++ backend/tprm/serializers.py | 20 +++++++++-- backend/tprm/views.py | 12 +++++-- 7 files changed, 113 insertions(+), 8 deletions(-) diff --git a/backend/core/apps.py b/backend/core/apps.py index ade819d20..2dd7b0614 100644 --- a/backend/core/apps.py +++ b/backend/core/apps.py @@ -276,6 +276,10 @@ "change_product", "view_product", "delete_product", + "add_entityassessment", + "change_entityassessment", + "view_entityassessment", + "delete_entityassessment", ] diff --git a/backend/core/models.py b/backend/core/models.py index bcd7decf6..a3c93d830 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1348,7 +1348,7 @@ class Status(models.TextChoices): DEPRECATED = "deprecated", _("Deprecated") project = models.ForeignKey( - "Project", on_delete=models.CASCADE, verbose_name=_("Project") + Project, on_delete=models.CASCADE, verbose_name=_("Project") ) version = models.CharField( max_length=100, diff --git a/backend/core/urls.py b/backend/core/urls.py index 95924df4b..c9a956124 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -1,13 +1,12 @@ -from iam.sso.views import SSOSettingsViewSet from .views import * from tprm.views import ( EntityViewSet, RepresentativeViewSet, SolutionViewSet, ProductViewSet, + EntityAssessmentViewSet, ) from library.views import StoredLibraryViewSet, LoadedLibraryViewSet -from iam.sso.saml.views import FinishACSView from django.urls import include, path @@ -18,6 +17,7 @@ router = routers.DefaultRouter() router.register(r"folders", FolderViewSet, basename="folders") router.register(r"entities", EntityViewSet, basename="entities") +router.register(r"entity-assessments", EntityAssessmentViewSet, basename="entity-assessments") router.register(r"solutions", SolutionViewSet, basename="solutions") router.register(r"products", ProductViewSet, basename="products") router.register(r"representatives", RepresentativeViewSet, basename="representatives") diff --git a/backend/tprm/migrations/0001_initial.py b/backend/tprm/migrations/0001_initial.py index 6fe64226c..97de82eab 100644 --- a/backend/tprm/migrations/0001_initial.py +++ b/backend/tprm/migrations/0001_initial.py @@ -1,8 +1,9 @@ -# Generated by Django 5.0.4 on 2024-08-21 11:31 +# Generated by Django 5.0.4 on 2024-08-21 13:03 import django.db.models.deletion import iam.models import uuid +from django.conf import settings from django.db import migrations, models @@ -11,7 +12,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('core', '0021_alter_framework_urn_alter_loadedlibrary_urn_and_more'), ('iam', '0005_alter_user_managers'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -34,6 +37,36 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Entities', }, ), + migrations.CreateModel( + name='EntityAssessment', + 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')), + ('version', models.CharField(blank=True, default='1.0', help_text='Version of the compliance assessment (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')), + ('criticality', models.IntegerField(default=0, verbose_name='Criticality')), + ('penetration', models.IntegerField(default=0, verbose_name='Penetration')), + ('dependency', models.IntegerField(default=0, verbose_name='Dependency')), + ('maturity', models.IntegerField(default=0, verbose_name='Maturity')), + ('trust', models.IntegerField(default=0, verbose_name='Trust')), + ('authors', models.ManyToManyField(blank=True, related_name='%(class)s_authors', to=settings.AUTH_USER_MODEL, verbose_name='Authors')), + ('compliance_assessment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.complianceassessment')), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tprm.entity')), + ('evidence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.evidence')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.project', verbose_name='Project')), + ('reviewers', models.ManyToManyField(blank=True, related_name='%(class)s_reviewers', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), + ], + options={ + 'verbose_name': 'Entity assessment', + 'verbose_name_plural': 'Entity assessments', + }, + ), migrations.CreateModel( name='Representative', fields=[ diff --git a/backend/tprm/models.py b/backend/tprm/models.py index bfce72500..d4dad9e84 100644 --- a/backend/tprm/models.py +++ b/backend/tprm/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from core.base_models import NameDescriptionMixin, AbstractBaseModel +from core.models import Assessment, ComplianceAssessment, Evidence from iam.models import FolderMixin, PublishInRootFolderMixin @@ -25,6 +26,49 @@ class Meta: verbose_name_plural = _("Entities") +class EntityAssessment(Assessment): + criticality = models.IntegerField( + default=0, + verbose_name=_("Criticality") + ) + penetration = models.IntegerField( + default=0, + verbose_name=_("Penetration") + ) + dependency = models.IntegerField( + default=0, + verbose_name=_("Dependency") + ) + maturity = models.IntegerField( + default=0, + verbose_name=_("Maturity") + ) + trust = models.IntegerField( + default=0, + verbose_name=_("Trust") + ) + entity = models.ForeignKey( + Entity, + on_delete=models.CASCADE, + ) + compliance_assessment = models.ForeignKey( + ComplianceAssessment, + on_delete=models.SET_NULL, + blank=True, + null=True + ) + evidence = models.ForeignKey( + Evidence, + on_delete=models.SET_NULL, + blank=True, + null=True + ) + + class Meta: + verbose_name = _("Entity assessment") + verbose_name_plural = _("Entity assessments") + + class Representative(AbstractBaseModel): """ This represents a person that is linked to an entity (typically an employee), diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py index 05bc5cf0d..189b79c2c 100644 --- a/backend/tprm/serializers.py +++ b/backend/tprm/serializers.py @@ -1,7 +1,7 @@ from core.serializer_fields import FieldsRelatedField from core.serializers import BaseModelSerializer from iam.models import Folder -from tprm.models import Entity, Representative, Solution, Product +from tprm.models import Entity, Representative, Solution, Product, EntityAssessment from rest_framework import serializers from django.utils.translation import gettext_lazy as _ @@ -33,7 +33,23 @@ def validate_owned_folders(self, owned_folders): class Meta: model = Entity exclude = [] - + + +class EntityAssessmentReadSerializer(BaseModelSerializer): + compliance_assessment = FieldsRelatedField() + evidence = FieldsRelatedField() + + class Meta: + model = EntityAssessment + exclude = [] + + +class EntityAssessmentWriteSerializer(BaseModelSerializer): + + class Meta: + model = EntityAssessment + exclude = [] + class RepresentativeReadSerializer(BaseModelSerializer): entity = FieldsRelatedField() diff --git a/backend/tprm/views.py b/backend/tprm/views.py index c7bae8840..5e0e0fb9e 100644 --- a/backend/tprm/views.py +++ b/backend/tprm/views.py @@ -1,5 +1,5 @@ from core.views import BaseModelViewSet as AbstractBaseModelViewSet -from tprm.models import Entity, Representative, Solution, Product +from tprm.models import Entity, Representative, Solution, Product, EntityAssessment class BaseModelViewSet(AbstractBaseModelViewSet): serializers_module = "tprm.serializers" @@ -11,7 +11,15 @@ class EntityViewSet(BaseModelViewSet): """ model = Entity - + + +class EntityAssessmentViewSet(BaseModelViewSet): + """ + API endpoint that allows entity assessments to be viewed or edited. + """ + + model = EntityAssessment + class RepresentativeViewSet(BaseModelViewSet): """ From d1d625920fc5cc8f47d3e2f56b933a2a38720fce Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 21 Aug 2024 15:13:43 +0200 Subject: [PATCH 006/142] chore: run format --- backend/core/urls.py | 4 +- backend/tprm/apps.py | 4 +- backend/tprm/migrations/0001_initial.py | 381 +++++++++++++++++++----- backend/tprm/models.py | 71 ++--- backend/tprm/serializers.py | 5 +- backend/tprm/views.py | 10 +- 6 files changed, 341 insertions(+), 134 deletions(-) diff --git a/backend/core/urls.py b/backend/core/urls.py index c9a956124..e0f30cdaa 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -17,7 +17,9 @@ router = routers.DefaultRouter() router.register(r"folders", FolderViewSet, basename="folders") router.register(r"entities", EntityViewSet, basename="entities") -router.register(r"entity-assessments", EntityAssessmentViewSet, basename="entity-assessments") +router.register( + r"entity-assessments", EntityAssessmentViewSet, basename="entity-assessments" +) router.register(r"solutions", SolutionViewSet, basename="solutions") router.register(r"products", ProductViewSet, basename="products") router.register(r"representatives", RepresentativeViewSet, basename="representatives") diff --git a/backend/tprm/apps.py b/backend/tprm/apps.py index 08bce33e2..d5883e16c 100644 --- a/backend/tprm/apps.py +++ b/backend/tprm/apps.py @@ -2,5 +2,5 @@ class TprmConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'tprm' + default_auto_field = "django.db.models.BigAutoField" + name = "tprm" diff --git a/backend/tprm/migrations/0001_initial.py b/backend/tprm/migrations/0001_initial.py index 97de82eab..1b23dd2f4 100644 --- a/backend/tprm/migrations/0001_initial.py +++ b/backend/tprm/migrations/0001_initial.py @@ -8,117 +8,346 @@ class Migration(migrations.Migration): - initial = True dependencies = [ - ('core', '0021_alter_framework_urn_alter_loadedlibrary_urn_and_more'), - ('iam', '0005_alter_user_managers'), + ("core", "0021_alter_framework_urn_alter_loadedlibrary_urn_and_more"), + ("iam", "0005_alter_user_managers"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Entity', + name="Entity", 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')), - ('mission', models.TextField(blank=True, null=True)), - ('reference_link', models.URLField(blank=True, null=True)), - ('folder', models.ForeignKey(default=iam.models.Folder.get_root_folder, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder')), - ('owned_folders', models.ManyToManyField(blank=True, related_name='owner', to='iam.folder', verbose_name='Owned folders')), + ( + "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"), + ), + ("mission", models.TextField(blank=True, null=True)), + ("reference_link", models.URLField(blank=True, null=True)), + ( + "folder", + models.ForeignKey( + default=iam.models.Folder.get_root_folder, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + ( + "owned_folders", + models.ManyToManyField( + blank=True, + related_name="owner", + to="iam.folder", + verbose_name="Owned folders", + ), + ), ], options={ - 'verbose_name': 'Entity', - 'verbose_name_plural': 'Entities', + "verbose_name": "Entity", + "verbose_name_plural": "Entities", }, ), migrations.CreateModel( - name='EntityAssessment', + name="EntityAssessment", 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')), - ('version', models.CharField(blank=True, default='1.0', help_text='Version of the compliance assessment (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')), - ('criticality', models.IntegerField(default=0, verbose_name='Criticality')), - ('penetration', models.IntegerField(default=0, verbose_name='Penetration')), - ('dependency', models.IntegerField(default=0, verbose_name='Dependency')), - ('maturity', models.IntegerField(default=0, verbose_name='Maturity')), - ('trust', models.IntegerField(default=0, verbose_name='Trust')), - ('authors', models.ManyToManyField(blank=True, related_name='%(class)s_authors', to=settings.AUTH_USER_MODEL, verbose_name='Authors')), - ('compliance_assessment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.complianceassessment')), - ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tprm.entity')), - ('evidence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.evidence')), - ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.project', verbose_name='Project')), - ('reviewers', models.ManyToManyField(blank=True, related_name='%(class)s_reviewers', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), + ( + "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"), + ), + ( + "version", + models.CharField( + blank=True, + default="1.0", + help_text="Version of the compliance assessment (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", + ), + ), + ( + "criticality", + models.IntegerField(default=0, verbose_name="Criticality"), + ), + ( + "penetration", + models.IntegerField(default=0, verbose_name="Penetration"), + ), + ( + "dependency", + models.IntegerField(default=0, verbose_name="Dependency"), + ), + ("maturity", models.IntegerField(default=0, verbose_name="Maturity")), + ("trust", models.IntegerField(default=0, verbose_name="Trust")), + ( + "authors", + models.ManyToManyField( + blank=True, + related_name="%(class)s_authors", + to=settings.AUTH_USER_MODEL, + verbose_name="Authors", + ), + ), + ( + "compliance_assessment", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="core.complianceassessment", + ), + ), + ( + "entity", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="tprm.entity" + ), + ), + ( + "evidence", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="core.evidence", + ), + ), + ( + "project", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="core.project", + verbose_name="Project", + ), + ), + ( + "reviewers", + models.ManyToManyField( + blank=True, + related_name="%(class)s_reviewers", + to=settings.AUTH_USER_MODEL, + verbose_name="Reviewers", + ), + ), ], options={ - 'verbose_name': 'Entity assessment', - 'verbose_name_plural': 'Entity assessments', + "verbose_name": "Entity assessment", + "verbose_name_plural": "Entity assessments", }, ), migrations.CreateModel( - name='Representative', + name="Representative", 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')), - ('email', models.EmailField(max_length=254, unique=True)), - ('first_name', models.CharField(blank=True, max_length=255)), - ('last_name', models.CharField(blank=True, max_length=255)), - ('phone', models.CharField(blank=True, max_length=255)), - ('role', models.CharField(blank=True, max_length=255)), - ('description', models.TextField(blank=True)), - ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', 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"), + ), + ("email", models.EmailField(max_length=254, unique=True)), + ("first_name", models.CharField(blank=True, max_length=255)), + ("last_name", models.CharField(blank=True, max_length=255)), + ("phone", models.CharField(blank=True, max_length=255)), + ("role", models.CharField(blank=True, max_length=255)), + ("description", models.TextField(blank=True)), + ( + "entity", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="representatives", + to="tprm.entity", + verbose_name="Entity", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='Solution', + name="Solution", 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(blank=True, max_length=255)), - ('criticality', models.IntegerField(default=0, verbose_name='Criticality')), - ('provider_entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='provided_solutions', to='tprm.entity', verbose_name='Provider entity')), - ('recipient_entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_solutions', to='tprm.entity', verbose_name='Recipient 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"), + ), + ("name", models.CharField(max_length=200, verbose_name="Name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ("ref_id", models.CharField(blank=True, max_length=255)), + ( + "criticality", + models.IntegerField(default=0, verbose_name="Criticality"), + ), + ( + "provider_entity", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="provided_solutions", + to="tprm.entity", + verbose_name="Provider entity", + ), + ), + ( + "recipient_entity", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="received_solutions", + to="tprm.entity", + verbose_name="Recipient entity", + ), + ), ], options={ - 'verbose_name': 'Solution', - 'verbose_name_plural': 'Solutions', + "verbose_name": "Solution", + "verbose_name_plural": "Solutions", }, ), migrations.CreateModel( - name='Product', + name="Product", 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')), - ('solution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='tprm.solution', verbose_name='Solution')), + ( + "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"), + ), + ( + "solution", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="products", + to="tprm.solution", + verbose_name="Solution", + ), + ), ], options={ - 'verbose_name': 'Product', - 'verbose_name_plural': 'Products', + "verbose_name": "Product", + "verbose_name_plural": "Products", }, ), ] diff --git a/backend/tprm/models.py b/backend/tprm/models.py index d4dad9e84..5291571c7 100644 --- a/backend/tprm/models.py +++ b/backend/tprm/models.py @@ -9,7 +9,7 @@ class Entity(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): """ An entity represents a legal entity, a corporate body, an administrative body, an association """ - + mission = models.TextField(blank=True, null=True) reference_link = models.URLField(blank=True, null=True) owned_folders = models.ManyToManyField( @@ -18,52 +18,31 @@ class Entity(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): blank=True, verbose_name=_("Owned folders"), ) - + fields_to_check = ["name"] - + class Meta: verbose_name = _("Entity") verbose_name_plural = _("Entities") class EntityAssessment(Assessment): - criticality = models.IntegerField( - default=0, - verbose_name=_("Criticality") - ) - penetration = models.IntegerField( - default=0, - verbose_name=_("Penetration") - ) - dependency = models.IntegerField( - default=0, - verbose_name=_("Dependency") - ) - maturity = models.IntegerField( - default=0, - verbose_name=_("Maturity") - ) - trust = models.IntegerField( - default=0, - verbose_name=_("Trust") - ) + criticality = models.IntegerField(default=0, verbose_name=_("Criticality")) + penetration = models.IntegerField(default=0, verbose_name=_("Penetration")) + dependency = models.IntegerField(default=0, verbose_name=_("Dependency")) + maturity = models.IntegerField(default=0, verbose_name=_("Maturity")) + trust = models.IntegerField(default=0, verbose_name=_("Trust")) entity = models.ForeignKey( Entity, on_delete=models.CASCADE, ) compliance_assessment = models.ForeignKey( - ComplianceAssessment, - on_delete=models.SET_NULL, - blank=True, - null=True + ComplianceAssessment, on_delete=models.SET_NULL, blank=True, null=True ) evidence = models.ForeignKey( - Evidence, - on_delete=models.SET_NULL, - blank=True, - null=True + Evidence, on_delete=models.SET_NULL, blank=True, null=True ) - + class Meta: verbose_name = _("Entity assessment") verbose_name_plural = _("Entity assessments") @@ -71,10 +50,10 @@ class Meta: class Representative(AbstractBaseModel): """ - This represents a person that is linked to an entity (typically an employee), - and that is relevant for the main entity, like a contact person for an assessment + This represents a person that is linked to an entity (typically an employee), + and that is relevant for the main entity, like a contact person for an assessment """ - + entity = models.ForeignKey( Entity, on_delete=models.CASCADE, @@ -87,14 +66,15 @@ class Representative(AbstractBaseModel): phone = models.CharField(max_length=255, blank=True) role = models.CharField(max_length=255, blank=True) description = models.TextField(blank=True) - + fields_to_check = ["name"] + class Solution(NameDescriptionMixin): """ A solution represents a product or service that is offered by an entity """ - + provider_entity = models.ForeignKey( Entity, on_delete=models.CASCADE, @@ -108,13 +88,10 @@ class Solution(NameDescriptionMixin): verbose_name=_("Recipient entity"), ) ref_id = models.CharField(max_length=255, blank=True) - criticality = models.IntegerField( - default=0, - verbose_name=_("Criticality") - ) - + criticality = models.IntegerField(default=0, verbose_name=_("Criticality")) + fields_to_check = ["name"] - + class Meta: verbose_name = _("Solution") verbose_name_plural = _("Solutions") @@ -124,16 +101,16 @@ class Product(NameDescriptionMixin): """ Product offered in a solution """ - + solution = models.ForeignKey( Solution, on_delete=models.CASCADE, related_name="products", verbose_name=_("Solution"), ) - + fields_to_check = ["name"] - + class Meta: verbose_name = _("Product") - verbose_name_plural = _("Products") \ No newline at end of file + verbose_name_plural = _("Products") diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py index 189b79c2c..9298a6507 100644 --- a/backend/tprm/serializers.py +++ b/backend/tprm/serializers.py @@ -21,7 +21,7 @@ class EntityWriteSerializer(BaseModelSerializer): many=True, required=False, ) - + def validate_owned_folders(self, owned_folders): for folder in owned_folders: if folder.owner.exists(): @@ -29,7 +29,7 @@ def validate_owned_folders(self, owned_folders): _("Folder {} is already owned by another entity").format(folder) ) return owned_folders - + class Meta: model = Entity exclude = [] @@ -45,7 +45,6 @@ class Meta: class EntityAssessmentWriteSerializer(BaseModelSerializer): - class Meta: model = EntityAssessment exclude = [] diff --git a/backend/tprm/views.py b/backend/tprm/views.py index 5e0e0fb9e..89b31883e 100644 --- a/backend/tprm/views.py +++ b/backend/tprm/views.py @@ -1,9 +1,11 @@ from core.views import BaseModelViewSet as AbstractBaseModelViewSet from tprm.models import Entity, Representative, Solution, Product, EntityAssessment + class BaseModelViewSet(AbstractBaseModelViewSet): serializers_module = "tprm.serializers" + # Create your views here. class EntityViewSet(BaseModelViewSet): """ @@ -27,9 +29,7 @@ class RepresentativeViewSet(BaseModelViewSet): """ model = Representative - filterset_fields = [ - "entity" - ] + filterset_fields = ["entity"] class SolutionViewSet(BaseModelViewSet): @@ -44,5 +44,5 @@ class ProductViewSet(BaseModelViewSet): """ API endpoint that allows products to be viewed or edited. """ - - model = Product \ No newline at end of file + + model = Product From 1db6567ac322d742fdd2a37245751e9786896007 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 21 Aug 2024 15:26:27 +0200 Subject: [PATCH 007/142] fix: remove null on string-based field --- backend/tprm/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tprm/models.py b/backend/tprm/models.py index 5291571c7..53b95fb3c 100644 --- a/backend/tprm/models.py +++ b/backend/tprm/models.py @@ -10,7 +10,7 @@ class Entity(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): An entity represents a legal entity, a corporate body, an administrative body, an association """ - mission = models.TextField(blank=True, null=True) + mission = models.TextField(blank=True) reference_link = models.URLField(blank=True, null=True) owned_folders = models.ManyToManyField( "iam.Folder", From dd2fbd635a8b1a9acadd0d0c57072138e65fdae9 Mon Sep 17 00:00:00 2001 From: Mohamed-Hacene Date: Wed, 21 Aug 2024 16:49:39 +0200 Subject: [PATCH 008/142] feat: add entities crud in frontend --- backend/core/serializer_fields.py | 1 + frontend/messages/en.json | 8 ++++- .../src/lib/components/Forms/ModelForm.svelte | 35 +++++++++++++++++++ .../src/lib/components/SideBar/navData.ts | 5 +++ frontend/src/lib/utils/crud.ts | 13 ++++++- frontend/src/lib/utils/schemas.ts | 11 +++++- frontend/src/lib/utils/table.ts | 7 ++++ frontend/src/lib/utils/types.ts | 3 +- 8 files changed, 79 insertions(+), 4 deletions(-) diff --git a/backend/core/serializer_fields.py b/backend/core/serializer_fields.py index 0d61a4a30..6884e87e4 100644 --- a/backend/core/serializer_fields.py +++ b/backend/core/serializer_fields.py @@ -27,6 +27,7 @@ def to_representation( res = {"str": str(value)} if value == Folder.get_root_folder(): + res.update({"id": value.id}) return res fields = fields or self.fields diff --git a/frontend/messages/en.json b/frontend/messages/en.json index c4bd1b4db..01a1a6e05 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -660,5 +660,11 @@ "back": "Back", "duplicate": "Duplicate", "duplicateRiskAssessment": "Duplicate the risk assessment", - "size": "Size" + "size": "Size", + "entity": "Entity", + "entities": "Entities", + "addEntity": "Add entity", + "referenceLink": "Reference link", + "mission": "Mission", + "ownedFolders": "Owned domains" } diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte index b8509a64e..5bd08d2af 100644 --- a/frontend/src/lib/components/Forms/ModelForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm.svelte @@ -698,6 +698,41 @@ + {:else if URLModel === 'entities'} + +