diff --git a/.gitignore b/.gitignore index 7696309af78..20b34e209af 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ pip-delete-this-directory.txt .ruff_cache nosetests.xml coverage.xml +selenium_page_source.html # Translations *.mo diff --git a/tests/finding_test.py b/tests/finding_test.py index 5f53ed21c38..c836c9bf717 100644 --- a/tests/finding_test.py +++ b/tests/finding_test.py @@ -115,8 +115,6 @@ def test_edit_finding(self): # Change: 'Severity' and 'cvssv3' # finding Severity Select(driver.find_element(By.ID, "id_severity")).select_by_visible_text("Critical") - # cvssv3 - 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") # finding Vulnerability Ids driver.find_element(By.ID, "id_vulnerability_ids").send_keys("\nREF-3\nREF-4\n") # "Click" the Done button to Edit the finding @@ -131,6 +129,96 @@ def test_edit_finding(self): self.assertTrue(self.is_text_present_on_page(text="REF-4")) self.assertTrue(self.is_text_present_on_page(text="Additional Vulnerability Ids")) + def _edit_finding_cvssv3_and_assert( + self, + cvssv3_value, + cvssv3_score, + expected_cvssv3_value, + expected_cvssv3_score, + expect_success=True, # noqa: FBT002 + success_message="Finding saved successfully", + error_message=None, + ): + driver = self.driver + # Navigate to All Finding page + self.goto_all_findings_list(driver) + # Select and click on the particular finding to edit + driver.find_element(By.LINK_TEXT, "App Vulnerable to XSS").click() + # Click on the 'dropdownMenu1 button' + driver.find_element(By.ID, "dropdownMenu1").click() + # Click on `Edit Finding` + driver.find_element(By.LINK_TEXT, "Edit Finding").click() + # Set cvssv3 value and score + driver.find_element(By.ID, "id_cvssv3").clear() + driver.find_element(By.ID, "id_cvssv3").send_keys(cvssv3_value) + driver.find_element(By.ID, "id_cvssv3_score").clear() + driver.find_element(By.ID, "id_cvssv3_score").send_keys(str(cvssv3_score)) + # Submit the form + driver.find_element(By.XPATH, "//input[@name='_Finished']").click() + + if expect_success: + self.assertTrue(self.is_success_message_present(text=success_message)) + # Go into edit mode again to check stored values + driver.find_element(By.ID, "dropdownMenu1").click() + driver.find_element(By.LINK_TEXT, "Edit Finding").click() + self.assertEqual(expected_cvssv3_value, driver.find_element(By.ID, "id_cvssv3").get_attribute("value")) + self.assertEqual(str(expected_cvssv3_score), driver.find_element(By.ID, "id_cvssv3_score").get_attribute("value")) + else: + self.assertTrue(self.is_error_message_present(text=error_message)) + + # See https://github.com/DefectDojo/django-DefectDojo/issues/8264 + # Capturing current behavior which might not be the desired one yet + @on_exception_html_source_logger + def test_edit_finding_cvssv3_valid_vector(self): + self._edit_finding_cvssv3_and_assert( + cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + cvssv3_score="1", + expected_cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + expected_cvssv3_score="8.8", + expect_success=True, + ) + + @on_exception_html_source_logger + def test_edit_finding_cvssv3_valid_vector_no_prefix(self): + self._edit_finding_cvssv3_and_assert( + cvssv3_value="AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + cvssv3_score="2", + expected_cvssv3_value="AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + expected_cvssv3_score="2.0", + expect_success=True, + ) + + @on_exception_html_source_logger + def test_edit_finding_cvssv3_valid_vector_with_trailing_slash(self): + self._edit_finding_cvssv3_and_assert( + cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/", + cvssv3_score="3", + expected_cvssv3_value="CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/", + expected_cvssv3_score="3.0", + expect_success=True, + ) + + @on_exception_html_source_logger + def test_edit_finding_cvssv3_with_v2_vector(self): + self._edit_finding_cvssv3_and_assert( + cvssv3_value="CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", + cvssv3_score="4", + expected_cvssv3_value="CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", + expected_cvssv3_score="4.0", + expect_success=True, + ) + + @on_exception_html_source_logger + def test_edit_finding_cvssv3_with_rubbish(self): + self._edit_finding_cvssv3_and_assert( + cvssv3_value="happy little vector", + cvssv3_score="4", + expected_cvssv3_value=None, + expected_cvssv3_score=None, + expect_success=False, + error_message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'", + ) + def test_add_image(self): # The Name of the Finding created by test_add_product_finding => 'App Vulnerable to XSS' # Test To Add Finding To product @@ -519,6 +607,11 @@ def add_finding_tests_to_suite(suite, *, jira=False, github=False, block_executi suite.addTest(FindingTest("test_excel_export")) suite.addTest(FindingTest("test_list_components")) suite.addTest(FindingTest("test_edit_finding")) + suite.addTest(FindingTest("test_edit_finding_cvssv3_valid_vector")) + suite.addTest(FindingTest("test_edit_finding_cvssv3_valid_vector_no_prefix")) + suite.addTest(FindingTest("test_edit_finding_cvssv3_valid_vector_with_trailing_slash")) + suite.addTest(FindingTest("test_edit_finding_cvssv3_with_v2_vector")) + suite.addTest(FindingTest("test_edit_finding_cvssv3_with_rubbish")) suite.addTest(FindingTest("test_add_note_to_finding")) suite.addTest(FindingTest("test_add_image")) suite.addTest(FindingTest("test_delete_image")) diff --git a/unittests/test_finding_model.py b/unittests/test_finding_model.py index 2e2be817b7e..aa6971accb0 100644 --- a/unittests/test_finding_model.py +++ b/unittests/test_finding_model.py @@ -8,6 +8,7 @@ class TestFindingModel(DojoTestCase): + fixtures = ["dojo_testdata.json"] def test_get_sast_source_file_path_with_link_no_file_path(self): finding = Finding() @@ -315,6 +316,47 @@ def test_get_references_with_links_markdown(self): finding.references = "URL: [https://www.example.com](https://www.example.com)" self.assertEqual("URL: [https://www.example.com](https://www.example.com)", finding.get_references_with_links()) + # See https://github.com/DefectDojo/django-DefectDojo/issues/8264 + # Capturing current behavior which might not be the desired one yet + def test_cvssv3(self): + """Tests if the CVSSv3 score is calculated correctly""" + user, _ = User.objects.get_or_create(username="admin") + product_type = self.create_product_type("test_product_type") + product = self.create_product(name="test_product", prod_type=product_type) + engagement = self.create_engagement("test_eng", product) + test = self.create_test(engagement=engagement, scan_type="ZAP Scan", title="test_test") + finding = Finding.objects.create( + test=test, + reporter=user, + title="test_finding", + severity="Critical", + date=datetime.now().date()) + finding.cvssv3 = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + finding.save() + + self.assertEqual(finding.cvssv3, "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H") + self.assertEqual(finding.cvssv3_score, 9.8) + finding_id = finding.id + + finding = Finding.objects.get(id=finding_id) + finding.cvssv3 = "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H" + finding.save() + + self.assertEqual(finding.cvssv3, "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H") + # invalid vector, so score still 9.8 from previous save (and not 8.8) + self.assertEqual(finding.cvssv3_score, 9.8) + + finding = Finding.objects.get(id=finding_id) + finding.cvssv3 = "happy little vector" + finding.save() + + self.assertEqual(finding.cvssv3, "happy little vector") + # invalid vector, so score still 9.8 from previous save + self.assertEqual(finding.cvssv3_score, 9.8) + + # we already have more cvssv3 test in test_rest_framework.py and finding_test.py + # the above here only shows that invalid vectors can be saved + class TestFindingSLAExpiration(DojoTestCase): fixtures = ["dojo_testdata.json"] diff --git a/unittests/test_rest_framework.py b/unittests/test_rest_framework.py index 44b60416dc6..0f04e7b7799 100644 --- a/unittests/test_rest_framework.py +++ b/unittests/test_rest_framework.py @@ -1281,6 +1281,70 @@ def test_severity_validation(self): self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST, "Severity just got set to something invalid") self.assertEqual(result.json()["severity"], ["Severity must be one of the following: ['Info', 'Low', 'Medium', 'High', 'Critical']"]) + # See https://github.com/DefectDojo/django-DefectDojo/issues/8264 + # Capturing current behavior which might not be the desired one yet + def test_cvss3_validation(self): + with self.subTest(i=0): + self.assertEqual(None, Finding.objects.get(id=2).cvssv3) + 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}) + self.assertEqual(result.status_code, status.HTTP_200_OK) + finding = Finding.objects.get(id=2) + self.assertEqual("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", finding.cvssv3) + self.assertEqual(8.8, finding.cvssv3_score) + + with self.subTest(i=1): + # extra slash makes it invalid + 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}) + self.assertEqual(result.status_code, status.HTTP_200_OK) + finding = Finding.objects.get(id=3) + self.assertEqual("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/", finding.cvssv3) + self.assertEqual(3, finding.cvssv3_score) + + with self.subTest(i=2): + # no CVSS version prefix makes it invalid + 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}) + self.assertEqual(result.status_code, status.HTTP_200_OK) + finding = Finding.objects.get(id=3) + self.assertEqual("AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", finding.cvssv3) + # invalid vector, so no calculated score + self.assertEqual(4, finding.cvssv3_score) + + with self.subTest(i=3): + # CVSS4 version makes it invalid + 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}) + self.assertEqual(result.status_code, status.HTTP_200_OK) + finding = Finding.objects.get(id=3) + self.assertEqual("CVSS:4.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", finding.cvssv3) + # invalid vector, so no calculated score + self.assertEqual(5, finding.cvssv3_score) + + with self.subTest(i=4): + # CVSS2 style vector makes not supported + result = self.client.patch(self.url + "3/", data={"cvssv3": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "cvssv3_score": 6}) + self.assertEqual(result.status_code, status.HTTP_200_OK) + finding = Finding.objects.get(id=3) + self.assertEqual("AV:N/AC:L/Au:N/C:P/I:P/A:P", finding.cvssv3) + # invalid vector, so no calculated score + self.assertEqual(6, finding.cvssv3_score) + + with self.subTest(i=5): + # CVSS2 prefix makes it invalid + 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}) + self.assertEqual(result.status_code, status.HTTP_200_OK) + finding = Finding.objects.get(id=3) + self.assertEqual("CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", finding.cvssv3) + # invalid vector, so no calculated score + self.assertEqual(7, finding.cvssv3_score) + + with self.subTest(i=6): + # try to put rubbish in there + result = self.client.patch(self.url + "4/", data={"cvssv3": "happy little vector", "cvssv3_score": 3}) + self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) + finding = Finding.objects.get(id=4) + self.assertEqual(None, finding.cvssv3) + # invalid request, so no score at all + self.assertEqual(None, finding.cvssv3_score) + class FindingMetadataTest(BaseClass.BaseClassTest): fixtures = ["dojo_testdata.json"]