Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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/"))
Expand Down Expand Up @@ -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()
Expand All @@ -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/"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -98,6 +99,14 @@ private boolean isVerifiedConfigured(final Project project) {
return null;
}

@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();
}
return null;
}

@Override
public String name() {
return "DefectDojo";
Expand Down Expand Up @@ -142,13 +151,14 @@ 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) {
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<String> testsIds = client.getDojoTestIds(apiKeyValue, engagementId.getPropertyValue());
Expand All @@ -160,7 +170,8 @@ public void upload(final Project project, final InputStream payload) {
engagementId.getPropertyValue(),
payload,
verifyFindings,
testTitle);
testTitle,
groupBy);
} else {
client.reimportDependencyTrackFindings(
apiKeyValue,
Expand All @@ -169,15 +180,17 @@ public void upload(final Project project, final InputStream payload) {
testId,
isDoNotReactivateConfigured(project),
verifyFindings,
testTitle);
testTitle,
groupBy);
}
} else {
client.uploadDependencyTrackFindings(
apiKeyValue,
engagementId.getPropertyValue(),
payload,
verifyFindings,
testTitle);
testTitle,
groupBy);
}
} catch (Exception e) {
LOGGER.error("An error occurred attempting to upload findings to DefectDojo", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,28 @@ void testIntegrationDisabledCases() {
Assertions.assertFalse(extension.isProjectConfigured(project));
}

}
@Test
void testGetGroupByReturnsNullWhenNotConfigured() {
Project project = qm.createProject("ACME Example", null, "1.0", null, null, null, null, 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, null, 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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,190 @@ void testUploadWithReimportAndNoExistingTest(WireMockRuntimeInfo wmRuntimeInfo)
""", true, false))));
}

@Test
void testUploadWithGroupBy(WireMockRuntimeInfo wmRuntimeInfo) {
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(),
"apiKeySecretName",
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(
HttpClient.newHttpClient(),
new TestSecretManager(Map.of("apiKeySecretName", "dojoApiKey")))
.run();

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(WireMockRuntimeInfo wmRuntimeInfo) {
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(),
"apiKeySecretName",
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(
HttpClient.newHttpClient(),
new TestSecretManager(Map.of("apiKeySecretName", "dojoApiKey")))
.run();

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.
* <p>
Expand Down
Loading