From bdacaef8a784e775bdd1c7d1bf206c134ff6f766 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Tue, 12 May 2026 18:29:53 +0200 Subject: [PATCH 1/4] test(integrations): add tests for defectdojo.groupBy per-project property Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> --- .../defectdojo/DefectDojoUploaderTest.java | 26 ++- .../tasks/DefectDojoUploadTaskTest.java | 178 ++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) diff --git a/apiserver/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java b/apiserver/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java index c8c1e207f9..e497822878 100644 --- a/apiserver/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java +++ b/apiserver/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java @@ -93,4 +93,28 @@ void testIntegrationDisabledCases() { Assertions.assertFalse(extension.isProjectConfigured(project)); } -} \ No newline at end of file + @Test + void testGetGroupByReturnsNullWhenNotConfigured() { + Project project = qm.createProject("ACME Example", null, "1.0", null, null, null, true, false); + DefectDojoUploader extension = new DefectDojoUploader(httpClient, secretManager); + extension.setQueryManager(qm); + Assertions.assertNull(extension.getGroupBy(project)); + } + + @Test + void testGetGroupByReturnsValueWhenConfigured() { + Project project = qm.createProject("ACME Example", null, "1.0", null, null, null, true, false); + qm.createProjectProperty( + project, + DEFECTDOJO_ENABLED.getGroupName(), + "defectdojo.groupBy", + "component_name", + IConfigProperty.PropertyType.STRING, + null + ); + DefectDojoUploader extension = new DefectDojoUploader(httpClient, secretManager); + extension.setQueryManager(qm); + Assertions.assertEquals("component_name", extension.getGroupBy(project)); + } + +} diff --git a/apiserver/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java b/apiserver/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java index f5e0516d15..649d2616af 100644 --- a/apiserver/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java +++ b/apiserver/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java @@ -931,6 +931,184 @@ void testUploadWithReimportAndNoExistingTest(WireMockRuntimeInfo wmRuntimeInfo) """, true, false)))); } + @Test + void testUploadWithGroupBy() { + qm.createConfigProperty( + DEFECTDOJO_ENABLED.getGroupName(), + DEFECTDOJO_ENABLED.getPropertyName(), + "true", + DEFECTDOJO_ENABLED.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_URL.getGroupName(), + DEFECTDOJO_URL.getPropertyName(), + wmRuntimeInfo.getHttpBaseUrl(), + DEFECTDOJO_URL.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_API_KEY.getGroupName(), + DEFECTDOJO_API_KEY.getPropertyName(), + "dojoApiKey", + DEFECTDOJO_API_KEY.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_REIMPORT_ENABLED.getGroupName(), + DEFECTDOJO_REIMPORT_ENABLED.getPropertyName(), + DEFECTDOJO_REIMPORT_ENABLED.getDefaultPropertyValue(), + DEFECTDOJO_REIMPORT_ENABLED.getPropertyType(), + null + ); + + stubFor(post(urlPathEqualTo("/api/v2/import-scan/")) + .willReturn(aResponse() + .withStatus(201))); + + final var project = new Project(); + project.setName("acme-app"); + project.setVersion("1.0.0"); + qm.persist(project); + + final var component = new Component(); + component.setProject(project); + component.setName("acme-lib"); + component.setVersion("1.2.3"); + qm.persist(component); + + qm.createProjectProperty(project, "integrations", "defectdojo.engagementId", + "666", IConfigProperty.PropertyType.STRING, null); + qm.createProjectProperty(project, "integrations", "defectdojo.groupBy", + "component_name", IConfigProperty.PropertyType.STRING, null); + + new DefectDojoUploadTask().inform(new DefectDojoUploadEventAbstract()); + + verify(postRequestedFor(urlPathEqualTo("/api/v2/import-scan/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token dojoApiKey")) + .withAnyRequestBodyPart(aMultipart() + .withName("engagement") + .withBody(equalTo("666"))) + .withAnyRequestBodyPart(aMultipart() + .withName("group_by") + .withBody(equalTo("component_name")))); + } + + @Test + void testUploadWithReimportAndGroupBy() { + qm.createConfigProperty( + DEFECTDOJO_ENABLED.getGroupName(), + DEFECTDOJO_ENABLED.getPropertyName(), + "true", + DEFECTDOJO_ENABLED.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_URL.getGroupName(), + DEFECTDOJO_URL.getPropertyName(), + wmRuntimeInfo.getHttpBaseUrl(), + DEFECTDOJO_URL.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_API_KEY.getGroupName(), + DEFECTDOJO_API_KEY.getPropertyName(), + "dojoApiKey", + DEFECTDOJO_API_KEY.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_REIMPORT_ENABLED.getGroupName(), + DEFECTDOJO_REIMPORT_ENABLED.getPropertyName(), + "false", + DEFECTDOJO_REIMPORT_ENABLED.getPropertyType(), + null + ); + + stubFor(get(urlPathEqualTo("/api/v2/tests/")) + .withQueryParam("engagement", equalTo("666")) + .withQueryParam("limit", equalTo("100")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token dojoApiKey")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .withBody(""" + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 1, + "tags": [], + "test_type_name": "Dependency Track Finding Packaging Format (FPF) Export", + "finding_groups": [], + "scan_type": "Dependency Track Finding Packaging Format (FPF) Export", + "title": null, + "description": null, + "target_start": "2023-04-29T00:00:00Z", + "target_end": "2023-04-29T21:39:21.513481Z", + "estimated_time": null, + "actual_time": null, + "percent_complete": 100, + "updated": "2023-04-29T21:39:21.617857Z", + "created": "2023-04-29T21:39:21.516216Z", + "version": "", + "build_id": "", + "commit_hash": "", + "branch_tag": "", + "engagement": 666, + "lead": 1, + "test_type": 63, + "environment": 7, + "api_scan_configuration": null, + "notes": [], + "files": [] + } + ], + "prefetch": {} + } + """))); + + stubFor(post(urlPathEqualTo("/api/v2/reimport-scan/")) + .willReturn(aResponse() + .withStatus(201))); + + final var project = new Project(); + project.setName("acme-app"); + project.setVersion("1.0.0"); + qm.persist(project); + + final var component = new Component(); + component.setProject(project); + component.setName("acme-lib"); + component.setVersion("1.2.3"); + qm.persist(component); + + qm.createProjectProperty(project, "integrations", "defectdojo.engagementId", + "666", IConfigProperty.PropertyType.STRING, null); + qm.createProjectProperty(project, "integrations", "defectdojo.reimport", + "true", IConfigProperty.PropertyType.BOOLEAN, null); + qm.createProjectProperty(project, "integrations", "defectdojo.groupBy", + "component_name+component_version", IConfigProperty.PropertyType.STRING, null); + + new DefectDojoUploadTask().inform(new DefectDojoUploadEventAbstract()); + + verify(1, getRequestedFor(urlPathEqualTo("/api/v2/tests/"))); + + verify(postRequestedFor(urlPathEqualTo("/api/v2/reimport-scan/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token dojoApiKey")) + .withAnyRequestBodyPart(aMultipart() + .withName("engagement") + .withBody(equalTo("666"))) + .withAnyRequestBodyPart(aMultipart() + .withName("test") + .withBody(equalTo("1"))) + .withAnyRequestBodyPart(aMultipart() + .withName("group_by") + .withBody(equalTo("component_name+component_version")))); + } + /** * Un-ignore this test to test the integration against a local DefectDojo deployment. *
From 0a599dbfef523524d956faef109d2705f04fe26c Mon Sep 17 00:00:00 2001
From: webdevred <148627186+webdevred@users.noreply.github.com>
Date: Tue, 12 May 2026 18:30:02 +0200
Subject: [PATCH 2/4] feat(integrations): add defectdojo.groupBy per-project
property
When set, forwards the value as group_by in the DefectDojo import-scan
and reimport-scan multipart form requests, allowing findings to be
grouped into Finding Groups on import.
When not set, behavior is unchanged (backwards compatible).
Closes #6061
Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com>
---
.../defectdojo/DefectDojoClient.java | 12 +++++++++--
.../defectdojo/DefectDojoUploader.java | 20 ++++++++++++++++---
2 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java b/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java
index 3f1b7a89b3..edbcccf3b3 100644
--- a/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java
+++ b/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java
@@ -60,7 +60,8 @@ public void uploadDependencyTrackFindings(
final String engagementId,
final InputStream findingsJson,
final Boolean verifyFindings,
- final @Nullable String testTitle) {
+ final @Nullable String testTitle,
+ final @Nullable String groupBy) {
LOGGER.debug("Uploading Dependency-Track findings to DefectDojo");
final var multipart = new MultipartBodyPublisher()
@@ -76,6 +77,9 @@ public void uploadDependencyTrackFindings(
if (testTitle != null) {
multipart.addFormField("test_title", testTitle);
}
+ if (groupBy != null) {
+ multipart.addFormField("group_by", groupBy);
+ }
final var request = HttpRequest.newBuilder()
.uri(URI.create(baseURL + "/api/v2/import-scan/"))
@@ -187,7 +191,8 @@ public void reimportDependencyTrackFindings(
final String testId,
final Boolean doNotReactivate,
final Boolean verifyFindings,
- final @Nullable String testTitle) {
+ final @Nullable String testTitle,
+ final @Nullable String groupBy) {
LOGGER.debug("Re-reimport Dependency-Track findings to DefectDojo per Engagement");
final var multipart = new MultipartBodyPublisher()
@@ -205,6 +210,9 @@ public void reimportDependencyTrackFindings(
if (testTitle != null) {
multipart.addFormField("test_title", testTitle);
}
+ if (groupBy != null) {
+ multipart.addFormField("group_by", groupBy);
+ }
final var request = HttpRequest.newBuilder()
.uri(URI.create(baseURL + "/api/v2/reimport-scan/"))
diff --git a/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java b/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java
index f0034bb8d1..a0a1fcecd5 100644
--- a/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java
+++ b/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java
@@ -53,6 +53,7 @@ public class DefectDojoUploader extends AbstractIntegrationPoint implements Proj
private static final String DO_NOT_REACTIVATE_PROPERTY = "defectdojo.doNotReactivate";
private static final String VERIFIED_PROPERTY = "defectdojo.verified";
private static final String TEST_TITLE_PROPERTY = "defectdojo.testTitle";
+ private static final String GROUP_BY_PROPERTY = "defectdojo.groupBy";
private final HttpClient httpClient;
private final SecretManager secretManager;
@@ -98,6 +99,14 @@ private boolean isVerifiedConfigured(final Project project) {
return null;
}
+ public String getGroupBy(final Project project) {
+ final ProjectProperty groupBy = qm.getProjectProperty(project, DEFECTDOJO_ENABLED.getGroupName(), GROUP_BY_PROPERTY);
+ if (groupBy != null && groupBy.getPropertyValue() != null) {
+ return groupBy.getPropertyValue();
+ }
+ return null;
+ }
+
@Override
public String name() {
return "DefectDojo";
@@ -142,6 +151,8 @@ public void upload(final Project project, final InputStream payload) {
final boolean globalReimportEnabled = qm.isEnabled(DEFECTDOJO_REIMPORT_ENABLED);
final ProjectProperty engagementId = qm.getProjectProperty(project, DEFECTDOJO_ENABLED.getGroupName(), ENGAGEMENTID_PROPERTY);
final boolean verifyFindings = isVerifiedConfigured(project);
+ final String testTitle = getTestTitle(project);
+ final String groupBy = getGroupBy(project);
try {
final String apiKeyValue = secretManager.getSecretValue(apiKeySecretName);
if (apiKeyValue == null) {
@@ -160,7 +171,8 @@ public void upload(final Project project, final InputStream payload) {
engagementId.getPropertyValue(),
payload,
verifyFindings,
- testTitle);
+ testTitle,
+ groupBy);
} else {
client.reimportDependencyTrackFindings(
apiKeyValue,
@@ -169,7 +181,8 @@ public void upload(final Project project, final InputStream payload) {
testId,
isDoNotReactivateConfigured(project),
verifyFindings,
- testTitle);
+ testTitle,
+ groupBy);
}
} else {
client.uploadDependencyTrackFindings(
@@ -177,7 +190,8 @@ public void upload(final Project project, final InputStream payload) {
engagementId.getPropertyValue(),
payload,
verifyFindings,
- testTitle);
+ testTitle,
+ groupBy);
}
} catch (Exception e) {
LOGGER.error("An error occurred attempting to upload findings to DefectDojo", e);
From a9ce4fc681e869dcf60eacff7ec79fe2afd20010 Mon Sep 17 00:00:00 2001
From: webdevred <148627186+webdevred@users.noreply.github.com>
Date: Mon, 15 Jun 2026 15:34:08 +0200
Subject: [PATCH 3/4] fix(defectdojo): remove duplicate testTitle, fix
getGroupBy visibility, adapt group_by tests to v5 API
Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com>
---
.../defectdojo/DefectDojoUploader.java | 3 +--
.../tasks/DefectDojoUploadTaskTest.java | 18 ++++++++++++------
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java b/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java
index a0a1fcecd5..477fbae8f0 100644
--- a/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java
+++ b/apiserver/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploader.java
@@ -99,7 +99,7 @@ private boolean isVerifiedConfigured(final Project project) {
return null;
}
- public String getGroupBy(final Project project) {
+ private @Nullable String getGroupBy(final Project project) {
final ProjectProperty groupBy = qm.getProjectProperty(project, DEFECTDOJO_ENABLED.getGroupName(), GROUP_BY_PROPERTY);
if (groupBy != null && groupBy.getPropertyValue() != null) {
return groupBy.getPropertyValue();
@@ -159,7 +159,6 @@ public void upload(final Project project, final InputStream payload) {
LOGGER.warn("DefectDojo API key secret '%s' could not be resolved. Aborting".formatted(apiKeySecretName));
return;
}
- final String testTitle = getTestTitle(project);
final DefectDojoClient client = new DefectDojoClient(httpClient, this, URI.create(defectDojoUrl.getPropertyValue()).toURL());
if (isReimportConfigured(project) || globalReimportEnabled) {
final ArrayList