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 @@