Skip to content

Commit 3c962cd

Browse files
fix tests, cleanup
1 parent fc1c701 commit 3c962cd

File tree

8 files changed

+244
-50
lines changed

8 files changed

+244
-50
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pip-delete-this-directory.txt
4141
.ruff_cache
4242
nosetests.xml
4343
coverage.xml
44+
selenium_page_source.html
4445

4546
# Translations
4647
*.mo

dojo/api_v2/serializers.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@
118118
requires_tool_type,
119119
)
120120
from dojo.user.utils import get_configuration_permissions_codenames
121-
from dojo.utils import is_scan_file_too_large, tag_validator
121+
from dojo.utils import is_scan_file_too_large
122+
from dojo.validators import tag_validator
122123

123124
logger = logging.getLogger(__name__)
124125
deduplicationLogger = logging.getLogger("dojo.specific-loggers.deduplication")
@@ -1849,8 +1850,6 @@ def validate(self, data):
18491850
# doing it here instead of in update because update doesn't know if the value changed
18501851
self.process_risk_acceptance(data)
18511852

1852-
# cvss3_validator(data.get("cvssv3"), exception_class=RestFrameworkValidationError)
1853-
18541853
return data
18551854

18561855
def validate_severity(self, value: str) -> str:
@@ -1981,8 +1980,6 @@ def validate(self, data):
19811980
msg = "Active findings cannot be risk accepted."
19821981
raise serializers.ValidationError(msg)
19831982

1984-
# cvss3_validator(data.get("cvssv3"), exception_class=RestFrameworkValidationError)
1985-
19861983
return data
19871984

19881985
def validate_severity(self, value: str) -> str:
@@ -2009,7 +2006,6 @@ class Meta:
20092006
exclude = ("cve",)
20102007

20112008
def create(self, validated_data):
2012-
# cvss3_validator(validated_data.get("cvssv3"), exception_class=RestFrameworkValidationError)
20132009

20142010
to_be_tagged, validated_data = self._pop_tags(validated_data)
20152011

@@ -2040,8 +2036,6 @@ def create(self, validated_data):
20402036
return new_finding_template
20412037

20422038
def update(self, instance, validated_data):
2043-
# cvss3_validator(validated_data.get("cvssv3"), exception_class=RestFrameworkValidationError)
2044-
20452039
# Save vulnerability ids and pop them
20462040
if "vulnerability_id_template_set" in validated_data:
20472041
vulnerability_id_set = validated_data.pop(

dojo/forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@
110110
get_system_setting,
111111
is_finding_groups_enabled,
112112
is_scan_file_too_large,
113-
tag_validator,
114113
)
114+
from dojo.validators import tag_validator
115115
from dojo.widgets import TableCheckboxWidget
116116

117117
logger = logging.getLogger(__name__)

dojo/utils.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
from django.conf import settings
2626
from django.contrib import messages
2727
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
28-
from django.core.exceptions import ValidationError
2928
from django.core.paginator import Paginator
3029
from django.db.models import Case, Count, IntegerField, Q, Sum, Value, When
3130
from django.db.models.query import QuerySet
@@ -2662,22 +2661,3 @@ def generate_file_response_from_file_path(
26622661
response["Content-Disposition"] = f'attachment; filename="{full_file_name}"'
26632662
response["Content-Length"] = file_size
26642663
return response
2665-
2666-
2667-
def tag_validator(value: str | list[str], exception_class: Callable = ValidationError) -> None:
2668-
TAG_PATTERN = re.compile(r'[ ,\'"]')
2669-
error_messages = []
2670-
2671-
if isinstance(value, list):
2672-
for tag in value:
2673-
if TAG_PATTERN.search(tag):
2674-
error_messages.append(f"Invalid tag: '{tag}'. Tags should not contain spaces, commas, or quotes.")
2675-
elif isinstance(value, str):
2676-
if TAG_PATTERN.search(value):
2677-
error_messages.append(f"Invalid tag: '{value}'. Tags should not contain spaces, commas, or quotes.")
2678-
else:
2679-
error_messages.append(f"Value must be a string or list of strings: {value} - {type(value)}.")
2680-
2681-
if error_messages:
2682-
logger.debug(f"Tag validation failed: {error_messages}")
2683-
raise exception_class(error_messages)

dojo/validators.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import re
23
from collections.abc import Callable
34

45
import cvss.parser
@@ -8,6 +9,25 @@
89
logger = logging.getLogger(__name__)
910

1011

12+
def tag_validator(value: str | list[str], exception_class: Callable = ValidationError) -> None:
13+
TAG_PATTERN = re.compile(r'[ ,\'"]')
14+
error_messages = []
15+
16+
if isinstance(value, list):
17+
for tag in value:
18+
if TAG_PATTERN.search(tag):
19+
error_messages.append(f"Invalid tag: '{tag}'. Tags should not contain spaces, commas, or quotes.")
20+
elif isinstance(value, str):
21+
if TAG_PATTERN.search(value):
22+
error_messages.append(f"Invalid tag: '{value}'. Tags should not contain spaces, commas, or quotes.")
23+
else:
24+
error_messages.append(f"Value must be a string or list of strings: {value} - {type(value)}.")
25+
26+
if error_messages:
27+
logger.debug(f"Tag validation failed: {error_messages}")
28+
raise exception_class(error_messages)
29+
30+
1131
def cvss3_validator(value: str | list[str], exception_class: Callable = ValidationError) -> None:
1232
logger.error("cvss3_validator called with value: %s", value)
1333
cvss_vectors = cvss.parser.parse_cvss_from_text(value)
@@ -31,5 +51,5 @@ def cvss3_validator(value: str | list[str], exception_class: Callable = Validati
3151

3252
# Explicitly raise an error if no CVSS vectors are found,
3353
# to avoid 'NoneType' errors during severity processing later.
34-
msg = "No CVSS vectors found by cvss.parse_cvss_from_text()"
54+
msg = "No valid CVSS vectors found by cvss.parse_cvss_from_text()"
3555
raise exception_class(msg)

tests/finding_test.py

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,8 @@ def test_edit_finding(self):
112112
driver.find_element(By.ID, "dropdownMenu1").click()
113113
# Click on `Edit Finding`
114114
driver.find_element(By.LINK_TEXT, "Edit Finding").click()
115-
# Change: 'Severity' and 'cvssv3'
116115
# finding Severity
117116
Select(driver.find_element(By.ID, "id_severity")).select_by_visible_text("Critical")
118-
# cvssv3
119-
driver.find_element(By.ID, "id_cvssv3").send_keys("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H")
120117
# finding Vulnerability Ids
121118
driver.find_element(By.ID, "id_vulnerability_ids").send_keys("\nREF-3\nREF-4\n")
122119
# "Click" the Done button to Edit the finding
@@ -131,6 +128,123 @@ def test_edit_finding(self):
131128
self.assertTrue(self.is_text_present_on_page(text="REF-4"))
132129
self.assertTrue(self.is_text_present_on_page(text="Additional Vulnerability Ids"))
133130

131+
def _edit_finding_cvssv3_and_assert(
132+
self,
133+
cvssv3_value,
134+
cvssv3_score,
135+
expected_cvssv3_value,
136+
expected_cvssv3_score,
137+
expect_success=True, # noqa: FBT002
138+
success_message="Finding saved successfully",
139+
error_message=None,
140+
):
141+
driver = self.driver
142+
# Navigate to All Finding page
143+
self.goto_all_findings_list(driver)
144+
# Select and click on the particular finding to edit
145+
driver.find_element(By.LINK_TEXT, "App Vulnerable to XSS").click()
146+
# Click on the 'dropdownMenu1 button'
147+
driver.find_element(By.ID, "dropdownMenu1").click()
148+
# Click on `Edit Finding`
149+
driver.find_element(By.LINK_TEXT, "Edit Finding").click()
150+
# Set cvssv3 value and score
151+
driver.find_element(By.ID, "id_cvssv3").clear()
152+
driver.find_element(By.ID, "id_cvssv3").send_keys(cvssv3_value)
153+
driver.find_element(By.ID, "id_cvssv3_score").clear()
154+
driver.find_element(By.ID, "id_cvssv3_score").send_keys(str(cvssv3_score))
155+
# Submit the form
156+
driver.find_element(By.XPATH, "//input[@name='_Finished']").click()
157+
158+
if expect_success:
159+
self.assertTrue(self.is_success_message_present(text=success_message))
160+
# Go into edit mode again to check stored values
161+
driver.find_element(By.ID, "dropdownMenu1").click()
162+
driver.find_element(By.LINK_TEXT, "Edit Finding").click()
163+
self.assertEqual(expected_cvssv3_value, driver.find_element(By.ID, "id_cvssv3").get_attribute("value"))
164+
self.assertEqual(str(expected_cvssv3_score), driver.find_element(By.ID, "id_cvssv3_score").get_attribute("value"))
165+
else:
166+
self.assertTrue(self.is_error_message_present(text=error_message))
167+
self.assertEqual(expected_cvssv3_value, driver.find_element(By.ID, "id_cvssv3").get_attribute("value"))
168+
self.assertEqual(str(expected_cvssv3_score), driver.find_element(By.ID, "id_cvssv3_score").get_attribute("value"))
169+
170+
# See https://github.com/DefectDojo/django-DefectDojo/issues/8264
171+
# Capturing current behavior which might not be the desired one yet
172+
@on_exception_html_source_logger
173+
def test_edit_finding_cvssv3_valid_vector(self):
174+
self._edit_finding_cvssv3_and_assert(
175+
cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
176+
cvssv3_score="1",
177+
expected_cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
178+
expected_cvssv3_score="8.8",
179+
expect_success=True,
180+
)
181+
182+
@on_exception_html_source_logger
183+
def test_edit_finding_cvssv3_valid_vector_no_prefix(self):
184+
self._edit_finding_cvssv3_and_assert(
185+
cvssv3_value="AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
186+
cvssv3_score="2",
187+
expected_cvssv3_value="AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
188+
expected_cvssv3_score="2",
189+
expect_success=False,
190+
error_message="No valid CVSS vectors found by cvss.parse_cvss_from_text()",
191+
)
192+
193+
@on_exception_html_source_logger
194+
def test_edit_finding_cvssv3_valid_vector_with_trailing_slash(self):
195+
self._edit_finding_cvssv3_and_assert(
196+
cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/",
197+
cvssv3_score="3",
198+
expected_cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/",
199+
expected_cvssv3_score="3",
200+
expect_success=False,
201+
error_message="No valid CVSS vectors found by cvss.parse_cvss_from_text()",
202+
)
203+
204+
@on_exception_html_source_logger
205+
def test_edit_finding_cvssv3_with_v2_vector_invalid_due_to_prefix(self):
206+
self._edit_finding_cvssv3_and_assert(
207+
cvssv3_value="CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P",
208+
cvssv3_score="4",
209+
expected_cvssv3_value="CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P",
210+
expected_cvssv3_score="4",
211+
expect_success=False,
212+
error_message="No valid CVSS vectors found by cvss.parse_cvss_from_text()",
213+
)
214+
215+
@on_exception_html_source_logger
216+
def test_edit_finding_cvssv3_with_v2_vector(self):
217+
self._edit_finding_cvssv3_and_assert(
218+
cvssv3_value="AV:N/AC:L/Au:N/C:P/I:P/A:P",
219+
cvssv3_score="4",
220+
expected_cvssv3_value="AV:N/AC:L/Au:N/C:P/I:P/A:P",
221+
expected_cvssv3_score="4",
222+
expect_success=False,
223+
error_message="Unsupported CVSS(2) version detected.",
224+
)
225+
226+
@on_exception_html_source_logger
227+
def test_edit_finding_cvssv3_with_v4_vector(self):
228+
self._edit_finding_cvssv3_and_assert(
229+
cvssv3_value="CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/S:U/C:H/I:H/A:H",
230+
cvssv3_score="5",
231+
expected_cvssv3_value="CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/S:U/C:H/I:H/A:H",
232+
expected_cvssv3_score="5",
233+
expect_success=False,
234+
error_message="No valid CVSS vectors found by cvss.parse_cvss_from_text()",
235+
)
236+
237+
@on_exception_html_source_logger
238+
def test_edit_finding_cvssv3_with_rubbish(self):
239+
self._edit_finding_cvssv3_and_assert(
240+
cvssv3_value="happy little vector",
241+
cvssv3_score="5",
242+
expected_cvssv3_value="happy little vector",
243+
expected_cvssv3_score="5",
244+
expect_success=False,
245+
error_message="No valid CVSS vectors found by cvss.parse_cvss_from_text()",
246+
)
247+
134248
def test_add_image(self):
135249
# The Name of the Finding created by test_add_product_finding => 'App Vulnerable to XSS'
136250
# Test To Add Finding To product
@@ -519,6 +633,13 @@ def add_finding_tests_to_suite(suite, *, jira=False, github=False, block_executi
519633
suite.addTest(FindingTest("test_excel_export"))
520634
suite.addTest(FindingTest("test_list_components"))
521635
suite.addTest(FindingTest("test_edit_finding"))
636+
suite.addTest(FindingTest("test_edit_finding_cvssv3_valid_vector"))
637+
suite.addTest(FindingTest("test_edit_finding_cvssv3_valid_vector_no_prefix"))
638+
suite.addTest(FindingTest("test_edit_finding_cvssv3_valid_vector_with_trailing_slash"))
639+
suite.addTest(FindingTest("test_edit_finding_cvssv3_with_v2_vector"))
640+
suite.addTest(FindingTest("test_edit_finding_cvssv3_with_v2_vector_invalid_due_to_prefix"))
641+
suite.addTest(FindingTest("test_edit_finding_cvssv3_with_v4_vector"))
642+
suite.addTest(FindingTest("test_edit_finding_cvssv3_with_rubbish"))
522643
suite.addTest(FindingTest("test_add_note_to_finding"))
523644
suite.addTest(FindingTest("test_add_image"))
524645
suite.addTest(FindingTest("test_delete_image"))

unittests/test_finding_model.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99

1010
class TestFindingModel(DojoTestCase):
11+
fixtures = ["dojo_testdata.json"]
1112

1213
def test_get_sast_source_file_path_with_link_no_file_path(self):
1314
finding = Finding()
@@ -315,6 +316,46 @@ def test_get_references_with_links_markdown(self):
315316
finding.references = "URL: [https://www.example.com](https://www.example.com)"
316317
self.assertEqual("URL: [https://www.example.com](https://www.example.com)", finding.get_references_with_links())
317318

319+
# This test saves vectors without any validation. This is capturing current behavior.
320+
def test_cvssv3(self):
321+
"""Tests if the CVSSv3 score is calculated correctly"""
322+
user, _ = User.objects.get_or_create(username="admin")
323+
product_type = self.create_product_type("test_product_type")
324+
product = self.create_product(name="test_product", prod_type=product_type)
325+
engagement = self.create_engagement("test_eng", product)
326+
test = self.create_test(engagement=engagement, scan_type="ZAP Scan", title="test_test")
327+
finding = Finding.objects.create(
328+
test=test,
329+
reporter=user,
330+
title="test_finding",
331+
severity="Critical",
332+
date=datetime.now().date())
333+
finding.cvssv3 = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
334+
finding.save()
335+
336+
self.assertEqual(finding.cvssv3, "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
337+
self.assertEqual(finding.cvssv3_score, 9.8)
338+
finding_id = finding.id
339+
340+
finding = Finding.objects.get(id=finding_id)
341+
finding.cvssv3 = "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
342+
finding.save()
343+
344+
self.assertEqual(finding.cvssv3, "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H")
345+
# invalid vector, so score still 9.8 from previous save (and not 8.8)
346+
self.assertEqual(finding.cvssv3_score, 9.8)
347+
348+
finding = Finding.objects.get(id=finding_id)
349+
finding.cvssv3 = "happy little vector"
350+
finding.save()
351+
352+
self.assertEqual(finding.cvssv3, "happy little vector")
353+
# invalid vector, so score still 9.8 from previous save
354+
self.assertEqual(finding.cvssv3_score, 9.8)
355+
356+
# we already have more cvssv3 test in test_rest_framework.py and finding_test.py
357+
# the above here only shows that invalid vectors can be saved
358+
318359

319360
class TestFindingSLAExpiration(DojoTestCase):
320361
fixtures = ["dojo_testdata.json"]

0 commit comments

Comments
 (0)