diff --git a/dojo/tools/ptart/assessment_parser.py b/dojo/tools/ptart/assessment_parser.py index 4aea74603aa..b240ffa29cb 100644 --- a/dojo/tools/ptart/assessment_parser.py +++ b/dojo/tools/ptart/assessment_parser.py @@ -7,13 +7,15 @@ def __init__(self): self.cvss_type = None def get_test_data(self, tree): - # Check that the report is valid, If we have no assessments, then return an empty list + # Check that the report is valid, If we have no assessments, then + # return an empty list if "assessments" not in tree: return [] self.cvss_type = tree.get("cvss_type", None) assessments = tree["assessments"] - return [finding for assessment in assessments for finding in self.parse_assessment(assessment)] + return [finding for assessment in assessments + for finding in self.parse_assessment(assessment)] def parse_assessment(self, assessment): hits = assessment.get("hits", []) @@ -23,7 +25,9 @@ def get_finding(self, assessment, hit): 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)), + effort_for_fixing=ptart_tools.parse_ptart_fix_effort( + hit.get("fix_complexity", 3) + ), component_name=assessment.get("title", "Unknown Component"), date=ptart_tools.parse_date_added_from_hit(hit), ) @@ -48,8 +52,10 @@ def get_finding(self, assessment, hit): finding.unsaved_endpoints = ptart_tools.parse_endpoints_from_hit(hit) - # Add screenshots to files, and add other attachments to the files as well. + # 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 f9e86a1c294..eb40fd53324 100644 --- a/dojo/tools/ptart/parser.py +++ b/dojo/tools/ptart/parser.py @@ -8,7 +8,8 @@ class PTARTParser(object): """ - Imports JSON reports from the PTART (https://github.com/certmichelin/PTART) reporting tool + Imports JSON reports from the PTART reporting tool + (https://github.com/certmichelin/PTART) """ def get_scan_types(self): @@ -29,14 +30,15 @@ def get_tests(self, scan_type, scan): version="", ) - # We set both to the same value for now, setting just the name doesn't seem to display when imported. - # This may cause issues with the UI in the future, but there's not much (read no) documentation on this. + # We set both to the same value for now, setting just the name doesn't + # seem to display when imported. This may cause issues with the UI in + # the future, but there's not much (read no) documentation on this. if "name" in data: test.name = data["name"] + " Report" test.type = data["name"] + " Report" # Generate a description from the various fields in the report data - description = ptart_tools.generate_test_description_from_report_base(data) + description = ptart_tools.generate_test_description_from_report(data) # Check that the fields are filled, otherwise don't set the description if description: @@ -45,10 +47,14 @@ def get_tests(self, scan_type, scan): # Setting the dates doesn't seem to want to work in reality :( # 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") + test.target_start = ptart_tools.parse_date( + 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 @@ -59,8 +65,9 @@ def get_findings(self, file, test): return self.get_items(data) def get_items(self, data): - # We have several main sections in the report json: Assessments and Retest Campaigns. - # I haven't been able to create multiple tests for each section, so we'll just merge them for now. + # We have several main sections in the report json: Assessments and + # Retest Campaigns. I haven't been able to create multiple tests for + # each section, so we'll just merge them for now. findings = PTARTAssessmentParser().get_test_data(data) findings.extend(PTARTRetestParser().get_test_data(data)) return findings diff --git a/dojo/tools/ptart/ptart_parser_tools.py b/dojo/tools/ptart/ptart_parser_tools.py index 3cf2b381350..28a711f2894 100644 --- a/dojo/tools/ptart/ptart_parser_tools.py +++ b/dojo/tools/ptart/ptart_parser_tools.py @@ -61,7 +61,7 @@ def parse_cvss_vector(hit, cvss_type): return None -def parse_retest_fix_status(status): +def parse_retest_status(status): fix_status_mapping = { "F": "Fixed", "NF": "Not Fixed", @@ -75,7 +75,8 @@ def parse_retest_fix_status(status): def parse_screenshots_from_hit(hit): if "screenshots" not in hit: return [] - screenshots = [parse_screenshot_data(screenshot) for screenshot in hit["screenshots"]] + screenshots = [parse_screenshot_data(screenshot) + for screenshot in hit["screenshots"]] return [ss for ss in screenshots if ss is not None] @@ -100,21 +101,25 @@ def get_screenshot_title(screenshot): def get_screenshot_data(screenshot): - if "screenshot" not in screenshot or "data" not in screenshot["screenshot"] or not screenshot["screenshot"]["data"]: + if ("screenshot" not in screenshot + or "data" not in screenshot["screenshot"] + or not screenshot["screenshot"]["data"]): raise ValueError("Screenshot data not found") return screenshot["screenshot"]["data"] def get_file_suffix_from_screenshot(screenshot): return pathlib.Path(screenshot['screenshot']['filename']).suffix \ - if "screenshot" in screenshot and "filename" in screenshot['screenshot'] \ + if ("screenshot" in screenshot + and "filename" in screenshot['screenshot']) \ else "" def parse_attachment_from_hit(hit): if "attachments" not in hit: return [] - files = [parse_attachment_data(attachment) for attachment in hit["attachments"]] + files = [parse_attachment_data(attachment) + for attachment in hit["attachments"]] return [f for f in files if f is not None] @@ -138,7 +143,9 @@ def get_attachment_data(attachment): def get_attachement_title(attachment): - return attachment.get("title", "attachment") if "title" in attachment and attachment["title"] else "attachment" + return attachment.get("title", "attachment") \ + if "title" in attachment and attachment["title"] \ + else "attachment" def parse_endpoints_from_hit(hit): @@ -148,7 +155,10 @@ def parse_endpoints_from_hit(hit): return [endpoint] -def generate_test_description_from_report_base(data): +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]) + description = "\n\n".join(data[key] + for key in keys + if key in data and data[key] + ) return description if description else None diff --git a/dojo/tools/ptart/retest_parser.py b/dojo/tools/ptart/retest_parser.py index 58ccebd7019..2b3eea16207 100644 --- a/dojo/tools/ptart/retest_parser.py +++ b/dojo/tools/ptart/retest_parser.py @@ -8,7 +8,7 @@ def generate_retest_hit_title(hit, original_hit): title = original_hit.get("title", "") hit_id = hit.get("id", None) if "status" in hit: - title = f"{title} ({ptart_tools.parse_retest_fix_status(hit['status'])})" + title = f"{title} ({ptart_tools.parse_retest_status(hit['status'])})" fake_retest_hit = { "title": title, "id": hit_id, @@ -28,7 +28,8 @@ def get_test_data(self, tree): else: return [] - return [finding for retest in retests for finding in self.parse_retest(retest)] + return [finding for retest in retests + for finding in self.parse_retest(retest)] def parse_retest(self, retest): hits = retest.get("hits", []) @@ -39,26 +40,35 @@ def parse_retest(self, retest): def get_finding(self, retest, hit): - # The negatives are a bit confusing, but we want to skip hits that don't have an original hit. - # Hit is invalid in a retest if not linked to an original. + # The negatives are a bit confusing, but we want to skip hits that + # don't have an original hit. Hit is invalid in a retest if not linked + # to an original. if "original_hit" not in hit or not hit["original_hit"]: return None # Get the original hit from the retest original_hit = hit["original_hit"] - # Set the Finding title to the original hit title with the retest status if available - # We don't really have any other places to set this field. + # Set the Finding title to the original hit title with the retest + # status if available. We don't really have any other places to set + # this field. finding_title = generate_retest_hit_title(hit, original_hit) - # As the retest hit doesn't have a date added, use the start of the retest campaign as something that's - # close enough. + # As the retest hit doesn't have a date added, use the start of the + # retest campaign as something that's close enough. finding = Finding( title=finding_title, - severity=ptart_tools.parse_ptart_severity(original_hit.get("severity", 5)), - effort_for_fixing=ptart_tools.parse_ptart_fix_effort(original_hit.get("fix_complexity", 3)), + severity=ptart_tools.parse_ptart_severity( + original_hit.get("severity") + ), + effort_for_fixing=ptart_tools.parse_ptart_fix_effort( + 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"), + date=ptart_tools.parse_date( + retest.get("start_date"), + "%Y-%m-%d" + ), ) # Don't add the fields if they are blank. @@ -71,16 +81,22 @@ def get_finding(self, retest, hit): if "id" in hit and hit["id"]: finding.unique_id_from_tool = hit.get("id") - cvss_vector = ptart_tools.parse_cvss_vector(original_hit, self.cvss_type) + cvss_vector = ptart_tools.parse_cvss_vector( + original_hit, + self.cvss_type + ) if cvss_vector: finding.cvssv3 = cvss_vector if "labels" in original_hit: finding.unsaved_tags = original_hit["labels"] - finding.unsaved_endpoints = ptart_tools.parse_endpoints_from_hit(original_hit) + finding.unsaved_endpoints = ptart_tools.parse_endpoints_from_hit( + original_hit + ) - # We only have screenshots in a retest. Refer to the original hit for the attachments. + # We only have screenshots in a retest. Refer to the original hit for + # the attachments. finding.unsaved_files = ptart_tools.parse_screenshots_from_hit(hit) return finding diff --git a/unittests/tools/test_ptart_parser.py b/unittests/tools/test_ptart_parser.py index eb0e30e121d..deb9ea5e907 100644 --- a/unittests/tools/test_ptart_parser.py +++ b/unittests/tools/test_ptart_parser.py @@ -97,21 +97,21 @@ def test_ptart_parser_tools_cvss_vector_acquisition(self): self.assertEqual(None, parse_cvss_vector(hit, 3)) def test_ptart_parser_tools_retest_fix_status_parse(self): - from dojo.tools.ptart.ptart_parser_tools import parse_retest_fix_status + from dojo.tools.ptart.ptart_parser_tools import parse_retest_status with self.subTest("Fixed"): - self.assertEqual("Fixed", parse_retest_fix_status("F")) + self.assertEqual("Fixed", parse_retest_status("F")) with self.subTest("Not Fixed"): - self.assertEqual("Not Fixed", parse_retest_fix_status("NF")) + self.assertEqual("Not Fixed", parse_retest_status("NF")) with self.subTest("Partially Fixed"): - self.assertEqual("Partially Fixed", parse_retest_fix_status("PF")) + self.assertEqual("Partially Fixed", parse_retest_status("PF")) with self.subTest("Not Applicable"): - self.assertEqual("Not Applicable", parse_retest_fix_status("NA")) + self.assertEqual("Not Applicable", parse_retest_status("NA")) with self.subTest("Not Tested"): - self.assertEqual("Not Tested", parse_retest_fix_status("NT")) + self.assertEqual("Not Tested", parse_retest_status("NT")) with self.subTest("Unknown"): - self.assertEqual(None, parse_retest_fix_status("U")) + self.assertEqual(None, parse_retest_status("U")) with self.subTest("Empty"): - self.assertEqual(None, parse_retest_fix_status("")) + self.assertEqual(None, parse_retest_status("")) def test_ptart_parser_tools_parse_screenshots_from_hit(self): from dojo.tools.ptart.ptart_parser_tools import parse_screenshots_from_hit @@ -290,25 +290,25 @@ def test_ptart_parser_tools_parse_attachment_from_hit(self): self.assertTrue(attachment["data"] == "TUlUIExpY2Vuc2UKCkNvcHl", "Invalid Attachment Data") def test_ptart_parser_tools_get_description_from_report_base(self): - from dojo.tools.ptart.ptart_parser_tools import generate_test_description_from_report_base + from dojo.tools.ptart.ptart_parser_tools import generate_test_description_from_report with self.subTest("No Description"): data = {} - self.assertEqual(None, generate_test_description_from_report_base(data)) + self.assertEqual(None, generate_test_description_from_report(data)) with self.subTest("Description from Executive Summary Only"): data = { "executive_summary": "This is a summary" } - self.assertEqual("This is a summary", generate_test_description_from_report_base(data)) + 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" } - self.assertEqual("This is an overview", generate_test_description_from_report_base(data)) + 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" } - self.assertEqual("This is a conclusion", generate_test_description_from_report_base(data)) + 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", @@ -316,42 +316,42 @@ def test_ptart_parser_tools_get_description_from_report_base(self): "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_base(data)) + 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" } self.assertEqual("This is a summary\n\nThis is a conclusion", - generate_test_description_from_report_base(data)) + 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" } self.assertEqual("This is a summary\n\nThis is an overview", - generate_test_description_from_report_base(data)) + 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" } self.assertEqual("This is an overview\n\nThis is a conclusion", - generate_test_description_from_report_base(data)) + generate_test_description_from_report(data)) with self.subTest("Description from All Sections with Empty Strings"): data = { "executive_summary": "", "engagement_overview": "", "conclusion": "" } - self.assertEqual(None, generate_test_description_from_report_base(data)) + 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": "" } - self.assertEqual("This is an overview", generate_test_description_from_report_base(data)) + 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: