Skip to content

Commit 2b957c3

Browse files
cvss4: UI + rest tests + fixes
1 parent 65ed424 commit 2b957c3

File tree

6 files changed

+59
-28
lines changed

6 files changed

+59
-28
lines changed

dojo/forms.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,9 @@ class FindingForm(forms.ModelForm):
13541354
vulnerability_ids = vulnerability_ids_field
13551355
cvssv3 = forms.CharField(max_length=117, required=False, widget=forms.TextInput(attrs={"class": "cvsscalculator", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false"}))
13561356
cvssv3_score = forms.FloatField(required=False, max_value=10.0, min_value=0.0)
1357+
cvssv4 = forms.CharField(max_length=255, required=False)
1358+
cvssv4_score = forms.FloatField(required=False, max_value=10.0, min_value=0.0)
1359+
13571360
description = forms.CharField(widget=forms.Textarea)
13581361
severity = forms.ChoiceField(
13591362
choices=SEVERITY_CHOICES,
@@ -1385,7 +1388,7 @@ class FindingForm(forms.ModelForm):
13851388

13861389
# the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit
13871390
field_order = ("title", "group", "date", "sla_start_date", "sla_expiration_date", "cwe", "vulnerability_ids", "severity", "cvssv3",
1388-
"cvssv3_score", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", "severity_justification",
1391+
"cvssv3_score", "cvssv4", "cvssv4_score", "description", "mitigation", "impact", "request", "response", "steps_to_reproduce", "severity_justification",
13891392
"endpoints", "endpoints_to_add", "references", "active", "mitigated", "mitigated_by", "verified", "false_p", "duplicate",
13901393
"out_of_scope", "risk_accept", "under_defect_review")
13911394

dojo/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2743,7 +2743,7 @@ def save(self, dedupe_option=True, rules_option=True, product_grading_option=Tru
27432743
try:
27442744
cvss_data = parse_cvss_data(self.cvssv4)
27452745
if cvss_data:
2746-
self.cvssv4 = cvss_data.get("cvssv4_vector")
2746+
self.cvssv4 = cvss_data.get("cvssv4")
27472747
if not self.cvssv4_score:
27482748
self.cvssv4_score = cvss_data.get("cvssv4_score")
27492749

dojo/templates/dojo/view_finding.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,16 +292,16 @@ <h3 class="pull-left finding-title">
292292
<td>
293293
<span class="label severity severity-{{ finding.severity }}">
294294
{% if finding.severity %}
295-
{% if finding.cvssv3 %}
295+
{{ finding.severity_display }}
296+
{% if finding.cvssv4_score %}
296297
<i class="no-italics has-popover" font-style="normal" data-toggle="tooltip" data-placement="bottom" data-container="body" title="" href="#"
297-
data-content="{{ finding.cvssv3 }}">
298+
data-content="{{ finding.cvssv4 }}">
299+
({{ finding.cvssv4_score }}{% if finding.cvssv3_score %},{% else %}){% endif %}</i>
298300
{% endif %}
299-
{{ finding.severity_display }}
300301
{% if finding.cvssv3_score %}
301-
({{ finding.cvssv3_score }})
302-
{% endif %}
303-
{% if finding.cvssv3 %}
304-
</i>
302+
<i class="no-italics has-popover" font-style="normal" data-toggle="tooltip" data-placement="bottom" data-container="body" title="" href="#"
303+
data-content="{{ finding.cvssv3 }}">
304+
{% if not finding.cvssv4_score %}({% endif %}{{ finding.cvssv3_score }})</i>
305305
{% endif %}
306306
{% else %}
307307
Unknown
@@ -760,7 +760,7 @@ <h4 class="has-filters">Similar Findings ({{ similar_findings.paginator.count }}
760760
</div>
761761
{% endif %}
762762
</span>
763-
</div>
763+
</div>
764764
{% endif %}
765765
{% comment %} Add a form to (ab)use to submit any actions related to similar/duplicates as POST requests {% endcomment %}
766766
<form method="post" style="display: none" id="related_action">

dojo/validators.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import re
33
from collections.abc import Callable
44

5-
import cvss.parser
65
from cvss import CVSS2, CVSS3, CVSS4
76
from django.core.exceptions import ValidationError
87

@@ -28,7 +27,8 @@ def tag_validator(value: str | list[str], exception_class: Callable = Validation
2827

2928
def cvss3_validator(value: str | list[str], exception_class: Callable = ValidationError) -> None:
3029
logger.debug("cvss3_validator called with value: %s", value)
31-
cvss_vectors = cvss.parser.parse_cvss_from_text(value)
30+
from dojo.utils import parse_cvss_from_text
31+
cvss_vectors = parse_cvss_from_text(value)
3232
if len(cvss_vectors) > 0:
3333
vector_obj = cvss_vectors[0]
3434

@@ -48,13 +48,14 @@ def cvss3_validator(value: str | list[str], exception_class: Callable = Validati
4848

4949
# Explicitly raise an error if no CVSS vectors are found,
5050
# to avoid 'NoneType' errors during severity processing later.
51-
msg = "No valid CVSS vectors found by cvss.parse_cvss_from_text()"
51+
msg = "No valid CVSS3 vectors found by cvss.parse_cvss_from_text()"
5252
raise exception_class(msg)
5353

5454

5555
def cvss4_validator(value: str | list[str], exception_class: Callable = ValidationError) -> None:
5656
logger.debug("cvss4_validator called with value: %s", value)
57-
cvss_vectors = cvss.parser.parse_cvss_from_text(value)
57+
from dojo.utils import parse_cvss_from_text
58+
cvss_vectors = parse_cvss_from_text(value)
5859
if len(cvss_vectors) > 0:
5960
vector_obj = cvss_vectors[0]
6061

@@ -74,5 +75,5 @@ def cvss4_validator(value: str | list[str], exception_class: Callable = Validati
7475

7576
# Explicitly raise an error if no CVSS vectors are found,
7677
# to avoid 'NoneType' errors during severity processing later.
77-
msg = "No valid CVSS vectors found by cvss.parse_cvss_from_text()"
78+
msg = "No valid CVSS4 vectors found by cvss.parse_cvss_from_text()"
7879
raise exception_class(msg)

unittests/test_metrics_queries.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def test_finding_queries_no_data(self):
7575

7676
@patch("django.utils.timezone.now")
7777
def test_finding_queries(self, mock_timezone):
78+
self.maxDiff = 999999999
7879
mock_datetime = datetime(2020, 12, 9, tzinfo=UTC)
7980
mock_timezone.return_value = mock_datetime
8081

unittests/test_rest_framework.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,41 +1287,67 @@ def test_cvss3_validation(self):
12871287
result = self.client.patch(self.url + "2/", data={"cvssv3": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "cvssv3_score": 3})
12881288
self.assertEqual(result.status_code, status.HTTP_200_OK)
12891289
finding = Finding.objects.get(id=2)
1290+
# valid so vector must be set and score calculated does not ovewrite the score provided by us/the report
1291+
self.assertEqual("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", finding.cvssv3)
1292+
self.assertEqual(3.0, finding.cvssv3_score)
1293+
1294+
with self.subTest(i=1):
1295+
result = self.client.patch(self.url + "5/", data={"cvssv3": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"})
1296+
self.assertEqual(result.status_code, status.HTTP_200_OK)
1297+
finding = Finding.objects.get(id=5)
12901298
# valid so vector must be set and score calculated
12911299
self.assertEqual("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", finding.cvssv3)
12921300
self.assertEqual(8.8, finding.cvssv3_score)
12931301

1294-
with self.subTest(i=1):
1302+
with self.subTest(i=2):
12951303
# extra slash makes it invalid
12961304
result = self.client.patch(self.url + "3/", data={"cvssv3": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/", "cvssv3_score": 3})
12971305
self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
12981306
finding = Finding.objects.get(id=3)
1299-
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS vectors found by cvss.parse_cvss_from_text()"])
1307+
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS3 vectors found by cvss.parse_cvss_from_text()"])
13001308
# invalid vector, so no calculated score and no score stored
13011309
self.assertEqual(None, finding.cvssv3)
13021310
self.assertEqual(None, finding.cvssv3_score)
13031311

1304-
with self.subTest(i=2):
1312+
with self.subTest(i=3):
13051313
# no CVSS version prefix makes it invalid
13061314
result = self.client.patch(self.url + "3/", data={"cvssv3": "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "cvssv3_score": 4})
13071315
self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
13081316
finding = Finding.objects.get(id=3)
1309-
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS vectors found by cvss.parse_cvss_from_text()"])
1317+
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS3 vectors found by cvss.parse_cvss_from_text()"])
13101318
# invalid vector, so no calculated score and no score stored
13111319
self.assertEqual(None, finding.cvssv3)
13121320
self.assertEqual(None, finding.cvssv3_score)
13131321

1314-
with self.subTest(i=3):
1322+
with self.subTest(i=4):
13151323
# CVSS4 version makes it invalid
13161324
result = self.client.patch(self.url + "3/", data={"cvssv3": "CVSS:4.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "cvssv3_score": 5})
13171325
self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
1318-
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS vectors found by cvss.parse_cvss_from_text()"])
1326+
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS3 vectors found by cvss.parse_cvss_from_text()"])
13191327
finding = Finding.objects.get(id=3)
13201328
# invalid vector, so no calculated score and no score stored
13211329
self.assertEqual(None, finding.cvssv3)
13221330
self.assertEqual(None, finding.cvssv3_score)
13231331

13241332
with self.subTest(i=4):
1333+
# CVSS4 version valid
1334+
result = self.client.patch(self.url + "3/", data={"cvssv4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", "cvssv4_score": 5})
1335+
self.assertEqual(result.status_code, status.HTTP_200_OK)
1336+
finding = Finding.objects.get(id=3)
1337+
# invalid vector, so no calculated score and our provided score is stored (not overwritten)
1338+
self.assertEqual("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", finding.cvssv4)
1339+
self.assertEqual(5.0, finding.cvssv4_score)
1340+
1341+
with self.subTest(i=14):
1342+
# CVSS4 version valid, calculate score
1343+
result = self.client.patch(self.url + "3/", data={"cvssv4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"})
1344+
self.assertEqual(result.status_code, status.HTTP_200_OK)
1345+
finding = Finding.objects.get(id=3)
1346+
# invalid vector, so no calculated score and our provided score is stored (not overwritten)
1347+
self.assertEqual("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", finding.cvssv4)
1348+
self.assertEqual(5.0, finding.cvssv4_score)
1349+
1350+
with self.subTest(i=5):
13251351
# CVSS2 style vector makes not supported
13261352
result = self.client.patch(self.url + "3/", data={"cvssv3": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "cvssv3_score": 6})
13271353
self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
@@ -1331,31 +1357,31 @@ def test_cvss3_validation(self):
13311357
self.assertEqual(None, finding.cvssv3)
13321358
self.assertEqual(None, finding.cvssv3_score)
13331359

1334-
with self.subTest(i=5):
1360+
with self.subTest(i=6):
13351361
# CVSS2 prefix makes it invalid
13361362
result = self.client.patch(self.url + "3/", data={"cvssv3": "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", "cvssv3_score": 7})
13371363
self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
1338-
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS vectors found by cvss.parse_cvss_from_text()"])
1364+
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS3 vectors found by cvss.parse_cvss_from_text()"])
13391365
finding = Finding.objects.get(id=3)
13401366
# invalid vector, so no calculated score and no score stored
13411367
self.assertEqual(None, finding.cvssv3)
13421368
self.assertEqual(None, finding.cvssv3_score)
13431369

1344-
with self.subTest(i=6):
1370+
with self.subTest(i=7):
13451371
# try to put rubbish in there
13461372
result = self.client.patch(self.url + "4/", data={"cvssv3": "happy little vector", "cvssv3_score": 3})
13471373
self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
1348-
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS vectors found by cvss.parse_cvss_from_text()"])
1374+
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS3 vectors found by cvss.parse_cvss_from_text()"])
13491375
finding = Finding.objects.get(id=4)
13501376
# invalid vector, so no calculated score and no score stored
13511377
self.assertEqual(None, finding.cvssv3)
13521378
self.assertEqual(None, finding.cvssv3_score)
13531379

1354-
with self.subTest(i=7):
1380+
with self.subTest(i=8):
13551381
# CVSS4 prefix makes it invalid
1356-
result = self.client.patch(self.url + "3/", data={"cvssv3": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/S:U/C:H/I:H/A:H", "cvssv3_score": 7})
1382+
result = self.client.patch(self.url + "3/", data={"cvssv3": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", "cvssv3_score": 7})
13571383
self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST)
1358-
self.assertEqual(result.json()["cvssv3"], ["No valid CVSS vectors found by cvss.parse_cvss_from_text()"])
1384+
self.assertEqual(result.json()["cvssv3"], ["CVSS(4) vector vannot be stored in the cvss3 field. Use the cvss4 fields."])
13591385
finding = Finding.objects.get(id=3)
13601386
# invalid vector, so no calculated score and no score stored
13611387
self.assertEqual(None, finding.cvssv3)

0 commit comments

Comments
 (0)