Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion dojo/importers/base_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,10 @@ def sanitize_severity(
There is a simple conversion process to convert any of the following
to a value of Info
- info, informational, Informational, None, none
If not, raise a ValidationError explaining as such
Severity matching is case-insensitive, so values such as "medium" or
"CRITICAL" are normalized to their supported form ("Medium", "Critical").
If the severity is still not recognized, raise a ValidationError
explaining as such
"""
# Checks around Informational/Info severity
starts_with_info = finding.severity.lower().startswith("info")
Expand All @@ -918,6 +921,11 @@ def sanitize_severity(
if not_info and (starts_with_info or lower_none):
# Correct the severity
finding.severity = "Info"
# Normalize the case of any remaining severity so that a value like
# "medium" is accepted as the supported "Medium" instead of rejected
if finding.severity not in SEVERITIES:
canonical_severities = {severity.lower(): severity for severity in SEVERITIES}
finding.severity = canonical_severities.get(finding.severity.lower(), finding.severity)
# Ensure the final severity is one of the supported options
if finding.severity not in SEVERITIES:
msg = (
Expand Down
50 changes: 50 additions & 0 deletions unittests/test_importers_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from django.core.exceptions import ValidationError
from django.utils import timezone
from parameterized import parameterized
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient

Expand Down Expand Up @@ -1607,3 +1608,52 @@ def test_batch_push_to_jira_last_finding_ungrouped(self):
flags[ungrouped_db.id],
msg=f"ungrouped finding must be pushed individually, dispatched with push_to_jira={flags[ungrouped_db.id]}",
)


class TestSanitizeSeverity(DojoTestCase):

"""Unit tests for BaseImporter.sanitize_severity severity normalization."""

# Regression: findings imported with a lowercase severity such as "medium"
# (e.g. via Generic Findings Import) were rejected with
# 'Finding severity "medium" is not supported' because only info/none
# variants were case-normalized. Severity matching must be case-insensitive.

def setUp(self):
# sanitize_severity does not use importer state, so it is exercised on a
# bare importer instance to keep this a pure unit test with no DB setup.
self.importer = DefaultImporter.__new__(DefaultImporter)

@parameterized.expand([
# Failing cases before the fix (wrong-case supported severities)
("medium", "Medium"),
("MEDIUM", "Medium"),
("high", "High"),
("HIGH", "High"),
("critical", "Critical"),
("low", "Low"),
# Control cases (already-correct values must be preserved)
("Medium", "Medium"),
("Critical", "Critical"),
("Info", "Info"),
# Existing info/none normalization must keep working
("info", "Info"),
("informational", "Info"),
("none", "Info"),
])
def test_sanitize_severity_is_case_insensitive(self, submitted, expected):
finding = Finding(severity=submitted)
result = self.importer.sanitize_severity(finding)
self.assertEqual(
result.severity, expected,
msg=f"expected severity={expected} for input {submitted!r}, got {result.severity!r}",
)
self.assertEqual(
result.numerical_severity, Finding.get_numerical_severity(expected),
msg=f"numerical_severity not set for normalized severity {expected}",
)

def test_sanitize_severity_rejects_genuinely_unsupported(self):
finding = Finding(severity="bogus")
with self.assertRaises(ValidationError):
self.importer.sanitize_severity(finding)