Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport: Fix component de-duplication potentially causing duplicate dependency graph entries #4461

Merged
merged 1 commit into from
Dec 12, 2024
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 @@ -657,9 +657,19 @@ private String resolveDirectDependenciesJson(
}

final var jsonDependencies = new JSONArray();
final var directDependencyIdentitiesSeen = new HashSet<ComponentIdentity>();
for (final String directDependencyBomRef : directDependencyBomRefs) {
final ComponentIdentity directDependencyIdentity = identitiesByBomRef.get(directDependencyBomRef);
if (directDependencyIdentity != null) {
if (!directDependencyIdentitiesSeen.add(directDependencyIdentity)) {
// It's possible that multiple direct dependencies of a project or component
// fall victim to de-duplication. In that case, we can ironically end up with
// duplicate component identities (i.e. duplicate BOM refs).
LOGGER.debug("Omitting duplicate direct dependency %s for BOM ref %s"
.formatted(directDependencyBomRef, dependencyBomRef));
continue;
}

jsonDependencies.put(directDependencyIdentity.toJSON());
} else {
LOGGER.warn("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,21 @@
import org.dependencytrack.parser.spdx.json.SpdxLicenseDetailParser;
import org.dependencytrack.search.document.ComponentDocument;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import javax.jdo.JDOObjectNotFoundException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -1440,23 +1447,74 @@ public void informIssue3981Test() {
}

@Test
public void informIssue3936Test() throws Exception{
public void informIssue3936Test() throws Exception {
final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false);
List<String> boms = new ArrayList<>(Arrays.asList("/unit/bom-issue3936-authors.json", "/unit/bom-issue3936-author.json", "/unit/bom-issue3936-both.json"));
for(String bom : boms){
final var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()),
resourceToByteArray(bom));
new BomUploadProcessingTask().inform(bomUploadEvent);
awaitBomProcessedNotification(bomUploadEvent);

assertThat(qm.getAllComponents(project)).isNotEmpty();
Component component = qm.getAllComponents().getFirst();
assertThat(component.getAuthor()).isEqualTo("Joane Doe et al.");
assertThat(component.getAuthors().get(0).getName()).isEqualTo("Joane Doe et al.");
assertThat(component.getAuthors().size()).isEqualTo(1);
for (String bom : boms) {
final var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()),
resourceToByteArray(bom));
new BomUploadProcessingTask().inform(bomUploadEvent);
awaitBomProcessedNotification(bomUploadEvent);

assertThat(qm.getAllComponents(project)).isNotEmpty();
Component component = qm.getAllComponents().getFirst();
assertThat(component.getAuthor()).isEqualTo("Joane Doe et al.");
assertThat(component.getAuthors().get(0).getName()).isEqualTo("Joane Doe et al.");
assertThat(component.getAuthors().size()).isEqualTo(1);
}
}

@Test
public void informIssue4455Test() throws Exception {
final var project = new Project();
project.setName("acme-app");
project.setVersion("1.2.3");
qm.persist(project);

var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()),
resourceToByteArray("/unit/bom-issue4455.json"));
new BomUploadProcessingTask().inform(bomUploadEvent);
awaitBomProcessedNotification(bomUploadEvent);

qm.getPersistenceManager().refresh(project);
assertThat(project.getDirectDependencies()).satisfies(directDependenciesJson -> {
final JsonReader jsonReader = Json.createReader(
new StringReader(directDependenciesJson));
final JsonArray directDependenciesArray = jsonReader.readArray();

final var uuidsSeen = new HashSet<String>();
for (int i = 0; i < directDependenciesArray.size(); i++) {
final JsonObject directDependencyObject = directDependenciesArray.getJsonObject(i);
final String directDependencyUuid = directDependencyObject.getString("uuid");
if (!uuidsSeen.add(directDependencyUuid)) {
Assert.fail("Duplicate UUID %s in project's directDependencies: %s".formatted(
directDependencyUuid, directDependenciesJson));
}
}
});

final List<Component> components = qm.getAllComponents(project);
assertThat(components).allSatisfy(component -> {
if (component.getDirectDependencies() == null) {
return;
}

final JsonReader jsonReader = Json.createReader(
new StringReader(component.getDirectDependencies()));
final JsonArray directDependenciesArray = jsonReader.readArray();

final var uuidsSeen = new HashSet<String>();
for (int i = 0; i < directDependenciesArray.size(); i++) {
final JsonObject directDependencyObject = directDependenciesArray.getJsonObject(i);
final String directDependencyUuid = directDependencyObject.getString("uuid");
if (!uuidsSeen.add(directDependencyUuid)) {
Assert.fail("Duplicate UUID %s in component's directDependencies: %s".formatted(
directDependencyUuid, component.getDirectDependencies()));
}
}
});
}

private void awaitBomProcessedNotification(final BomUploadEvent bomUploadEvent) {
try {
await("BOM Processed Notification")
Expand Down
Loading
Loading