diff --git a/backend/core/migrations/0052_exception_appliedcontrol_exceptions_asset_exceptions_and_more.py b/backend/core/migrations/0052_exception_appliedcontrol_exceptions_asset_exceptions_and_more.py deleted file mode 100644 index 2dd6ea964..000000000 --- a/backend/core/migrations/0052_exception_appliedcontrol_exceptions_asset_exceptions_and_more.py +++ /dev/null @@ -1,167 +0,0 @@ -# Generated by Django 5.1.5 on 2025-02-07 18:27 - -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): - dependencies = [ - ("core", "0051_rename_project_perimeter_alter_perimeter_options_and_more"), - ("iam", "0010_user_preferences"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Exception", - 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=100, - null=True, - verbose_name="Reference ID", - ), - ), - ( - "severity", - models.SmallIntegerField( - choices=[ - (-1, "undefined"), - (0, "low"), - (1, "medium"), - (2, "high"), - (3, "critical"), - ], - default=-1, - verbose_name="Severity", - ), - ), - ( - "status", - models.CharField( - choices=[ - ("undefined", "undefined"), - ("active", "active"), - ("mitigated", "mitigated"), - ("resolved", "resolved"), - ("deprecated", "deprecated"), - ], - default="undefined", - max_length=10, - verbose_name="Status", - ), - ), - ( - "expiration_date", - models.DateField( - help_text="Specify when the exception will no longer apply", - null=True, - verbose_name="Expiration date", - ), - ), - ( - "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", - ), - ), - ( - "owners", - models.ManyToManyField( - blank=True, - related_name="exceptions", - to=settings.AUTH_USER_MODEL, - verbose_name="Owner", - ), - ), - ], - options={ - "ordering": ["name"], - "abstract": False, - }, - ), - migrations.AddField( - model_name="appliedcontrol", - name="exceptions", - field=models.ManyToManyField( - blank=True, - related_name="applied_controls", - to="core.exception", - verbose_name="Exceptions", - ), - ), - migrations.AddField( - model_name="asset", - name="exceptions", - field=models.ManyToManyField( - blank=True, - related_name="assets", - to="core.exception", - verbose_name="Exceptions", - ), - ), - migrations.AddField( - model_name="requirementassessment", - name="exceptions", - field=models.ManyToManyField( - blank=True, - related_name="requirement_assessments", - to="core.exception", - verbose_name="Exceptions", - ), - ), - migrations.AddField( - model_name="riskscenario", - name="exceptions", - field=models.ManyToManyField( - blank=True, - related_name="risk_scenarios", - to="core.exception", - verbose_name="Exceptions", - ), - ), - migrations.AddField( - model_name="vulnerability", - name="exceptions", - field=models.ManyToManyField( - blank=True, - related_name="vulnerabilities", - to="core.exception", - verbose_name="Exceptions", - ), - ), - ] diff --git a/backend/core/migrations/0052_securityexception_appliedcontrol_security_exceptions_and_more.py b/backend/core/migrations/0052_securityexception_appliedcontrol_security_exceptions_and_more.py new file mode 100644 index 000000000..f2221d321 --- /dev/null +++ b/backend/core/migrations/0052_securityexception_appliedcontrol_security_exceptions_and_more.py @@ -0,0 +1,65 @@ +# Generated by Django 5.1.4 on 2025-02-08 17:25 + +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): + + dependencies = [ + ('core', '0051_rename_project_perimeter_alter_perimeter_options_and_more'), + ('iam', '0010_user_preferences'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='SecurityException', + 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=100, null=True, verbose_name='Reference ID')), + ('severity', models.SmallIntegerField(choices=[(-1, 'undefined'), (0, 'low'), (1, 'medium'), (2, 'high'), (3, 'critical')], default=-1, verbose_name='Severity')), + ('status', models.CharField(choices=[('draft', 'draft'), ('in review', 'in review'), ('approved', 'approved'), ('resolved', 'resolved'), ('expired', 'expired'), ('deprecated', 'deprecated')], default='draft', max_length=10, verbose_name='Status')), + ('expiration_date', models.DateField(help_text='Specify when the security exception will no longer apply', null=True, verbose_name='Expiration date')), + ('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')), + ('owners', models.ManyToManyField(blank=True, related_name='security_exceptions', to=settings.AUTH_USER_MODEL, verbose_name='Owner')), + ], + options={ + 'ordering': ['name'], + 'abstract': False, + }, + ), + migrations.AddField( + model_name='appliedcontrol', + name='security_exceptions', + field=models.ManyToManyField(blank=True, related_name='applied_controls', to='core.securityexception', verbose_name='Security exceptions'), + ), + migrations.AddField( + model_name='asset', + name='security_exceptions', + field=models.ManyToManyField(blank=True, related_name='assets', to='core.securityexception', verbose_name='Security exceptions'), + ), + migrations.AddField( + model_name='requirementassessment', + name='security_exceptions', + field=models.ManyToManyField(blank=True, related_name='requirement_assessments', to='core.securityexception', verbose_name='Security exceptions'), + ), + migrations.AddField( + model_name='riskscenario', + name='security_exceptions', + field=models.ManyToManyField(blank=True, related_name='risk_scenarios', to='core.securityexception', verbose_name='Security exceptions'), + ), + migrations.AddField( + model_name='vulnerability', + name='security_exceptions', + field=models.ManyToManyField(blank=True, related_name='vulnerabilities', to='core.securityexception', verbose_name='Security exceptions'), + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index b53d3a6c2..cf46a62e5 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1393,7 +1393,7 @@ def __str__(self): return self.folder.name + "/" + self.name -class Exception(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): +class SecurityException(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): class Severity(models.IntegerChoices): UNDEFINED = -1, "undefined" LOW = 0, "low" @@ -1402,10 +1402,11 @@ class Severity(models.IntegerChoices): CRITICAL = 3, "critical" class Status(models.TextChoices): - UNDEFINED = "undefined", "undefined" - ACTIVE = "active", "active" - MITIGATED = "mitigated", "mitigated" + DRAFT = "draft", "draft" + IN_REVIEW = "in review", "in review" + APPROVED = "approved", "approved" RESOLVED = "resolved", "resolved" + EXPIRED = "expired", "expired" DEPRECATED = "deprecated", "deprecated" ref_id = models.CharField( @@ -1417,11 +1418,12 @@ class Status(models.TextChoices): status = models.CharField( verbose_name="Status", choices=Status.choices, - default=Status.UNDEFINED, + null=False, + default=Status.DRAFT, max_length=10, ) expiration_date = models.DateField( - help_text="Specify when the exception will no longer apply", + help_text="Specify when the security exception will no longer apply", null=True, verbose_name="Expiration date", ) @@ -1429,7 +1431,7 @@ class Status(models.TextChoices): User, blank=True, verbose_name="Owner", - related_name="exceptions", + related_name="security_exceptions", ) fields_to_check = ["name"] @@ -1561,10 +1563,10 @@ class Type(models.TextChoices): verbose_name=_("Owner"), related_name="assets", ) - exceptions = models.ManyToManyField( - Exception, + security_exceptions = models.ManyToManyField( + SecurityException, blank=True, - verbose_name="Exceptions", + verbose_name="Security exceptions", related_name="assets", ) @@ -1912,10 +1914,10 @@ class Status(models.TextChoices): MaxValueValidator(100, message="Progress cannot be more than 100"), ], ) - exceptions = models.ManyToManyField( - Exception, + security_exceptions = models.ManyToManyField( + SecurityException, blank=True, - verbose_name="Exceptions", + verbose_name="Security exceptions", related_name="applied_controls", ) @@ -2063,10 +2065,10 @@ class Status(models.TextChoices): verbose_name=_("Applied controls"), related_name="vulnerabilities", ) - exceptions = models.ManyToManyField( - Exception, + security_exceptions = models.ManyToManyField( + SecurityException, blank=True, - verbose_name="Exceptions", + verbose_name="Security exceptions", related_name="vulnerabilities", ) @@ -2653,10 +2655,10 @@ class RiskScenario(NameDescriptionMixin): justification = models.CharField( max_length=500, blank=True, null=True, verbose_name=_("Justification") ) - exceptions = models.ManyToManyField( - Exception, + security_exceptions = models.ManyToManyField( + SecurityException, blank=True, - verbose_name="Exceptions", + verbose_name="Security exceptions", related_name="risk_scenarios", ) @@ -3435,10 +3437,10 @@ class Result(models.TextChoices): null=True, verbose_name=_("Answer"), ) - exceptions = models.ManyToManyField( - Exception, + security_exceptions = models.ManyToManyField( + SecurityException, blank=True, - verbose_name="Exceptions", + verbose_name="Security exceptions", related_name="requirement_assessments", ) diff --git a/backend/core/serializers.py b/backend/core/serializers.py index dd6232780..190097eb2 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -157,7 +157,7 @@ class VulnerabilityReadSerializer(BaseModelSerializer): folder = FieldsRelatedField() applied_controls = FieldsRelatedField(many=True) filtering_labels = FieldsRelatedField(["folder"], many=True) - exceptions = FieldsRelatedField(many=True) + security_exceptions = FieldsRelatedField(many=True) class Meta: model = Vulnerability @@ -330,7 +330,7 @@ class AssetReadSerializer(AssetWriteSerializer): ) filtering_labels = FieldsRelatedField(["folder"], many=True) type = serializers.CharField(source="get_type_display") - exceptions = FieldsRelatedField(many=True) + security_exceptions = FieldsRelatedField(many=True) class AssetImportExportSerializer(BaseModelSerializer): @@ -483,7 +483,7 @@ class RiskScenarioReadSerializer(RiskScenarioWriteSerializer): existing_applied_controls = FieldsRelatedField(many=True) owner = FieldsRelatedField(many=True) - exceptions = FieldsRelatedField(many=True) + security_exceptions = FieldsRelatedField(many=True) class RiskScenarioImportExportSerializer(BaseModelSerializer): @@ -544,7 +544,7 @@ class AppliedControlReadSerializer(AppliedControlWriteSerializer): ranking_score = serializers.IntegerField(source="get_ranking_score") owner = FieldsRelatedField(many=True) - exceptions = FieldsRelatedField(many=True) + security_exceptions = FieldsRelatedField(many=True) # These properties shouldn't be displayed in the frontend detail view as they are simple derivations from fields already displayed in the detail view. # has_evidences = serializers.BooleanField() # eta_missed = serializers.BooleanField() @@ -966,7 +966,7 @@ class Meta: folder = FieldsRelatedField() assessable = serializers.BooleanField(source="requirement.assessable") requirement = FilteredNodeSerializer() - exceptions = FieldsRelatedField(many=True) + security_exceptions = FieldsRelatedField(many=True) class Meta: model = RequirementAssessment @@ -1089,21 +1089,21 @@ class QualificationWriteSerializer(QualificationReadSerializer): pass -class ExceptionWriteSerializer(BaseModelSerializer): +class SecurityExceptionWriteSerializer(BaseModelSerializer): requirement_assessments = serializers.PrimaryKeyRelatedField( many=True, queryset=RequirementAssessment.objects.all(), required=False ) class Meta: - model = Exception + model = SecurityException fields = "__all__" -class ExceptionReadSerializer(BaseModelSerializer): +class SecurityExceptionReadSerializer(BaseModelSerializer): folder = FieldsRelatedField() owners = FieldsRelatedField(many=True) severity = serializers.CharField(source="get_severity_display") class Meta: - model = Exception + model = SecurityException fields = "__all__" diff --git a/backend/core/startup.py b/backend/core/startup.py index 027279447..70e08346f 100644 --- a/backend/core/startup.py +++ b/backend/core/startup.py @@ -47,7 +47,7 @@ "view_operationalscenario", "view_qualification", "view_globalsettings", - "view_exception", + "view_security_exception", ] APPROVER_PERMISSIONS_LIST = [ @@ -84,7 +84,7 @@ "view_operationalscenario", "view_qualification", "view_globalsettings", - "view_exception", + "view_security_exception", ] ANALYST_PERMISSIONS_LIST = [ @@ -192,10 +192,10 @@ "delete_operationalscenario", "view_qualification", "view_globalsettings", - "view_exception", - "add_exception", - "change_exception", - "delete_exception", + "view_security_exception", + "add_security_exception", + "change_security_exception", + "delete_security_exception", ] DOMAIN_MANAGER_PERMISSIONS_LIST = [ @@ -311,10 +311,10 @@ "delete_operationalscenario", "view_qualification", "view_globalsettings", - "view_exception", - "add_exception", - "change_exception", - "delete_exception", + "view_security_exception", + "add_security_exception", + "change_security_exception", + "delete_security_exception", ] ADMINISTRATOR_PERMISSIONS_LIST = [ @@ -461,10 +461,10 @@ "add_qualification", "change_qualification", "delete_qualification", - "view_exception", - "add_exception", - "change_exception", - "delete_exception", + "view_security_exception", + "add_security_exception", + "change_security_exception", + "delete_security_exception", ] THIRD_PARTY_RESPONDENT_PERMISSIONS_LIST = [ diff --git a/backend/core/urls.py b/backend/core/urls.py index e62eee9ee..ac718ecb3 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -72,9 +72,9 @@ basename="qualifications", ) router.register( - r"exceptions", - ExceptionViewSet, - basename="exceptions", + r"security_exceptions", + SecurityExceptionViewSet, + basename="security-exceptions", ) ROUTES = settings.ROUTES diff --git a/backend/core/views.py b/backend/core/views.py index bec35e38d..66c858f03 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -4153,18 +4153,18 @@ def export_mp_csv(request): return response -class ExceptionViewSet(BaseModelViewSet): +class SecurityExceptionViewSet(BaseModelViewSet): """ - API endpoint that allows exceptions to be viewed or edited. + API endpoint that allows security exceptions to be viewed or edited. """ - model = Exception + model = SecurityException filterset_fields = ["requirement_assessments", "risk_scenarios"] @action(detail=False, name="Get severity choices") def severity(self, request): - return Response(dict(Exception.Severity.choices)) + return Response(dict(SecurityException.Severity.choices)) @action(detail=False, name="Get status choices") def status(self, request): - return Response(dict(Exception.Status.choices)) + return Response(dict(SecurityException.Status.choices)) diff --git a/enterprise/frontend/src/lib/components/SideBar/navData.ts b/enterprise/frontend/src/lib/components/SideBar/navData.ts index 8e9f01774..b056fb3d6 100644 --- a/enterprise/frontend/src/lib/components/SideBar/navData.ts +++ b/enterprise/frontend/src/lib/components/SideBar/navData.ts @@ -177,6 +177,11 @@ export const navData = { name: 'riskAcceptances', fa_icon: 'fa-solid fa-signature', href: '/risk-acceptances' + }, + { + name: 'securityExceptions', + fa_icon: 'fa-solid fa-circle-exclamation', + href: '/security-exceptions' } ] }, diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 8287218db..e070d2e0d 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -484,6 +484,8 @@ "toDo": "To do", "inProgress": "In progress", "inReview": "In review", + "approved": "Approved", + "resolved": "Resolved", "deprecated": "Deprecated", "onHold": "On hold", "done": "Done", @@ -1157,8 +1159,8 @@ "recap": "Recap", "sectionMoved": "Section moved here", "more": "More", - "exception": "Exception", - "exceptions": "Exceptions", - "addException": "Add exception", + "securityException": "Exception", + "securityExceptions": "Exceptions", + "addSecurityException": "Add exception", "expirationDate": "Expiration date" } diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 34f27560e..658c084ec 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -480,7 +480,9 @@ "toDo": "À faire", "inProgress": "En cours", "inReview": "En révision", + "approved": "Approuvé", "deprecated": "Déprécié", + "resolved": "Résolu", "onHold": "En attente", "done": "Terminé", "nonCompliant": "Non conforme", @@ -1152,8 +1154,8 @@ "recap": "Recap", "sectionMoved": "Section déplacée ici", "more": "Plus", - "exception": "Exception", - "exceptions": "Exceptions", - "addException": "Ajouter une exception", + "securityException": "Exception", + "securityExceptions": "Exceptions", + "addSecurityException": "Ajouter une exception", "expirationDate": "Date d'expiration" } diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte index 2a2ddbef8..f0cd85a25 100644 --- a/frontend/src/lib/components/Forms/ModelForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm.svelte @@ -31,7 +31,7 @@ import RoToForm from './ModelForm/RoToForm.svelte'; import StakeholderForm from './ModelForm/StakeholderForm.svelte'; import AttackPathForm from './ModelForm/AttackPathForm.svelte'; - import ExceptionForm from './ModelForm/ExceptionForm.svelte'; + import SecurityExceptionForm from './ModelForm/SecurityExceptionForm.svelte'; import AutocompleteSelect from './AutocompleteSelect.svelte'; @@ -280,8 +280,8 @@ {:else if URLModel === 'operational-scenarios'} - {:else if URLModel === 'exceptions'} - + {:else if URLModel === 'security-exceptions'} + {/if}
{#if closeModal} diff --git a/frontend/src/lib/components/Forms/ModelForm/AppliedControlPolicyForm.svelte b/frontend/src/lib/components/Forms/ModelForm/AppliedControlPolicyForm.svelte index 41c2e4eeb..1e7f13004 100644 --- a/frontend/src/lib/components/Forms/ModelForm/AppliedControlPolicyForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm/AppliedControlPolicyForm.svelte @@ -92,11 +92,11 @@ {#if schema.shape.category}