Skip to content

Commit

Permalink
include aliases of vulnerabilities when collecting the affected projects
Browse files Browse the repository at this point in the history
fixes #2794

Signed-off-by: Ronny Perinke <[email protected]>
  • Loading branch information
sephiroth-j committed Jan 5, 2025
1 parent 22d43b1 commit 2485cd3
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import alpine.event.framework.Event;
import alpine.persistence.PaginatedResult;
import alpine.resources.AlpineRequest;
import com.nimbusds.jose.util.Pair;
import org.apache.commons.lang3.StringUtils;
import org.dependencytrack.event.IndexEvent;
import org.dependencytrack.model.AffectedVersionAttribution;
Expand All @@ -46,11 +47,14 @@
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

final class VulnerabilityQueryManager extends QueryManager implements IQueryManager {
private static final Logger LOGGER = Logger.getLogger(VulnDbSyncTask.class);
Expand Down Expand Up @@ -496,48 +500,52 @@ private String generateExcludeSuppressed(Project project, Component component) {
}

/**
* Generates partial JDOQL statement excluding suppressed vulnerabilities for this project.
* @param project the project to query on
* @return a partial where clause
*/
private String generateExcludeSuppressed(Project project) {
return generateExcludeSuppressed(project, null);
}

/**
* Returns a List of Projects affected by a specific vulnerability.
* Returns a list of projects affected by a specific vulnerability including those of aliases.
* @param vulnerability the vulnerability to query on
* @return a List of AffectedProjects
*/
public List<AffectedProject> getAffectedProjects(Vulnerability vulnerability) {
final Map<UUID, AffectedProject> affectedProjectMap = new HashMap<>();

final List<Project> projects = new ArrayList<>();
for (final Component component: vulnerability.getComponents()) {
if (! super.hasAccess(super.principal, component.getProject())) {
continue;
}
boolean affected = true;
final Analysis projectAnalysis = getAnalysis(component, vulnerability);
if (projectAnalysis != null && projectAnalysis.isSuppressed()) {
affected = false;
}
if (affected) {
Project project = component.getProject();
AffectedProject affectedProject = affectedProjectMap.get(project.getUuid());
if(affectedProject == null) {
affectedProject = new AffectedProject(
project.getUuid(),
project.getDirectDependencies() != null,
project.getName(),
project.getVersion(),
project.isActive(),
null);
affectedProjectMap.put(project.getUuid(), affectedProject);
final Map<UUID, AffectedProject> affectedProjectMap = Stream.of(vulnerability)
// expand this stream containing only one vulnerability to a stream with the vulnerability and all of its alias vulnerabilities
.<Vulnerability>mapMulti((vuln, consumer) -> {
if (vuln.getAliases() == null || vuln.getAliases().isEmpty()) {
// only the original vulnerability
consumer.accept(vuln);
} else {
// all vulnerabilities including the aliases
vuln.getAliases().stream()
// getAllBySource includes the original vulnerability
.flatMap(alias -> alias.getAllBySource().entrySet().stream())
.map(entry -> getVulnerabilityByVulnId(entry.getKey(), entry.getValue(), false))
.forEach(consumer);
}
affectedProject.getAffectedComponentUuids().add(component.getUuid());
}
}
})
// for each vulnerability, iterate over each affected component
.flatMap(vuln -> vuln.getComponents().stream().map(component -> Pair.of(component, vuln)))
// process only components that the principal has access to
.filter(pair -> super.hasAccess(super.principal, pair.getLeft().getProject()))
// do not process suppressed analysis
.filter(pair -> {
var projectAnalysis = getAnalysis(pair.getLeft(), pair.getRight());
return projectAnalysis == null || !projectAnalysis.isSuppressed();
})
// now collect all affected projects
.map(pair -> {
final var project = pair.getLeft().getProject();
return new AffectedProject(
project.getUuid(),
project.getDirectDependencies() != null,
project.getName(),
project.getVersion(),
project.isActive(),
new HashSet<>(Set.of(pair.getLeft().getUuid())));
})
// group the affected projects and combine their affected components
.collect(Collectors.toMap(AffectedProject::getUuid, Function.identity(), (left, right) -> {
left.getAffectedComponentUuids().addAll(right.getAffectedComponentUuids());
return left;
}));

return affectedProjectMap.values().stream().toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
*/
package org.dependencytrack.resources.v1.vo;

import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
Expand All @@ -40,15 +40,15 @@ public class AffectedProject {

private final boolean active;

private final List<UUID> affectedComponentUuids;
private final Set<UUID> affectedComponentUuids;

public AffectedProject(UUID uuid, boolean dependencyGraphAvailable, String name, String version, boolean active, List<UUID> affectedComponentUuids) {
public AffectedProject(UUID uuid, boolean dependencyGraphAvailable, String name, String version, boolean active, Set<UUID> affectedComponentUuids) {
this.uuid = uuid;
this.dependencyGraphAvailable = dependencyGraphAvailable;
this.name = name;
this.version = version;
this.active = active;
this.affectedComponentUuids = affectedComponentUuids == null ? new ArrayList<>() : affectedComponentUuids;
this.affectedComponentUuids = affectedComponentUuids == null ? new HashSet<>() : affectedComponentUuids;
}

public UUID getUuid() {
Expand All @@ -70,7 +70,7 @@ public boolean getActive() {
return active;
}

public List<UUID> getAffectedComponentUuids() {
public Set<UUID> getAffectedComponentUuids() {
return affectedComponentUuids;
}
}
Loading

0 comments on commit 2485cd3

Please sign in to comment.