From 43e01d3185f17f75e578c7285b0c85335c6136b2 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 12 May 2025 11:41:56 -0600 Subject: [PATCH 1/5] Tags: Add support for comma separation for multipart forms (import/reimport) --- dojo/api_v2/serializers.py | 6 ++++-- unittests/dojo_test_case.py | 6 ++++++ unittests/test_tags.py | 23 +++++++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index c9e93a92183..7ea1c693b5a 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -225,9 +225,11 @@ def to_internal_value(self, data): self.fail("not_a_str") # Run the children validation self.child.run_validation(s) - # Validate the tag to ensure it doesn't contain invalid characters - tag_validator(s, exception_class=RestFrameworkValidationError) + # Split the tags up in any way we need to substrings = re.findall(r'(?:"[^"]*"|[^",]+)', s) + # Validate the tag to ensure it doesn't contain invalid characters + for sub in substrings: + tag_validator(sub, exception_class=RestFrameworkValidationError) data_safe.extend(substrings) return tagulous.utils.render_tags(data_safe) diff --git a/unittests/dojo_test_case.py b/unittests/dojo_test_case.py index 1851a84ba75..0ee5335875b 100644 --- a/unittests/dojo_test_case.py +++ b/unittests/dojo_test_case.py @@ -480,6 +480,12 @@ def import_scan(self, payload, expected_http_status_code): response = self.client.post(reverse("importscan-list"), payload) self.assertEqual(expected_http_status_code, response.status_code, response.content[:1000]) return json.loads(response.content) + + def multipart_import_scan(self, payload, expected_http_status_code): + logger.debug("import_scan payload %s", payload) + response = self.client.post(reverse("importscan-list"), data=payload, format="multipart") + self.assertEqual(expected_http_status_code, response.status_code, response.content[:1000]) + return json.loads(response.content) def reimport_scan(self, payload, expected_http_status_code): logger.debug("reimport_scan payload %s", payload) diff --git a/unittests/test_tags.py b/unittests/test_tags.py index e431f7e94e6..ecabaec0234 100644 --- a/unittests/test_tags.py +++ b/unittests/test_tags.py @@ -163,10 +163,6 @@ def test_finding_patch_remove_tags_all(self): def test_finding_patch_remove_tags_non_existent(self): return self.test_finding_put_remove_tags_non_existent() - def test_finding_create_tags_with_commas(self): - tags = ["one,two"] - self.create_finding_with_tags(tags, expected_status_code=400) - def test_finding_create_tags_with_spaces(self): tags = ["one two"] self.create_finding_with_tags(tags, expected_status_code=400) @@ -212,6 +208,25 @@ def test_import_and_reimport_with_tags(self): for tag in tags: self.assertIn(tag, response["tags"]) + def test_import_reimport_multipart_tags(self): + with (self.zap_sample5_filename).open(encoding="utf-8") as testfile: + data = { + "engagement": [1], + "file": [testfile], + "scan_type": ["ZAP Scan"], + "tags": ["bug,security", "urgent"], + } + response = self.multipart_import_scan(data, 201) + # Make sure the serializer returns the correct tags + success_tags = ["bug", "security", "urgent"] + self.assertEqual(response["tags"], success_tags) + # Check that the test has the same issue + test_id = response["test"] + response = self.get_test_api(test_id) + self.assertEqual(len(success_tags), len(response.get("tags"))) + for tag in success_tags: + self.assertIn(tag, response["tags"]) + class InheritedTagsTests(DojoAPITestCase): fixtures = ["dojo_testdata.json"] From 5630b5c6317e06ed09c361adfd66ce36adbeb723 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 12 May 2025 11:42:43 -0600 Subject: [PATCH 2/5] Correcting ruff --- unittests/dojo_test_case.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unittests/dojo_test_case.py b/unittests/dojo_test_case.py index 0ee5335875b..2979fac68cf 100644 --- a/unittests/dojo_test_case.py +++ b/unittests/dojo_test_case.py @@ -480,10 +480,10 @@ def import_scan(self, payload, expected_http_status_code): response = self.client.post(reverse("importscan-list"), payload) self.assertEqual(expected_http_status_code, response.status_code, response.content[:1000]) return json.loads(response.content) - + def multipart_import_scan(self, payload, expected_http_status_code): logger.debug("import_scan payload %s", payload) - response = self.client.post(reverse("importscan-list"), data=payload, format="multipart") + response = self.client.post(reverse("importscan-list"), data=payload, format="multipart") self.assertEqual(expected_http_status_code, response.status_code, response.content[:1000]) return json.loads(response.content) From 82d9c717722dd3e578a297caa5a76c8c6944247a Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 12 May 2025 12:23:13 -0600 Subject: [PATCH 3/5] Revert multipart specific tests --- unittests/dojo_test_case.py | 6 ------ unittests/test_tags.py | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/unittests/dojo_test_case.py b/unittests/dojo_test_case.py index 2979fac68cf..1851a84ba75 100644 --- a/unittests/dojo_test_case.py +++ b/unittests/dojo_test_case.py @@ -481,12 +481,6 @@ def import_scan(self, payload, expected_http_status_code): self.assertEqual(expected_http_status_code, response.status_code, response.content[:1000]) return json.loads(response.content) - def multipart_import_scan(self, payload, expected_http_status_code): - logger.debug("import_scan payload %s", payload) - response = self.client.post(reverse("importscan-list"), data=payload, format="multipart") - self.assertEqual(expected_http_status_code, response.status_code, response.content[:1000]) - return json.loads(response.content) - def reimport_scan(self, payload, expected_http_status_code): logger.debug("reimport_scan payload %s", payload) response = self.client.post(reverse("reimportscan-list"), payload) diff --git a/unittests/test_tags.py b/unittests/test_tags.py index ecabaec0234..54c7e576053 100644 --- a/unittests/test_tags.py +++ b/unittests/test_tags.py @@ -214,9 +214,9 @@ def test_import_reimport_multipart_tags(self): "engagement": [1], "file": [testfile], "scan_type": ["ZAP Scan"], - "tags": ["bug,security", "urgent"], + "tags": ["bug,security", "urgent"], # Attempting to mimic the two "tag" fields (-F 'tags=tag1' -F 'tags=tag2') } - response = self.multipart_import_scan(data, 201) + response = self.import_scan(data, 201) # Make sure the serializer returns the correct tags success_tags = ["bug", "security", "urgent"] self.assertEqual(response["tags"], success_tags) From 48d6b557c8415747f8632d03f0df44be724eb023 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 12 May 2025 12:27:29 -0600 Subject: [PATCH 4/5] Ruff stuff --- unittests/test_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/test_tags.py b/unittests/test_tags.py index 54c7e576053..686ffc049b3 100644 --- a/unittests/test_tags.py +++ b/unittests/test_tags.py @@ -214,7 +214,7 @@ def test_import_reimport_multipart_tags(self): "engagement": [1], "file": [testfile], "scan_type": ["ZAP Scan"], - "tags": ["bug,security", "urgent"], # Attempting to mimic the two "tag" fields (-F 'tags=tag1' -F 'tags=tag2') + "tags": ["bug,security", "urgent"], # Attempting to mimic the two "tag" fields (-F 'tags=tag1' -F 'tags=tag2') } response = self.import_scan(data, 201) # Make sure the serializer returns the correct tags From a0652a306ccc7a85c90cb2a1fcf948acfcf55596 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 12 May 2025 17:31:40 -0600 Subject: [PATCH 5/5] Update unittests/test_tags.py Co-authored-by: valentijnscholten --- unittests/test_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/test_tags.py b/unittests/test_tags.py index 686ffc049b3..5be9f65db1c 100644 --- a/unittests/test_tags.py +++ b/unittests/test_tags.py @@ -208,7 +208,7 @@ def test_import_and_reimport_with_tags(self): for tag in tags: self.assertIn(tag, response["tags"]) - def test_import_reimport_multipart_tags(self): + def test_import_multipart_tags(self): with (self.zap_sample5_filename).open(encoding="utf-8") as testfile: data = { "engagement": [1],