diff --git a/dojo/tools/ptart/assessment_parser.py b/dojo/tools/ptart/assessment_parser.py index b240ffa29cb..8aa633d20f9 100644 --- a/dojo/tools/ptart/assessment_parser.py +++ b/dojo/tools/ptart/assessment_parser.py @@ -22,24 +22,23 @@ def parse_assessment(self, assessment): return [self.get_finding(assessment, hit) for hit in hits] def get_finding(self, assessment, hit): + effort = ptart_tools.parse_ptart_fix_effort(hit.get("fix_complexity")) finding = Finding( title=ptart_tools.parse_title_from_hit(hit), - severity=ptart_tools.parse_ptart_severity(hit.get("severity", 5)), - effort_for_fixing=ptart_tools.parse_ptart_fix_effort( - hit.get("fix_complexity", 3) - ), + severity=ptart_tools.parse_ptart_severity(hit.get("severity")), + effort_for_fixing=effort, component_name=assessment.get("title", "Unknown Component"), date=ptart_tools.parse_date_added_from_hit(hit), ) # Don't add fields if they are blank - if "body" in hit and hit["body"]: - finding.description = hit["body"] + if hit["body"]: + finding.description = hit.get("body") - if "remediation" in hit and hit["remediation"]: - finding.mitigation = hit["remediation"] + if hit["remediation"]: + finding.mitigation = hit.get("remediation") - if "id" in hit and hit["id"]: + if hit["id"]: finding.unique_id_from_tool = hit.get("id") # Clean up and parse the CVSS vector @@ -54,8 +53,6 @@ def get_finding(self, assessment, hit): # Add screenshots to files, and add other attachments as well. finding.unsaved_files = ptart_tools.parse_screenshots_from_hit(hit) - finding.unsaved_files.extend( - ptart_tools.parse_attachment_from_hit(hit) - ) + finding.unsaved_files.extend(ptart_tools.parse_attachment_from_hit(hit)) return finding diff --git a/dojo/tools/ptart/parser.py b/dojo/tools/ptart/parser.py index eb40fd53324..c52ebf4fb49 100644 --- a/dojo/tools/ptart/parser.py +++ b/dojo/tools/ptart/parser.py @@ -6,7 +6,8 @@ from dojo.tools.ptart.retest_parser import PTARTRetestParser -class PTARTParser(object): +class PTARTParser: + """ Imports JSON reports from the PTART reporting tool (https://github.com/certmichelin/PTART) @@ -48,13 +49,13 @@ def get_tests(self, scan_type, scan): # Perhaps in a future version of DefectDojo? if "start_date" in data: test.target_start = ptart_tools.parse_date( - data["start_date"], - "%Y-%m-%d" + data["start_date"], "%Y-%m-%d", ) if "end_date" in data: - test.target_end = ptart_tools.parse_date(data["end_date"], - "%Y-%m-%d") + test.target_end = ptart_tools.parse_date( + data["end_date"], "%Y-%m-%d", + ) findings = self.get_items(data) test.findings = findings diff --git a/dojo/tools/ptart/ptart_parser_tools.py b/dojo/tools/ptart/ptart_parser_tools.py index 28a711f2894..f6a3f0cb2cb 100644 --- a/dojo/tools/ptart/ptart_parser_tools.py +++ b/dojo/tools/ptart/ptart_parser_tools.py @@ -5,13 +5,16 @@ from dojo.models import Endpoint +ATTACHMENT_ERROR = "Attachment data not found" +SCREENSHOT_ERROR = "Screenshot data not found" + def parse_ptart_severity(severity): severity_mapping = { 1: "Critical", 2: "High", 3: "Medium", - 4: "Low" + 4: "Low", } return severity_mapping.get(severity, "Info") # Default severity @@ -20,7 +23,7 @@ def parse_ptart_fix_effort(effort): effort_mapping = { 1: "High", 2: "Medium", - 3: "Low" + 3: "Low", } return effort_mapping.get(effort, None) @@ -67,7 +70,7 @@ def parse_retest_status(status): "NF": "Not Fixed", "PF": "Partially Fixed", "NA": "Not Applicable", - "NT": "Not Tested" + "NT": "Not Tested", } return fix_status_mapping.get(status, None) @@ -86,32 +89,31 @@ def parse_screenshot_data(screenshot): data = get_screenshot_data(screenshot) return { "title": title, - "data": data + "data": data, } except ValueError: return None def get_screenshot_title(screenshot): - caption = screenshot.get('caption', 'screenshot') \ - if "caption" in screenshot and screenshot["caption"] \ - else "screenshot" - title = f"{caption}{get_file_suffix_from_screenshot(screenshot)}" - return title + caption = screenshot.get("caption", "screenshot") + if not caption: + caption = "screenshot" + return f"{caption}{get_file_suffix_from_screenshot(screenshot)}" def get_screenshot_data(screenshot): if ("screenshot" not in screenshot or "data" not in screenshot["screenshot"] or not screenshot["screenshot"]["data"]): - raise ValueError("Screenshot data not found") + raise ValueError(SCREENSHOT_ERROR) return screenshot["screenshot"]["data"] def get_file_suffix_from_screenshot(screenshot): - return pathlib.Path(screenshot['screenshot']['filename']).suffix \ + return pathlib.Path(screenshot["screenshot"]["filename"]).suffix \ if ("screenshot" in screenshot - and "filename" in screenshot['screenshot']) \ + and "filename" in screenshot["screenshot"]) \ else "" @@ -129,7 +131,7 @@ def parse_attachment_data(attachment): data = get_attachment_data(attachment) return { "title": title, - "data": data + "data": data, } except ValueError: # No data in attachment, let's not import this file. @@ -138,14 +140,15 @@ def parse_attachment_data(attachment): def get_attachment_data(attachment): if "data" not in attachment or not attachment["data"]: - raise ValueError("Attachment data not found") + raise ValueError(ATTACHMENT_ERROR) return attachment["data"] def get_attachement_title(attachment): - return attachment.get("title", "attachment") \ - if "title" in attachment and attachment["title"] \ - else "attachment" + title = attachment.get("title", "attachment") + if not title: + title = "attachment" + return title def parse_endpoints_from_hit(hit): @@ -157,8 +160,6 @@ def parse_endpoints_from_hit(hit): def generate_test_description_from_report(data): keys = ["executive_summary", "engagement_overview", "conclusion"] - description = "\n\n".join(data[key] - for key in keys - if key in data and data[key] - ) - return description if description else None + clauses = [clause for clause in [data.get(key) for key in keys] if clause] + description = "\n\n".join(clauses) + return description or None diff --git a/dojo/tools/ptart/retest_parser.py b/dojo/tools/ptart/retest_parser.py index 2b3eea16207..59673c61974 100644 --- a/dojo/tools/ptart/retest_parser.py +++ b/dojo/tools/ptart/retest_parser.py @@ -1,6 +1,5 @@ import dojo.tools.ptart.ptart_parser_tools as ptart_tools from dojo.models import Finding -from dojo.tools.ptart.ptart_parser_tools import parse_title_from_hit def generate_retest_hit_title(hit, original_hit): @@ -13,8 +12,7 @@ def generate_retest_hit_title(hit, original_hit): "title": title, "id": hit_id, } - finding_title = parse_title_from_hit(fake_retest_hit) - return finding_title + return ptart_tools.parse_title_from_hit(fake_retest_hit) class PTARTRetestParser: @@ -59,31 +57,31 @@ def get_finding(self, retest, hit): finding = Finding( title=finding_title, severity=ptart_tools.parse_ptart_severity( - original_hit.get("severity") + original_hit.get("severity"), ), effort_for_fixing=ptart_tools.parse_ptart_fix_effort( - original_hit.get("fix_complexity") + original_hit.get("fix_complexity"), ), component_name=f"Retest: {retest.get('name', 'Retest')}", date=ptart_tools.parse_date( retest.get("start_date"), - "%Y-%m-%d" + "%Y-%m-%d", ), ) # Don't add the fields if they are blank. - if "body" in hit and hit["body"]: - finding.description = hit["body"] + if hit["body"]: + finding.description = hit.get("body") - if "remediation" in original_hit and original_hit["remediation"]: - finding.mitigation = original_hit["remediation"] + if original_hit["remediation"]: + finding.mitigation = original_hit.get("remediation") - if "id" in hit and hit["id"]: + if hit["id"]: finding.unique_id_from_tool = hit.get("id") cvss_vector = ptart_tools.parse_cvss_vector( original_hit, - self.cvss_type + self.cvss_type, ) if cvss_vector: finding.cvssv3 = cvss_vector @@ -92,7 +90,7 @@ def get_finding(self, retest, hit): finding.unsaved_tags = original_hit["labels"] finding.unsaved_endpoints = ptart_tools.parse_endpoints_from_hit( - original_hit + original_hit, ) # We only have screenshots in a retest. Refer to the original hit for diff --git a/unittests/tools/test_ptart_parser.py b/unittests/tools/test_ptart_parser.py index deb9ea5e907..4b9059cebcf 100644 --- a/unittests/tools/test_ptart_parser.py +++ b/unittests/tools/test_ptart_parser.py @@ -1,6 +1,6 @@ from django.test import TestCase -from dojo.models import Test, Product, Engagement +from dojo.models import Engagement, Product, Test from dojo.tools.ptart.parser import PTARTParser @@ -64,22 +64,22 @@ def test_ptart_parser_tools_cvss_vector_acquisition(self): from dojo.tools.ptart.ptart_parser_tools import parse_cvss_vector with self.subTest("Test CVSSv3 Vector"): hit = { - "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", } self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", parse_cvss_vector(hit, 3)) with self.subTest("Test CVSSv4 Vector"): hit = { - "cvss_vector": "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" + "cvss_vector": "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", } self.assertEqual(None, parse_cvss_vector(hit, 4)) with self.subTest("Test CVSSv3 Vector with CVSSv4 Request"): hit = { - "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" + "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", } self.assertEqual(None, parse_cvss_vector(hit, 4)) with self.subTest("Test CVSSv4 Vector with CVSSv3 Request"): hit = { - "cvss_vector": "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" + "cvss_vector": "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", } self.assertEqual(None, parse_cvss_vector(hit, 3)) with self.subTest("Test No CVSS Vector"): @@ -87,12 +87,12 @@ def test_ptart_parser_tools_cvss_vector_acquisition(self): self.assertEqual(None, parse_cvss_vector(hit, 3)) with self.subTest("Test CVSSv2 Vector"): hit = { - "cvss_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:C/I:C/A:C" + "cvss_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:C/I:C/A:C", } self.assertEqual(None, parse_cvss_vector(hit, 2)) with self.subTest("Test Blank CVSS Vector"): hit = { - "cvss_vector": "" + "cvss_vector": "", } self.assertEqual(None, parse_cvss_vector(hit, 3)) @@ -126,9 +126,9 @@ def test_ptart_parser_tools_parse_screenshots_from_hit(self): "order": 0, "screenshot": { "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", - "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk" - } - }] + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], } screenshots = parse_screenshots_from_hit(hit) self.assertEqual(1, len(screenshots)) @@ -143,16 +143,16 @@ def test_ptart_parser_tools_parse_screenshots_from_hit(self): "order": 0, "screenshot": { "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", - "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk" - } + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, }, { "caption": "Two", "order": 1, "screenshot": { "filename": "screenshots/123e4567-e89b-12d3-a456-426614174000.png", - "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk" - } - }] + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], } screenshots = parse_screenshots_from_hit(hit) self.assertEqual(2, len(screenshots)) @@ -171,9 +171,9 @@ def test_ptart_parser_tools_parse_screenshots_from_hit(self): "order": 0, "screenshot": { "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", - "data": "" - } - }] + "data": "", + }, + }], } screenshots = parse_screenshots_from_hit(hit) self.assertEqual(0, len(screenshots)) @@ -183,9 +183,9 @@ def test_ptart_parser_tools_parse_screenshots_from_hit(self): "order": 0, "screenshot": { "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", - "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk" - } - }] + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], } screenshots = parse_screenshots_from_hit(hit) self.assertEqual(1, len(screenshots)) @@ -200,9 +200,9 @@ def test_ptart_parser_tools_parse_screenshots_from_hit(self): "order": 0, "screenshot": { "filename": "screenshots/a78bebcc-6da7-4c25-86a3-441435ea68d0.png", - "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk" - } - }] + "data": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR42mNk", + }, + }], } screenshots = parse_screenshots_from_hit(hit) self.assertEqual(1, len(screenshots)) @@ -221,8 +221,8 @@ def test_ptart_parser_tools_parse_attachment_from_hit(self): hit = { "attachments": [{ "title": "License", - "data": "TUlUIExpY2Vuc2UKCkNvcHl" - }] + "data": "TUlUIExpY2Vuc2UKCkNvcHl", + }], } attachments = parse_attachment_from_hit(hit) self.assertEqual(1, len(attachments)) @@ -233,11 +233,11 @@ def test_ptart_parser_tools_parse_attachment_from_hit(self): hit = { "attachments": [{ "title": "License", - "data": "TUlUIExpY2Vuc2UKCkNvcHl" + "data": "TUlUIExpY2Vuc2UKCkNvcHl", }, { "title": "Readme", - "data": "UkVBRERtZQoK" - }] + "data": "UkVBRERtZQoK", + }], } attachments = parse_attachment_from_hit(hit) self.assertEqual(2, len(attachments)) @@ -251,24 +251,24 @@ def test_ptart_parser_tools_parse_attachment_from_hit(self): hit = { "attachments": [{ "title": "License", - "data": "" - }] + "data": "", + }], } attachments = parse_attachment_from_hit(hit) self.assertEqual(0, len(attachments)) with self.subTest("No Data Attachment"): hit = { "attachments": [{ - "title": "License" - }] + "title": "License", + }], } attachments = parse_attachment_from_hit(hit) self.assertEqual(0, len(attachments)) with self.subTest("Attachement with no Title"): hit = { "attachments": [{ - "data": "TUlUIExpY2Vuc2UKCkNvcHl" - }] + "data": "TUlUIExpY2Vuc2UKCkNvcHl", + }], } attachments = parse_attachment_from_hit(hit) self.assertEqual(1, len(attachments)) @@ -279,8 +279,8 @@ def test_ptart_parser_tools_parse_attachment_from_hit(self): hit = { "attachments": [{ "title": "", - "data": "TUlUIExpY2Vuc2UKCkNvcHl" - }] + "data": "TUlUIExpY2Vuc2UKCkNvcHl", + }], } attachments = parse_attachment_from_hit(hit) self.assertEqual(1, len(attachments)) @@ -296,45 +296,45 @@ def test_ptart_parser_tools_get_description_from_report_base(self): self.assertEqual(None, generate_test_description_from_report(data)) with self.subTest("Description from Executive Summary Only"): data = { - "executive_summary": "This is a summary" + "executive_summary": "This is a summary", } self.assertEqual("This is a summary", generate_test_description_from_report(data)) with self.subTest("Description from Engagement Overview Only"): data = { - "engagement_overview": "This is an overview" + "engagement_overview": "This is an overview", } self.assertEqual("This is an overview", generate_test_description_from_report(data)) with self.subTest("Description from Conclusion Only"): data = { - "conclusion": "This is a conclusion" + "conclusion": "This is a conclusion", } self.assertEqual("This is a conclusion", generate_test_description_from_report(data)) with self.subTest("Description from All Sections"): data = { "executive_summary": "This is a summary", "engagement_overview": "This is an overview", - "conclusion": "This is a conclusion" + "conclusion": "This is a conclusion", } self.assertEqual("This is a summary\n\nThis is an overview\n\nThis is a conclusion", generate_test_description_from_report(data)) with self.subTest("Description from Executive Summary and Conclusion"): data = { "executive_summary": "This is a summary", - "conclusion": "This is a conclusion" + "conclusion": "This is a conclusion", } self.assertEqual("This is a summary\n\nThis is a conclusion", generate_test_description_from_report(data)) with self.subTest("Description from Executive Summary and Engagement Overview"): data = { "executive_summary": "This is a summary", - "engagement_overview": "This is an overview" + "engagement_overview": "This is an overview", } self.assertEqual("This is a summary\n\nThis is an overview", generate_test_description_from_report(data)) with self.subTest("Description from Engagement Overview and Conclusion"): data = { "engagement_overview": "This is an overview", - "conclusion": "This is a conclusion" + "conclusion": "This is a conclusion", } self.assertEqual("This is an overview\n\nThis is a conclusion", generate_test_description_from_report(data)) @@ -342,32 +342,32 @@ def test_ptart_parser_tools_get_description_from_report_base(self): data = { "executive_summary": "", "engagement_overview": "", - "conclusion": "" + "conclusion": "", } self.assertEqual(None, generate_test_description_from_report(data)) with self.subTest("Description with Some Blank Strings"): data = { "executive_summary": "", "engagement_overview": "This is an overview", - "conclusion": "" + "conclusion": "", } self.assertEqual("This is an overview", generate_test_description_from_report(data)) def test_ptart_parser_with_empty_json_throws_error(self): - with open("unittests/scans/ptart/empty_with_error.json") as testfile: + with open("unittests/scans/ptart/empty_with_error.json", encoding="utf-8") as testfile: parser = PTARTParser() findings = parser.get_findings(testfile, self.test) self.assertEqual(0, len(findings)) def test_ptart_parser_with_no_assessments_has_no_findings(self): - with open("unittests/scans/ptart/ptart_zero_vul.json") as testfile: + with open("unittests/scans/ptart/ptart_zero_vul.json", encoding="utf-8") as testfile: parser = PTARTParser() findings = parser.get_findings(testfile, self.test) self.assertEqual(0, len(findings)) self.assertEqual([], findings) def test_ptart_parser_with_one_assessment_has_one_finding(self): - with open("unittests/scans/ptart/ptart_one_vul.json") as testfile: + with open("unittests/scans/ptart/ptart_one_vul.json", encoding="utf-8") as testfile: parser = PTARTParser() findings = parser.get_findings(testfile, self.test) self.assertEqual(1, len(findings)) @@ -389,7 +389,7 @@ def test_ptart_parser_with_one_assessment_has_one_finding(self): self.assertEqual(2, len(finding.unsaved_tags)) self.assertEqual([ "A01:2021-Broken Access Control", - "A04:2021-Insecure Design" + "A04:2021-Insecure Design", ], finding.unsaved_tags) self.assertEqual(1, len(finding.unsaved_endpoints)) endpoint = finding.unsaved_endpoints[0] @@ -403,7 +403,7 @@ def test_ptart_parser_with_one_assessment_has_one_finding(self): self.assertTrue(attachment["data"].startswith("TUlUIExpY2Vuc2UKCkNvcHl"), "Invalid Attachment Data") def test_ptart_parser_with_one_assessment_has_many_findings(self): - with open("unittests/scans/ptart/ptart_many_vul.json") as testfile: + with open("unittests/scans/ptart/ptart_many_vul.json", encoding="utf-8") as testfile: parser = PTARTParser() findings = parser.get_findings(testfile, self.test) self.assertEqual(2, len(findings)) @@ -445,7 +445,7 @@ def test_ptart_parser_with_one_assessment_has_many_findings(self): self.assertEqual("2024-09-06", finding.date.strftime("%Y-%m-%d")) def test_ptart_parser_with_multiple_assessments_has_many_findings_correctly_grouped(self): - with open("unittests/scans/ptart/ptart_vulns_with_mult_assessments.json") as testfile: + with open("unittests/scans/ptart/ptart_vulns_with_mult_assessments.json", encoding="utf-8") as testfile: parser = PTARTParser() findings = parser.get_findings(testfile, self.test) self.assertEqual(3, len(findings)) @@ -504,7 +504,7 @@ def test_ptart_parser_with_multiple_assessments_has_many_findings_correctly_grou self.assertEqual(0, len(finding.unsaved_files)) def test_ptart_parser_with_single_vuln_on_import_test(self): - with open("unittests/scans/ptart/ptart_one_vul.json") as testfile: + with open("unittests/scans/ptart/ptart_one_vul.json", encoding="utf-8") as testfile: parser = PTARTParser() tests = parser.get_tests("PTART Report", testfile) self.assertEqual(1, len(tests)) @@ -533,7 +533,7 @@ def test_ptart_parser_with_single_vuln_on_import_test(self): self.assertEqual(2, len(finding.unsaved_tags)) self.assertEqual([ "A01:2021-Broken Access Control", - "A04:2021-Insecure Design" + "A04:2021-Insecure Design", ], finding.unsaved_tags) self.assertEqual(1, len(finding.unsaved_endpoints)) endpoint = finding.unsaved_endpoints[0] @@ -547,7 +547,7 @@ def test_ptart_parser_with_single_vuln_on_import_test(self): self.assertTrue(attachment["data"].startswith("TUlUIExpY2Vuc2UKCkNvcHl"), "Invalid Attachment Data") def test_ptart_parser_with_retest_campaign(self): - with open("unittests/scans/ptart/ptart_vuln_plus_retest.json") as testfile: + with open("unittests/scans/ptart/ptart_vuln_plus_retest.json", encoding="utf-8") as testfile: parser = PTARTParser() findings = parser.get_findings(testfile, self.test) self.assertEqual(3, len(findings))