diff --git a/dojo/reports/widgets.py b/dojo/reports/widgets.py index 0dc5df4e1bd..c5bba864897 100644 --- a/dojo/reports/widgets.py +++ b/dojo/reports/widgets.py @@ -19,7 +19,7 @@ ) from dojo.forms import CustomReportOptionsForm from dojo.models import Endpoint, Finding -from dojo.utils import get_page_items, get_system_setting, get_words_for_field +from dojo.utils import first_elem, get_page_items, get_system_setting, get_words_for_field """ Widgets are content sections that can be included on reports. The report builder will allow any number of widgets @@ -371,10 +371,12 @@ def report_widget_factory(json_data=None, request=None, user=None, *, finding_no widgets = json.loads(json_data) for idx, widget in enumerate(widgets): - if list(widget.keys())[0] == "page-break": - selected_widgets[list(widget.keys())[0] + "-" + str(idx)] = PageBreak() + first_widget_keys = first_elem(widget.keys()) - if list(widget.keys())[0] == "endpoint-list": + if first_widget_keys == "page-break": + selected_widgets[first_widget_keys + "-" + str(idx)] = PageBreak() + + if first_widget_keys == "endpoint-list": endpoints = Endpoint.objects.filter(finding__active=True, finding__false_p=False, finding__duplicate=False, @@ -386,7 +388,7 @@ def report_widget_factory(json_data=None, request=None, user=None, *, finding_no endpoints = endpoints.distinct() d = QueryDict(mutable=True) - for item in widget.get(list(widget.keys())[0]): + for item in widget.get(first_widget_keys): if item["name"] in d: d.appendlist(item["name"], item["value"]) else: @@ -400,12 +402,12 @@ def report_widget_factory(json_data=None, request=None, user=None, *, finding_no endpoints = EndpointList(request=request, endpoints=endpoints, finding_notes=finding_notes, finding_images=finding_images, host=host, user_id=user_id) - selected_widgets[list(widget.keys())[0] + "-" + str(idx)] = endpoints + selected_widgets[first_widget_keys + "-" + str(idx)] = endpoints - if list(widget.keys())[0] == "finding-list": + if first_widget_keys == "finding-list": findings = Finding.objects.all() d = QueryDict(mutable=True) - for item in widget.get(list(widget.keys())[0]): + for item in widget.get(first_widget_keys): if item["name"] in d: d.appendlist(item["name"], item["value"]) else: @@ -414,47 +416,47 @@ def report_widget_factory(json_data=None, request=None, user=None, *, finding_no filter_class = ReportFindingFilterWithoutObjectLookups if filter_string_matching else ReportFindingFilter findings = filter_class(d, queryset=findings) user_id = user.id if user is not None else None - selected_widgets[list(widget.keys())[0] + "-" + str(idx)] = FindingList(request=request, findings=findings, + selected_widgets[first_widget_keys + "-" + str(idx)] = FindingList(request=request, findings=findings, finding_notes=finding_notes, finding_images=finding_images, host=host, user_id=user_id) - if list(widget.keys())[0] == "custom-content": + if first_widget_keys == "custom-content": wysiwyg_content = WYSIWYGContent(request=request) wysiwyg_content.heading = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "heading"), None)["value"] + next((item for item in widget.get(first_widget_keys) if item["name"] == "heading"), None)["value"] wysiwyg_content.content = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "hidden_content"), None)["value"] + next((item for item in widget.get(first_widget_keys) if item["name"] == "hidden_content"), None)["value"] wysiwyg_content.page_break_after = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "page_break_after"), + next((item for item in widget.get(first_widget_keys) if item["name"] == "page_break_after"), {"value": False})["value"] - selected_widgets[list(widget.keys())[0] + "-" + str(idx)] = wysiwyg_content - if list(widget.keys())[0] == "report-options": + selected_widgets[first_widget_keys + "-" + str(idx)] = wysiwyg_content + if first_widget_keys == "report-options": options = ReportOptions(request=request) options.include_finding_notes = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "include_finding_notes"), None)[ + next((item for item in widget.get(first_widget_keys) if item["name"] == "include_finding_notes"), None)[ "value"] options.include_finding_images = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "include_finding_images"), None)[ + next((item for item in widget.get(first_widget_keys) if item["name"] == "include_finding_images"), None)[ "value"] options.report_type = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "report_type"), None)["value"] + next((item for item in widget.get(first_widget_keys) if item["name"] == "report_type"), None)["value"] options.report_name = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "report_name"), None)["value"] - selected_widgets[list(widget.keys())[0]] = options - if list(widget.keys())[0] == "table-of-contents": + next((item for item in widget.get(first_widget_keys) if item["name"] == "report_name"), None)["value"] + selected_widgets[first_widget_keys] = options + if first_widget_keys == "table-of-contents": toc = TableOfContents(request=request) - toc.heading = next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "heading"), None)[ + toc.heading = next((item for item in widget.get(first_widget_keys) if item["name"] == "heading"), None)[ "value"] - selected_widgets[list(widget.keys())[0]] = toc - if list(widget.keys())[0] == "cover-page": + selected_widgets[first_widget_keys] = toc + if first_widget_keys == "cover-page": cover_page = CoverPage(request=request) - cover_page.heading = next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "heading"), None)[ + cover_page.heading = next((item for item in widget.get(first_widget_keys) if item["name"] == "heading"), None)[ "value"] cover_page.sub_heading = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "sub_heading"), None)["value"] + next((item for item in widget.get(first_widget_keys) if item["name"] == "sub_heading"), None)["value"] cover_page.meta_info = \ - next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == "meta_info"), None)["value"] - selected_widgets[list(widget.keys())[0]] = cover_page + next((item for item in widget.get(first_widget_keys) if item["name"] == "meta_info"), None)["value"] + selected_widgets[first_widget_keys] = cover_page return selected_widgets diff --git a/dojo/tools/api_cobalt/api_client.py b/dojo/tools/api_cobalt/api_client.py index acd01635e90..4c6309f8c36 100644 --- a/dojo/tools/api_cobalt/api_client.py +++ b/dojo/tools/api_cobalt/api_client.py @@ -1,6 +1,8 @@ import requests from django.conf import settings +from dojo.utils import first_elem + class CobaltAPI: @@ -90,7 +92,7 @@ def test_connection(self): orgs = filter( lambda org: org["resource"]["token"] == self.org_token, data, ) - org = list(orgs)[0] + org = first_elem(orgs) org_name = org["resource"]["name"] return f'You have access to the "{org_name}" organization' msg = ( diff --git a/dojo/tools/burp_enterprise/parser.py b/dojo/tools/burp_enterprise/parser.py index 58b2a5a6ea6..8b455a20038 100644 --- a/dojo/tools/burp_enterprise/parser.py +++ b/dojo/tools/burp_enterprise/parser.py @@ -4,6 +4,7 @@ from lxml import etree, html from dojo.models import Endpoint, Finding +from dojo.utils import first_elem logger = logging.getLogger(__name__) @@ -117,7 +118,7 @@ def _get_content(self, container: etree.Element): if stripped_text is not None: value += stripped_text + "\n" elif stripped_text.isspace(): - value = list(elem.itertext())[0] + value = first_elem(elem.itertext()) elif elem.tag == "div" or elem.tag == "span": value = elem.text_content().strip().replace("\n", "") + "\n" else: diff --git a/dojo/tools/hadolint/parser.py b/dojo/tools/hadolint/parser.py index d781e83b8a0..459b0135c28 100644 --- a/dojo/tools/hadolint/parser.py +++ b/dojo/tools/hadolint/parser.py @@ -30,7 +30,7 @@ def get_items(self, tree, test): ) items[unique_key] = item - return items.values() + return list(items.values()) def get_item(vulnerability, test): diff --git a/dojo/tools/harbor_vulnerability/parser.py b/dojo/tools/harbor_vulnerability/parser.py index d99fab69088..b5c0e2fc490 100644 --- a/dojo/tools/harbor_vulnerability/parser.py +++ b/dojo/tools/harbor_vulnerability/parser.py @@ -2,6 +2,7 @@ import json from dojo.models import Finding +from dojo.utils import first_elem class HarborVulnerabilityParser: @@ -32,7 +33,7 @@ def get_findings(self, filename, test): vulnerability = data["vulnerabilities"] # To be compatible with update in version with contextlib.suppress(KeyError, StopIteration, TypeError): - vulnerability = data[next(iter(data.keys()))]["vulnerabilities"] + vulnerability = data[first_elem(data.keys())]["vulnerabilities"] # Early exit if empty if "vulnerability" not in locals() or vulnerability is None: diff --git a/dojo/tools/sarif/parser.py b/dojo/tools/sarif/parser.py index 4b93e6a48cc..a4818568bed 100644 --- a/dojo/tools/sarif/parser.py +++ b/dojo/tools/sarif/parser.py @@ -8,6 +8,7 @@ from dojo.models import Finding from dojo.tools.parser_test import ParserTest +from dojo.utils import first_elem logger = logging.getLogger(__name__) @@ -460,7 +461,7 @@ def get_items_from_result(result, rules, artifacts, run_date): # compare it if result.get("fingerprints"): hashes = get_fingerprints_hashes(result["fingerprints"]) - first_item = next(iter(hashes.items())) + first_item = first_elem(hashes.items()) finding.unique_id_from_tool = first_item[1]["value"] elif result.get("partialFingerprints"): # for this one we keep an order to have id that could be compared diff --git a/dojo/tools/sonarqube/soprasteria_html.py b/dojo/tools/sonarqube/soprasteria_html.py index c4fb4e688cd..198ac8eac7e 100644 --- a/dojo/tools/sonarqube/soprasteria_html.py +++ b/dojo/tools/sonarqube/soprasteria_html.py @@ -1,6 +1,7 @@ import logging from dojo.tools.sonarqube.soprasteria_helper import SonarQubeSoprasteriaHelper +from dojo.utils import first_elem logger = logging.getLogger(__name__) @@ -23,13 +24,13 @@ def get_items(self, tree, test, mode): rulesDic = {} for rule in rules_table: rule_properties = list(rule.iter("td")) - rule_name = list(rule_properties[0].iter("a"))[0].text.strip() - rule_details = list(rule_properties[1].iter("details"))[0] + rule_name = first_elem(rule_properties[0].iter("a")).text.strip() + rule_details = first_elem(rule_properties[1].iter("details")) rulesDic[rule_name] = rule_details for vuln in vulnerabilities_table: vuln_properties = list(vuln.iter("td")) - rule_key = list(vuln_properties[0].iter("a"))[0].text + rule_key = first_elem(vuln_properties[0].iter("a")).text vuln_rule_name = rule_key and rule_key.strip() vuln_severity = SonarQubeSoprasteriaHelper().convert_sonar_severity( vuln_properties[1].text and vuln_properties[1].text.strip(), diff --git a/dojo/tools/trufflehog/parser.py b/dojo/tools/trufflehog/parser.py index a7f8334625a..f81bc509ae5 100644 --- a/dojo/tools/trufflehog/parser.py +++ b/dojo/tools/trufflehog/parser.py @@ -2,6 +2,7 @@ import json from dojo.models import Finding +from dojo.utils import first_elem class TruffleHogParser: @@ -112,7 +113,7 @@ def get_findings_v3(self, data, test): source = {} source_data = {} if metadata: - source = list(metadata.keys())[0] + source = first_elem(metadata.keys()) source_data = metadata.get(source) file = source_data.get("file", "") diff --git a/dojo/tools/veracode_sca/parser.py b/dojo/tools/veracode_sca/parser.py index 81dcb48d589..c8b4101ae49 100644 --- a/dojo/tools/veracode_sca/parser.py +++ b/dojo/tools/veracode_sca/parser.py @@ -9,6 +9,7 @@ from django.utils import timezone from dojo.models import Finding +from dojo.utils import first_elem class VeracodeScaParser: @@ -159,7 +160,7 @@ def get_findings_csv(self, file, test): issueId = row.get("Issue ID", None) if not issueId: # Workaround for possible encoding issue - issueId = list(row.values())[0] + issueId = first_elem(row.values()) library = row.get("Library", None) if row.get("Package manager") == "MAVEN" and row.get( "Coordinate 2", diff --git a/dojo/utils.py b/dojo/utils.py index f9e8e5a203e..230e62391a4 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -176,7 +176,7 @@ def match_finding_to_existing_findings(finding, product=None, engagement=None, t deduplicationLogger.debug( "Matching finding %i:%s to existing findings in %s %s using %s as deduplication algorithm.", - finding.id, finding.title, custom_filter_type, list(custom_filter.values())[0], deduplication_algorithm, + finding.id, finding.title, custom_filter_type, first_elem(custom_filter.values()), deduplication_algorithm, ) if deduplication_algorithm == "hash_code": @@ -2701,3 +2701,9 @@ def generate_file_response_from_file_path( response["Content-Disposition"] = f'attachment; filename="{full_file_name}"' response["Content-Length"] = file_size return response + + +def first_elem(x): + # This function is workaround for using of `list(...)[0]`. + # RUF015 recommends to use `next(iter(x))` but it is harder for reading in regular code + return next(iter(x)) diff --git a/ruff.toml b/ruff.toml index 3a34a063009..a4b2fbe75dd 100644 --- a/ruff.toml +++ b/ruff.toml @@ -97,7 +97,6 @@ ignore = [ "SIM102", "SIM115", "RUF012", - "RUF015", "D205", "FIX002", # TODOs need some love but we will probably not get of them "D211", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. diff --git a/tests/import_scanner_test.py b/tests/import_scanner_test.py index f3b0372c6a9..0a47b3e8e28 100644 --- a/tests/import_scanner_test.py +++ b/tests/import_scanner_test.py @@ -13,6 +13,8 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import Select +from dojo.utils import first_elem + dir_path = Path(os.path.realpath(__file__)).parent logger = logging.getLogger(__name__) @@ -171,7 +173,7 @@ def test_engagement_import_scan_result(self): found_matches[index] = matches[0] if len(found_matches) == 1: - index = list(found_matches.keys())[0] + index = first_elem(found_matches.keys()) scan_map[test] = options_text[index] elif len(found_matches) > 1: index = list(found_matches.values()).index(temp_test) diff --git a/unittests/tools/test_blackduck_parser.py b/unittests/tools/test_blackduck_parser.py index aaa9b723185..6ed6e26283b 100644 --- a/unittests/tools/test_blackduck_parser.py +++ b/unittests/tools/test_blackduck_parser.py @@ -22,7 +22,6 @@ def test_blackduck_csv_parser_has_many_findings(self): parser = BlackduckParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(24, len(findings)) - findings = list(findings) self.assertEqual(1, len(findings[10].unsaved_vulnerability_ids)) self.assertEqual("CVE-2007-3386", findings[10].unsaved_vulnerability_ids[0]) self.assertEqual(findings[4].component_name, "Apache Tomcat") @@ -34,7 +33,6 @@ def test_blackduck_csv_parser_new_format_has_many_findings(self): testfile = get_unit_tests_scans_path("blackduck") / "many_vulns_new_format.csv" parser = BlackduckParser() findings = parser.get_findings(testfile, Test()) - findings = list(findings) self.assertEqual(9, len(findings)) self.assertEqual(findings[0].component_name, "kryo") self.assertEqual(findings[2].component_name, "jackson-databind") diff --git a/unittests/tools/test_hadolint_parser.py b/unittests/tools/test_hadolint_parser.py index 74b24f54517..d9a2b8a4f96 100644 --- a/unittests/tools/test_hadolint_parser.py +++ b/unittests/tools/test_hadolint_parser.py @@ -11,7 +11,7 @@ def test_parse_file_with_one_dockerfile(self): findings = parser.get_findings(testfile, Test()) testfile.close() self.assertEqual(4, len(findings)) - finding = list(findings)[0] + finding = findings[0] self.assertEqual(finding.line, 9) self.assertEqual(finding.file_path, "django-DefectDojo\\Dockerfile.django") diff --git a/unittests/tools/test_intsights_parser.py b/unittests/tools/test_intsights_parser.py index 0b77b9591b2..a82c6558db5 100644 --- a/unittests/tools/test_intsights_parser.py +++ b/unittests/tools/test_intsights_parser.py @@ -12,7 +12,7 @@ def test_intsights_parser_with_one_critical_vuln_has_one_findings_json( self.assertEqual(1, len(findings)) - finding = list(findings)[0] + finding = findings[0] self.assertEqual( "5c80dbf83b4a3900078b6be6", @@ -32,7 +32,7 @@ def test_intsights_parser_with_one_critical_vuln_has_one_findings_csv( findings = parser.get_findings(testfile, Test()) self.assertEqual(1, len(findings)) - finding = list(findings)[0] + finding = findings[0] self.assertEqual( "mn7xy83finmmth4ja363rci9", diff --git a/unittests/tools/test_mend_parser.py b/unittests/tools/test_mend_parser.py index 7aa28f3cd8c..378d6d877a4 100644 --- a/unittests/tools/test_mend_parser.py +++ b/unittests/tools/test_mend_parser.py @@ -16,7 +16,7 @@ def test_parse_file_with_one_vuln_has_one_findings(self): parser = MendParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(1, len(findings)) - finding = list(findings)[0] + finding = findings[0] self.assertEqual(1, len(finding.unsaved_vulnerability_ids)) self.assertEqual("CVE-2019-9658", finding.unsaved_vulnerability_ids[0]) self.assertEqual("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", finding.cvssv3) @@ -41,7 +41,7 @@ def test_parse_file_with_one_sca_vuln_finding(self): parser = MendParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(1, len(findings)) - finding = list(findings)[0] + finding = findings[0] self.assertEqual("**Locations Found**: D:\\MendRepo\\test-product\\test-project\\test-project-subcomponent\\path\\to\\the\\Java\\commons-codec-1.6_donotuse.jar", finding.steps_to_reproduce) self.assertEqual("WS-2019-0379 | commons-codec-1.6.jar", finding.title) @@ -56,7 +56,7 @@ def test_parse_file_with_one_vuln_has_one_findings_platform(self): parser = MendParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(1, len(findings)) - finding = list(findings)[0] + finding = findings[0] self.assertEqual(1, len(finding.unsaved_vulnerability_ids)) self.assertEqual("CVE-2024-51744", finding.unsaved_vulnerability_ids[0]) self.assertEqual("CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", finding.cvssv3)