Skip to content

Commit

Permalink
Merge pull request #654 from DependencyTrack/issue-947-vulnerability-…
Browse files Browse the repository at this point in the history
…tags

Issue 947 : Add support for manual vulnerability tags
  • Loading branch information
nscuro authored Apr 26, 2024
2 parents 59f3d70 + 75d9e81 commit 4d1579c
Show file tree
Hide file tree
Showing 11 changed files with 468 additions and 85 deletions.
13 changes: 13 additions & 0 deletions src/main/java/org/dependencytrack/model/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public class Tag implements Serializable {
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
private List<Project> projects;

@Persistent
@JsonIgnore
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "vulnId ASC"))
private List<Vulnerability> vulnerabilities;

public long getId() {
return id;
}
Expand All @@ -92,6 +97,14 @@ public void setProjects(List<Project> projects) {
this.projects = projects;
}

public List<Vulnerability> getVulnerabilities() {
return vulnerabilities;
}

public void setVulnerabilities(List<Vulnerability> vulnerabilities) {
this.vulnerabilities = vulnerabilities;
}

@Override
public boolean equals(Object object) {
if (object instanceof Tag) {
Expand Down
19 changes: 18 additions & 1 deletion src/main/java/org/dependencytrack/model/Vulnerability.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@

import javax.jdo.annotations.Column;
import javax.jdo.annotations.Convert;
import javax.jdo.annotations.Element;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.FetchGroup;
import javax.jdo.annotations.FetchGroups;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Index;
import javax.jdo.annotations.Join;
import javax.jdo.annotations.Order;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
Expand Down Expand Up @@ -80,7 +82,8 @@
@Persistent(name = "owaspRRLikelihoodScore"),
@Persistent(name = "owaspRRTechnicalImpactScore"),
@Persistent(name = "owaspRRBusinessImpactScore"),
@Persistent(name = "severity")
@Persistent(name = "severity"),
@Persistent(name = "tags")
}),
@FetchGroup(name = "COMPONENTS", members = {
@Persistent(name = "components")
Expand Down Expand Up @@ -318,6 +321,12 @@ public static boolean isKnownSource(String source) {
@NotNull
private UUID uuid;

@Persistent(table = "VULNERABILITIES_TAGS", defaultFetchGroup = "true", mappedBy = "vulnerabilities")
@Join(column = "VULNERABILITY_ID")
@Element(column = "TAG_ID")
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
private List<Tag> tags;

private transient Epss epss;

private transient int affectedProjectCount;
Expand Down Expand Up @@ -733,4 +742,12 @@ public BigDecimal getEpssScore() {
public BigDecimal getEpssPercentile() {
return epss != null ? epss.getPercentile() : null;
}

public List<Tag> getTags() {
return tags;
}

public void setTags(List<Tag> tags) {
this.tags = tags;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -376,86 +376,6 @@ public PaginatedResult getProjects(final Tag tag) {
return getProjects(tag, false, false, false);
}

/**
* Returns a list of Tag objects what have been resolved. It resolved
* tags by querying the database to retrieve the tag. If the tag does
* not exist, the tag will be created and returned with other resolved
* tags.
*
* @param tags a List of Tags to resolve
* @return List of resolved Tags
*/
private synchronized List<Tag> resolveTags(final List<Tag> tags) {
if (tags == null) {
return new ArrayList<>();
}
final List<Tag> resolvedTags = new ArrayList<>();
final List<String> unresolvedTags = new ArrayList<>();
for (final Tag tag : tags) {
final String trimmedTag = StringUtils.trimToNull(tag.getName());
if (trimmedTag != null) {
final Tag resolvedTag = getTagByName(trimmedTag);
if (resolvedTag != null) {
resolvedTags.add(resolvedTag);
} else {
unresolvedTags.add(trimmedTag);
}
}
}
resolvedTags.addAll(createTags(unresolvedTags));
return resolvedTags;
}

/**
* Returns a list of Tag objects by name.
*
* @param name the name of the Tag
* @return a Tag object
*/
@Override
public Tag getTagByName(final String name) {
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
final Query<Tag> query = pm.newQuery(Tag.class, "name == :name");
query.setRange(0, 1);
return singleResult(query.execute(loweredTrimmedTag));
}

/**
* Creates a new Tag object with the specified name.
*
* @param name the name of the Tag to create
* @return the created Tag object
*/
@Override
public Tag createTag(final String name) {
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
final Tag resolvedTag = getTagByName(loweredTrimmedTag);
if (resolvedTag != null) {
return resolvedTag;
}
final Tag tag = new Tag();
tag.setName(loweredTrimmedTag);
return persist(tag);
}

/**
* Creates one or more Tag objects from the specified name(s).
*
* @param names the name(s) of the Tag(s) to create
* @return the created Tag object(s)
*/
private List<Tag> createTags(final List<String> names) {
final List<Tag> newTags = new ArrayList<>();
for (final String name : names) {
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
if (getTagByName(loweredTrimmedTag) == null) {
final Tag tag = new Tag();
tag.setName(loweredTrimmedTag);
newTags.add(tag);
}
}
return new ArrayList<>(persist(newTags));
}

/**
* Creates a new Project.
Expand Down
22 changes: 18 additions & 4 deletions src/main/java/org/dependencytrack/persistence/QueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,7 @@ public class QueryManager extends AlpineQueryManager {
private VulnerableSoftwareQueryManager vulnerableSoftwareQueryManager;
private WorkflowStateQueryManager workflowStateQueryManager;
private IntegrityMetaQueryManager integrityMetaQueryManager;

private IntegrityAnalysisQueryManager integrityAnalysisQueryManager;

private TagQueryManager tagQueryManager;
private EpssQueryManager epssQueryManager;

Expand Down Expand Up @@ -549,11 +547,11 @@ public boolean doesProjectExist(final String name, final String version) {
}

public Tag getTagByName(final String name) {
return getProjectQueryManager().getTagByName(name);
return getTagQueryManager().getTagByName(name);
}

public Tag createTag(final String name) {
return getProjectQueryManager().createTag(name);
return getTagQueryManager().createTag(name);
}

public Project createProject(String name, String description, String version, List<Tag> tags, Project parent, PackageURL purl, boolean active, boolean commitIndex) {
Expand Down Expand Up @@ -1936,4 +1934,20 @@ public Epss getEpssByCveId(String cveId) {
public Map<String, Epss> getEpssForCveIds(List<String> cveIds) {
return getEpssQueryManager().getEpssForCveIds(cveIds);
}

public List<Tag> resolveTags(final List<Tag> tags) {
return getTagQueryManager().resolveTags(tags);
}

public List<Tag> resolveTagsByName(final List<String> tagNames) {
return getTagQueryManager().resolveTagsByName(tagNames);
}

public void bind(Vulnerability vulnerability, List<Tag> tags) {
getVulnerabilityQueryManager().bind(vulnerability, tags);
}

public PaginatedResult getVulnerabilities(final Tag tag) {
return getVulnerabilityQueryManager().getVulnerabilities(tag);
}
}
91 changes: 91 additions & 0 deletions src/main/java/org/dependencytrack/persistence/TagQueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
import alpine.common.logging.Logger;
import alpine.persistence.PaginatedResult;
import alpine.resources.AlpineRequest;
import org.apache.commons.lang3.StringUtils;
import org.dependencytrack.model.Policy;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Tag;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
Expand Down Expand Up @@ -76,4 +79,92 @@ public PaginatedResult getTags(String policyUuid) {
return (new PaginatedResult()).objects(tagsToShow).total(tagsToShow.size());
}

/**
* Returns a list of Tag objects what have been resolved. It resolved
* tags by querying the database to retrieve the tag. If the tag does
* not exist, the tag will be created and returned with other resolved
* tags.
*
* @param tags a List of Tags to resolve
* @return List of resolved Tags
*/
public synchronized List<Tag> resolveTags(final List<Tag> tags) {
if (tags == null) {
return new ArrayList<>();
}
List<String> tagNames = tags.stream().map(tag -> tag.getName()).toList();
return resolveTagsByName(tagNames);
}

public synchronized List<Tag> resolveTagsByName(final List<String> tags) {
if (tags == null) {
return new ArrayList<>();
}
final List<Tag> resolvedTags = new ArrayList<>();
final List<String> unresolvedTags = new ArrayList<>();
for (final String tag : tags) {
final String trimmedTag = StringUtils.trimToNull(tag);
if (trimmedTag != null) {
final Tag resolvedTag = getTagByName(trimmedTag);
if (resolvedTag != null) {
resolvedTags.add(resolvedTag);
} else {
unresolvedTags.add(trimmedTag);
}
}
}
resolvedTags.addAll(createTags(unresolvedTags));
return resolvedTags;
}

/**
* Returns a list of Tag objects by name.
*
* @param name the name of the Tag
* @return a Tag object
*/
@Override
public Tag getTagByName(final String name) {
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
final Query<Tag> query = pm.newQuery(Tag.class, "name == :name");
query.setRange(0, 1);
return singleResult(query.execute(loweredTrimmedTag));
}

/**
* Creates a new Tag object with the specified name.
*
* @param name the name of the Tag to create
* @return the created Tag object
*/
@Override
public Tag createTag(final String name) {
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
final Tag resolvedTag = getTagByName(loweredTrimmedTag);
if (resolvedTag != null) {
return resolvedTag;
}
final Tag tag = new Tag();
tag.setName(loweredTrimmedTag);
return persist(tag);
}

/**
* Creates one or more Tag objects from the specified name(s).
*
* @param names the name(s) of the Tag(s) to create
* @return the created Tag object(s)
*/
private List<Tag> createTags(final List<String> names) {
final List<Tag> newTags = new ArrayList<>();
for (final String name : names) {
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
if (getTagByName(loweredTrimmedTag) == null) {
final Tag tag = new Tag();
tag.setName(loweredTrimmedTag);
newTags.add(tag);
}
}
return new ArrayList<>(persist(newTags));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.dependencytrack.model.Epss;
import org.dependencytrack.model.FindingAttribution;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Tag;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.model.VulnerabilityAlias;
import org.dependencytrack.model.VulnerableSoftware;
Expand Down Expand Up @@ -887,4 +888,47 @@ public void deleteAffectedVersionAttributions(final Vulnerability vulnerability)
query.setParameters(vulnerability);
query.deletePersistentAll();
}

/**
* Binds the two objects together in a corresponding join table.
*
* @param vulnerability a Vulnerability object
* @param tags a List of Tag objects
*/
@SuppressWarnings("unchecked")
public void bind(Vulnerability vulnerability, List<Tag> tags) {
runInTransaction(() -> {
final Query<Tag> query = pm.newQuery(Tag.class, "vulnerabilities.contains(:vulnerability)");
final List<Tag> currentVulnerabilityTags = (List<Tag>) query.execute(vulnerability);
for (final Tag tag : currentVulnerabilityTags) {
if (!tags.contains(tag)) {
tag.getVulnerabilities().remove(vulnerability);
}
}
vulnerability.setTags(tags);
for (final Tag tag : tags) {
final List<Vulnerability> vulnerabilities = tag.getVulnerabilities();
if (!vulnerabilities.contains(vulnerability)) {
vulnerabilities.add(vulnerability);
}
}
});
}

/**
* Returns a paginated result of vulnerabilities by tag.
*
* @param tag the tag associated with the Vulnerability
* @return a List of vulnerabilities that contain the tag
*/
public PaginatedResult getVulnerabilities(final Tag tag) {
final Query<Vulnerability> query = pm.newQuery(Vulnerability.class);
if (orderBy == null) {
query.setOrdering("vulnId asc, id asc");
}
query.setFilter("(tags.contains(:tag))");
Map<String, Object> params = new HashMap<>();
params.put("tag", tag);
return execute(query, params);
}
}
Loading

0 comments on commit 4d1579c

Please sign in to comment.