From bea32877c2e825e363ad6ca54ff84e56faf1a63d Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 17 Apr 2024 14:51:16 +0100 Subject: [PATCH 1/9] WIP --- .../dependencytrack/model/Vulnerability.java | 15 ++ .../model/VulnerabilityTag.java | 103 ++++++++++++ .../persistence/QueryManager.java | 19 ++- .../VulnerabilityTagQueryManager.java | 158 ++++++++++++++++++ .../resources/v1/VulnerabilityResource.java | 5 + 5 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/dependencytrack/model/VulnerabilityTag.java create mode 100644 src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java diff --git a/src/main/java/org/dependencytrack/model/Vulnerability.java b/src/main/java/org/dependencytrack/model/Vulnerability.java index 8935b3889..105c559ba 100644 --- a/src/main/java/org/dependencytrack/model/Vulnerability.java +++ b/src/main/java/org/dependencytrack/model/Vulnerability.java @@ -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; @@ -318,6 +320,11 @@ public static boolean isKnownSource(String source) { @NotNull private UUID uuid; + @Persistent(table = "VULNERABILITY_TAGS", defaultFetchGroup = "true") + @Join(column = "VULNERABILITY_ID") + @Element(column = "VULNERABILITY_TAG_ID") + private List vulnerabilityTags; + private transient Epss epss; private transient int affectedProjectCount; @@ -733,4 +740,12 @@ public BigDecimal getEpssScore() { public BigDecimal getEpssPercentile() { return epss != null ? epss.getPercentile() : null; } + + public List getVulnerabilityTags() { + return vulnerabilityTags; + } + + public void setVulnerabilityTags(List vulnerabilityTags) { + this.vulnerabilityTags = vulnerabilityTags; + } } diff --git a/src/main/java/org/dependencytrack/model/VulnerabilityTag.java b/src/main/java/org/dependencytrack/model/VulnerabilityTag.java new file mode 100644 index 000000000..6a56f984e --- /dev/null +++ b/src/main/java/org/dependencytrack/model/VulnerabilityTag.java @@ -0,0 +1,103 @@ +/* + * This file is part of Dependency-Track. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.dependencytrack.model; + +import alpine.common.validation.RegexSequence; +import alpine.server.json.TrimmedStringDeserializer; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import javax.jdo.annotations.Column; +import javax.jdo.annotations.Extension; +import javax.jdo.annotations.IdGeneratorStrategy; +import javax.jdo.annotations.Order; +import javax.jdo.annotations.PersistenceCapable; +import javax.jdo.annotations.Persistent; +import javax.jdo.annotations.PrimaryKey; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * Model for assigning tags to vulnerabilities. + */ +@PersistenceCapable +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VulnerabilityTag implements Serializable { + + private static final long serialVersionUID = -7798359808664731988L; + + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.NATIVE) + @JsonIgnore + private long id; + + @Persistent + @Column(name = "NAME", allowsNull = "false") + @NotBlank + @Size(min = 1, max = 255) + @JsonDeserialize(using = TrimmedStringDeserializer.class) + @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") + private String name; + + @Persistent + @JsonIgnore + private List vulnerabilities; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getVulnerabilities() { + return vulnerabilities; + } + + public void setVulnerabilities(List vulnerabilities) { + this.vulnerabilities = vulnerabilities; + } + + @Override + public boolean equals(Object object) { + if (object instanceof VulnerabilityTag) { + return this.id == ((VulnerabilityTag) object).id; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index db999be3d..d9aba0f7d 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -84,6 +84,7 @@ import org.dependencytrack.model.VulnerabilityMetrics; import org.dependencytrack.model.VulnerabilityPolicyBundle; import org.dependencytrack.model.VulnerabilityScan; +import org.dependencytrack.model.VulnerabilityTag; import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.model.WorkflowState; import org.dependencytrack.model.WorkflowStatus; @@ -148,11 +149,10 @@ public class QueryManager extends AlpineQueryManager { private VulnerableSoftwareQueryManager vulnerableSoftwareQueryManager; private WorkflowStateQueryManager workflowStateQueryManager; private IntegrityMetaQueryManager integrityMetaQueryManager; - private IntegrityAnalysisQueryManager integrityAnalysisQueryManager; - private TagQueryManager tagQueryManager; private EpssQueryManager epssQueryManager; + private VulnerabilityTagQueryManager vulnerabilityTagQueryManager; /** * Default constructor. @@ -422,6 +422,13 @@ private IntegrityAnalysisQueryManager getIntegrityAnalysisQueryManager() { return integrityAnalysisQueryManager; } + private VulnerabilityTagQueryManager getVulnerabilityTagQueryManager() { + if (vulnerabilityTagQueryManager == null) { + vulnerabilityTagQueryManager = (request == null) ? new VulnerabilityTagQueryManager(getPersistenceManager()) : new VulnerabilityTagQueryManager(getPersistenceManager(), request); + } + return vulnerabilityTagQueryManager; + } + /** * Get the IDs of the {@link Team}s a given {@link Principal} is a member of. * @@ -1936,4 +1943,12 @@ public Epss getEpssByCveId(String cveId) { public Map getEpssForCveIds(List cveIds) { return getEpssQueryManager().getEpssForCveIds(cveIds); } + + public List resolveVulnerabilityTags(final List tags) { + return getVulnerabilityTagQueryManager().resolveVulnerabilityTags(tags); + } + + public void bindTagsToVulnerability(final Vulnerability vulnerability, final List tags) { + getVulnerabilityTagQueryManager().bindTagsToVulnerability(vulnerability, tags); + } } diff --git a/src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java new file mode 100644 index 000000000..7f7206af3 --- /dev/null +++ b/src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java @@ -0,0 +1,158 @@ +/* + * This file is part of Dependency-Track. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.dependencytrack.persistence; + +import alpine.common.logging.Logger; +import alpine.persistence.PaginatedResult; +import alpine.resources.AlpineRequest; +import org.apache.commons.lang3.StringUtils; +import org.dependencytrack.model.Project; +import org.dependencytrack.model.Tag; +import org.dependencytrack.model.Vulnerability; +import org.dependencytrack.model.VulnerabilityTag; + +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; + +public class VulnerabilityTagQueryManager extends QueryManager implements IQueryManager { + + private static final Comparator TAG_COMPARATOR = Comparator.comparingInt( + (VulnerabilityTag tag) -> tag.getVulnerabilities().size()).reversed(); + + private static final Logger LOGGER = Logger.getLogger(VulnerabilityTagQueryManager.class); + + /** + * Constructs a new QueryManager. + * @param pm a PersistenceManager object + */ + VulnerabilityTagQueryManager(final PersistenceManager pm) { + super(pm); + } + + /** + * Constructs a new QueryManager. + * @param pm a PersistenceManager object + * @param request an AlpineRequest object + */ + VulnerabilityTagQueryManager(final PersistenceManager pm, final AlpineRequest request) { + super(pm, request); + } + + public PaginatedResult getAllVulnerabilityTags() { + + LOGGER.debug("Retrieving all vulnerability tags"); + final Stream tags; + tags = pm.newQuery(VulnerabilityTag.class).executeList().stream(); + List tagsToShow = tags.sorted(TAG_COMPARATOR).toList(); + + return (new PaginatedResult()).objects(tagsToShow).total(tagsToShow.size()); + } + + /** + * Returns a list of VulnerabilityTag 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 VulnerabilityTag to resolve + * @return List of resolved VulnerabilityTag + */ + public synchronized List resolveVulnerabilityTags(final List tags) { + if (tags == null) { + return new ArrayList<>(); + } + final List resolvedTags = new ArrayList<>(); + final List unresolvedTags = new ArrayList<>(); + for (final VulnerabilityTag tag : tags) { + final String trimmedTag = StringUtils.trimToNull(tag.getName()); + if (trimmedTag != null) { + final VulnerabilityTag resolvedTag = getVulnerabilityTagByName(trimmedTag); + if (resolvedTag != null) { + resolvedTags.add(resolvedTag); + } else { + unresolvedTags.add(trimmedTag); + } + } + } + resolvedTags.addAll(createVulnerabilityTags(unresolvedTags)); + return resolvedTags; + } + + /** + * Returns a list of VulnerabilityTag objects by name. + * + * @param name the name of the VulnerabilityTag + * @return a VulnerabilityTag object + */ + public VulnerabilityTag getVulnerabilityTagByName(final String name) { + final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name)); + final Query query = pm.newQuery(VulnerabilityTag.class, "name == :name"); + query.setRange(0, 1); + return singleResult(query.execute(loweredTrimmedTag)); + } + + /** + * Creates one or more VulnerabilityTag objects from the specified name(s). + * + * @param names the name(s) of the VulnerabilityTag(s) to create + * @return the created VulnerabilityTag object(s) + */ + private List createVulnerabilityTags(final List names) { + final List newTags = new ArrayList<>(); + for (final String name : names) { + final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name)); + if (getVulnerabilityTagByName(loweredTrimmedTag) == null) { + final VulnerabilityTag tag = new VulnerabilityTag(); + tag.setName(loweredTrimmedTag); + newTags.add(tag); + } + } + return new ArrayList<>(persist(newTags)); + } + + /** + * Binds the two objects together in a corresponding join table. + * + * @param vulnerability a Vulnerability object + * @param tags a List of VulnerabilityTag objects + */ + @SuppressWarnings("unchecked") + public void bindTagsToVulnerability(Vulnerability vulnerability, List tags) { + final Query query = pm.newQuery(VulnerabilityTag.class, "vulnerabilities.contains(:vulnerability)"); + final List currentVulnerabilityTags = (List) query.execute(vulnerability); + pm.currentTransaction().begin(); + for (final VulnerabilityTag tag : currentVulnerabilityTags) { + if (!tags.contains(tag)) { + tag.getVulnerabilities().remove(vulnerability); + } + } + vulnerability.setVulnerabilityTags(tags); + for (final VulnerabilityTag tag : tags) { + final List vulnerabilities = tag.getVulnerabilities(); + if (!vulnerabilities.contains(vulnerability)) { + vulnerabilities.add(vulnerability); + } + } + pm.currentTransaction().commit(); + } +} diff --git a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java index 4352fed14..61927d3ca 100644 --- a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java @@ -35,6 +35,7 @@ import org.dependencytrack.model.Cwe; import org.dependencytrack.model.Project; import org.dependencytrack.model.Vulnerability; +import org.dependencytrack.model.VulnerabilityTag; import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.persistence.QueryManager; import org.dependencytrack.resources.v1.vo.AffectedComponent; @@ -371,6 +372,10 @@ public Response updateVulnerability(Vulnerability jsonVuln) { } } } + + final List resolvedTags = qm.resolveVulnerabilityTags(jsonVuln.getVulnerabilityTags()); + qm.bindTagsToVulnerability(vulnerability, resolvedTags); + recalculateScoresFromVector(jsonVuln); vulnerability = qm.updateVulnerability(jsonVuln, true); qm.persist(vsList); From b8dedc164841765b41b8f3ec117fdd29398c8e78 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 17 Apr 2024 15:52:00 +0100 Subject: [PATCH 2/9] move tag query methods --- .../java/org/dependencytrack/model/Tag.java | 12 ++ .../dependencytrack/model/Vulnerability.java | 12 +- .../model/VulnerabilityTag.java | 103 ------------ .../persistence/ProjectQueryManager.java | 80 --------- .../persistence/QueryManager.java | 21 +-- .../persistence/TagQueryManager.java | 83 +++++++++ .../VulnerabilityQueryManager.java | 27 +++ .../VulnerabilityTagQueryManager.java | 158 ------------------ .../resources/v1/VulnerabilityResource.java | 8 +- .../resources/migration/changelog-v5.5.0.xml | 26 +++ 10 files changed, 164 insertions(+), 366 deletions(-) delete mode 100644 src/main/java/org/dependencytrack/model/VulnerabilityTag.java delete mode 100644 src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java diff --git a/src/main/java/org/dependencytrack/model/Tag.java b/src/main/java/org/dependencytrack/model/Tag.java index 627de13b4..0f03762c8 100644 --- a/src/main/java/org/dependencytrack/model/Tag.java +++ b/src/main/java/org/dependencytrack/model/Tag.java @@ -68,6 +68,10 @@ public class Tag implements Serializable { @Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC")) private List projects; + @Persistent + @JsonIgnore + private List vulnerabilities; + public long getId() { return id; } @@ -92,6 +96,14 @@ public void setProjects(List projects) { this.projects = projects; } + public List getVulnerabilities() { + return vulnerabilities; + } + + public void setVulnerabilities(List vulnerabilities) { + this.vulnerabilities = vulnerabilities; + } + @Override public boolean equals(Object object) { if (object instanceof Tag) { diff --git a/src/main/java/org/dependencytrack/model/Vulnerability.java b/src/main/java/org/dependencytrack/model/Vulnerability.java index 105c559ba..31c62fbaf 100644 --- a/src/main/java/org/dependencytrack/model/Vulnerability.java +++ b/src/main/java/org/dependencytrack/model/Vulnerability.java @@ -322,8 +322,8 @@ public static boolean isKnownSource(String source) { @Persistent(table = "VULNERABILITY_TAGS", defaultFetchGroup = "true") @Join(column = "VULNERABILITY_ID") - @Element(column = "VULNERABILITY_TAG_ID") - private List vulnerabilityTags; + @Element(column = "TAG_ID") + private List tags; private transient Epss epss; @@ -741,11 +741,11 @@ public BigDecimal getEpssPercentile() { return epss != null ? epss.getPercentile() : null; } - public List getVulnerabilityTags() { - return vulnerabilityTags; + public List getTags() { + return tags; } - public void setVulnerabilityTags(List vulnerabilityTags) { - this.vulnerabilityTags = vulnerabilityTags; + public void setTags(List tags) { + this.tags = tags; } } diff --git a/src/main/java/org/dependencytrack/model/VulnerabilityTag.java b/src/main/java/org/dependencytrack/model/VulnerabilityTag.java deleted file mode 100644 index 6a56f984e..000000000 --- a/src/main/java/org/dependencytrack/model/VulnerabilityTag.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * This file is part of Dependency-Track. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) OWASP Foundation. All Rights Reserved. - */ -package org.dependencytrack.model; - -import alpine.common.validation.RegexSequence; -import alpine.server.json.TrimmedStringDeserializer; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import javax.jdo.annotations.Column; -import javax.jdo.annotations.Extension; -import javax.jdo.annotations.IdGeneratorStrategy; -import javax.jdo.annotations.Order; -import javax.jdo.annotations.PersistenceCapable; -import javax.jdo.annotations.Persistent; -import javax.jdo.annotations.PrimaryKey; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; -import java.io.Serializable; -import java.util.List; -import java.util.Objects; - -/** - * Model for assigning tags to vulnerabilities. - */ -@PersistenceCapable -@JsonInclude(JsonInclude.Include.NON_NULL) -public class VulnerabilityTag implements Serializable { - - private static final long serialVersionUID = -7798359808664731988L; - - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.NATIVE) - @JsonIgnore - private long id; - - @Persistent - @Column(name = "NAME", allowsNull = "false") - @NotBlank - @Size(min = 1, max = 255) - @JsonDeserialize(using = TrimmedStringDeserializer.class) - @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") - private String name; - - @Persistent - @JsonIgnore - private List vulnerabilities; - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getVulnerabilities() { - return vulnerabilities; - } - - public void setVulnerabilities(List vulnerabilities) { - this.vulnerabilities = vulnerabilities; - } - - @Override - public boolean equals(Object object) { - if (object instanceof VulnerabilityTag) { - return this.id == ((VulnerabilityTag) object).id; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(id); - } -} diff --git a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java index bba3be8f5..7d49ae1c5 100644 --- a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java @@ -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 resolveTags(final List tags) { - if (tags == null) { - return new ArrayList<>(); - } - final List resolvedTags = new ArrayList<>(); - final List 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 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 createTags(final List names) { - final List 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. diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index d9aba0f7d..4098d1eea 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -84,7 +84,6 @@ import org.dependencytrack.model.VulnerabilityMetrics; import org.dependencytrack.model.VulnerabilityPolicyBundle; import org.dependencytrack.model.VulnerabilityScan; -import org.dependencytrack.model.VulnerabilityTag; import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.model.WorkflowState; import org.dependencytrack.model.WorkflowStatus; @@ -152,7 +151,6 @@ public class QueryManager extends AlpineQueryManager { private IntegrityAnalysisQueryManager integrityAnalysisQueryManager; private TagQueryManager tagQueryManager; private EpssQueryManager epssQueryManager; - private VulnerabilityTagQueryManager vulnerabilityTagQueryManager; /** * Default constructor. @@ -422,13 +420,6 @@ private IntegrityAnalysisQueryManager getIntegrityAnalysisQueryManager() { return integrityAnalysisQueryManager; } - private VulnerabilityTagQueryManager getVulnerabilityTagQueryManager() { - if (vulnerabilityTagQueryManager == null) { - vulnerabilityTagQueryManager = (request == null) ? new VulnerabilityTagQueryManager(getPersistenceManager()) : new VulnerabilityTagQueryManager(getPersistenceManager(), request); - } - return vulnerabilityTagQueryManager; - } - /** * Get the IDs of the {@link Team}s a given {@link Principal} is a member of. * @@ -556,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 tags, Project parent, PackageURL purl, boolean active, boolean commitIndex) { @@ -1944,11 +1935,11 @@ public Map getEpssForCveIds(List cveIds) { return getEpssQueryManager().getEpssForCveIds(cveIds); } - public List resolveVulnerabilityTags(final List tags) { - return getVulnerabilityTagQueryManager().resolveVulnerabilityTags(tags); + public List resolveTags(final List tags) { + return getTagQueryManager().resolveTags(tags); } - public void bindTagsToVulnerability(final Vulnerability vulnerability, final List tags) { - getVulnerabilityTagQueryManager().bindTagsToVulnerability(vulnerability, tags); + public void bind(Vulnerability vulnerability, List tags) { + getVulnerabilityQueryManager().bind(vulnerability, tags); } } diff --git a/src/main/java/org/dependencytrack/persistence/TagQueryManager.java b/src/main/java/org/dependencytrack/persistence/TagQueryManager.java index 5234917a0..007f99953 100644 --- a/src/main/java/org/dependencytrack/persistence/TagQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/TagQueryManager.java @@ -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; @@ -76,4 +79,84 @@ 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 resolveTags(final List tags) { + if (tags == null) { + return new ArrayList<>(); + } + final List resolvedTags = new ArrayList<>(); + final List 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 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 createTags(final List names) { + final List 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)); + } } diff --git a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java index 290ff4b8b..05f168562 100644 --- a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java @@ -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; @@ -887,4 +888,30 @@ 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 tags) { + final Query query = pm.newQuery(Tag.class, "vulnerabilities.contains(:vulnerability)"); + final List currentVulnerabilityTags = (List) query.execute(vulnerability); + pm.currentTransaction().begin(); + for (final Tag tag : currentVulnerabilityTags) { + if (!tags.contains(tag)) { + tag.getVulnerabilities().remove(vulnerability); + } + } + vulnerability.setTags(tags); + for (final Tag tag : tags) { + final List vulnerabilities = tag.getVulnerabilities(); + if (!vulnerabilities.contains(vulnerability)) { + vulnerabilities.add(vulnerability); + } + } + pm.currentTransaction().commit(); + } } diff --git a/src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java deleted file mode 100644 index 7f7206af3..000000000 --- a/src/main/java/org/dependencytrack/persistence/VulnerabilityTagQueryManager.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * This file is part of Dependency-Track. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) OWASP Foundation. All Rights Reserved. - */ -package org.dependencytrack.persistence; - -import alpine.common.logging.Logger; -import alpine.persistence.PaginatedResult; -import alpine.resources.AlpineRequest; -import org.apache.commons.lang3.StringUtils; -import org.dependencytrack.model.Project; -import org.dependencytrack.model.Tag; -import org.dependencytrack.model.Vulnerability; -import org.dependencytrack.model.VulnerabilityTag; - -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; - -public class VulnerabilityTagQueryManager extends QueryManager implements IQueryManager { - - private static final Comparator TAG_COMPARATOR = Comparator.comparingInt( - (VulnerabilityTag tag) -> tag.getVulnerabilities().size()).reversed(); - - private static final Logger LOGGER = Logger.getLogger(VulnerabilityTagQueryManager.class); - - /** - * Constructs a new QueryManager. - * @param pm a PersistenceManager object - */ - VulnerabilityTagQueryManager(final PersistenceManager pm) { - super(pm); - } - - /** - * Constructs a new QueryManager. - * @param pm a PersistenceManager object - * @param request an AlpineRequest object - */ - VulnerabilityTagQueryManager(final PersistenceManager pm, final AlpineRequest request) { - super(pm, request); - } - - public PaginatedResult getAllVulnerabilityTags() { - - LOGGER.debug("Retrieving all vulnerability tags"); - final Stream tags; - tags = pm.newQuery(VulnerabilityTag.class).executeList().stream(); - List tagsToShow = tags.sorted(TAG_COMPARATOR).toList(); - - return (new PaginatedResult()).objects(tagsToShow).total(tagsToShow.size()); - } - - /** - * Returns a list of VulnerabilityTag 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 VulnerabilityTag to resolve - * @return List of resolved VulnerabilityTag - */ - public synchronized List resolveVulnerabilityTags(final List tags) { - if (tags == null) { - return new ArrayList<>(); - } - final List resolvedTags = new ArrayList<>(); - final List unresolvedTags = new ArrayList<>(); - for (final VulnerabilityTag tag : tags) { - final String trimmedTag = StringUtils.trimToNull(tag.getName()); - if (trimmedTag != null) { - final VulnerabilityTag resolvedTag = getVulnerabilityTagByName(trimmedTag); - if (resolvedTag != null) { - resolvedTags.add(resolvedTag); - } else { - unresolvedTags.add(trimmedTag); - } - } - } - resolvedTags.addAll(createVulnerabilityTags(unresolvedTags)); - return resolvedTags; - } - - /** - * Returns a list of VulnerabilityTag objects by name. - * - * @param name the name of the VulnerabilityTag - * @return a VulnerabilityTag object - */ - public VulnerabilityTag getVulnerabilityTagByName(final String name) { - final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name)); - final Query query = pm.newQuery(VulnerabilityTag.class, "name == :name"); - query.setRange(0, 1); - return singleResult(query.execute(loweredTrimmedTag)); - } - - /** - * Creates one or more VulnerabilityTag objects from the specified name(s). - * - * @param names the name(s) of the VulnerabilityTag(s) to create - * @return the created VulnerabilityTag object(s) - */ - private List createVulnerabilityTags(final List names) { - final List newTags = new ArrayList<>(); - for (final String name : names) { - final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name)); - if (getVulnerabilityTagByName(loweredTrimmedTag) == null) { - final VulnerabilityTag tag = new VulnerabilityTag(); - tag.setName(loweredTrimmedTag); - newTags.add(tag); - } - } - return new ArrayList<>(persist(newTags)); - } - - /** - * Binds the two objects together in a corresponding join table. - * - * @param vulnerability a Vulnerability object - * @param tags a List of VulnerabilityTag objects - */ - @SuppressWarnings("unchecked") - public void bindTagsToVulnerability(Vulnerability vulnerability, List tags) { - final Query query = pm.newQuery(VulnerabilityTag.class, "vulnerabilities.contains(:vulnerability)"); - final List currentVulnerabilityTags = (List) query.execute(vulnerability); - pm.currentTransaction().begin(); - for (final VulnerabilityTag tag : currentVulnerabilityTags) { - if (!tags.contains(tag)) { - tag.getVulnerabilities().remove(vulnerability); - } - } - vulnerability.setVulnerabilityTags(tags); - for (final VulnerabilityTag tag : tags) { - final List vulnerabilities = tag.getVulnerabilities(); - if (!vulnerabilities.contains(vulnerability)) { - vulnerabilities.add(vulnerability); - } - } - pm.currentTransaction().commit(); - } -} diff --git a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java index 61927d3ca..c3cfa6f9e 100644 --- a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java @@ -34,8 +34,8 @@ import org.dependencytrack.model.Component; import org.dependencytrack.model.Cwe; import org.dependencytrack.model.Project; +import org.dependencytrack.model.Tag; import org.dependencytrack.model.Vulnerability; -import org.dependencytrack.model.VulnerabilityTag; import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.persistence.QueryManager; import org.dependencytrack.resources.v1.vo.AffectedComponent; @@ -373,9 +373,9 @@ public Response updateVulnerability(Vulnerability jsonVuln) { } } - final List resolvedTags = qm.resolveVulnerabilityTags(jsonVuln.getVulnerabilityTags()); - qm.bindTagsToVulnerability(vulnerability, resolvedTags); - + final List resolvedTags = qm.resolveTags(jsonVuln.getTags()); + qm.bind(vulnerability, resolvedTags); + recalculateScoresFromVector(jsonVuln); vulnerability = qm.updateVulnerability(jsonVuln, true); qm.persist(vsList); diff --git a/src/main/resources/migration/changelog-v5.5.0.xml b/src/main/resources/migration/changelog-v5.5.0.xml index 046f421ba..a8aa4b4aa 100644 --- a/src/main/resources/migration/changelog-v5.5.0.xml +++ b/src/main/resources/migration/changelog-v5.5.0.xml @@ -62,4 +62,30 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From c0b4b8f6a911efb12efea18229ba5251841771a5 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 17 Apr 2024 17:49:45 +0100 Subject: [PATCH 3/9] fix test --- src/main/java/org/dependencytrack/model/Tag.java | 1 + .../org/dependencytrack/model/Vulnerability.java | 6 ++++-- src/main/resources/migration/changelog-v5.5.0.xml | 14 +++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/dependencytrack/model/Tag.java b/src/main/java/org/dependencytrack/model/Tag.java index 0f03762c8..b93caa23f 100644 --- a/src/main/java/org/dependencytrack/model/Tag.java +++ b/src/main/java/org/dependencytrack/model/Tag.java @@ -70,6 +70,7 @@ public class Tag implements Serializable { @Persistent @JsonIgnore + @Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC")) private List vulnerabilities; public long getId() { diff --git a/src/main/java/org/dependencytrack/model/Vulnerability.java b/src/main/java/org/dependencytrack/model/Vulnerability.java index 31c62fbaf..7d6fc4766 100644 --- a/src/main/java/org/dependencytrack/model/Vulnerability.java +++ b/src/main/java/org/dependencytrack/model/Vulnerability.java @@ -82,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") @@ -320,9 +321,10 @@ public static boolean isKnownSource(String source) { @NotNull private UUID uuid; - @Persistent(table = "VULNERABILITY_TAGS", defaultFetchGroup = "true") + @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 tags; private transient Epss epss; diff --git a/src/main/resources/migration/changelog-v5.5.0.xml b/src/main/resources/migration/changelog-v5.5.0.xml index a8aa4b4aa..3813fc968 100644 --- a/src/main/resources/migration/changelog-v5.5.0.xml +++ b/src/main/resources/migration/changelog-v5.5.0.xml @@ -64,7 +64,7 @@ - + @@ -72,19 +72,19 @@ - + - + - - From 7edbf94da0cf775e67b70901f43b349df09b498e Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 18 Apr 2024 12:45:11 +0100 Subject: [PATCH 4/9] add test for tag query manager --- .../java/org/dependencytrack/model/Tag.java | 2 +- .../persistence/TagQueryManagerTest.java | 76 +++++++++++++++++++ .../v1/VulnerabilityResourceTest.java | 3 + 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java diff --git a/src/main/java/org/dependencytrack/model/Tag.java b/src/main/java/org/dependencytrack/model/Tag.java index b93caa23f..bf71b20d3 100644 --- a/src/main/java/org/dependencytrack/model/Tag.java +++ b/src/main/java/org/dependencytrack/model/Tag.java @@ -70,7 +70,7 @@ public class Tag implements Serializable { @Persistent @JsonIgnore - @Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC")) + @Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "vulnId ASC")) private List vulnerabilities; public long getId() { diff --git a/src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java b/src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java new file mode 100644 index 000000000..25893d68f --- /dev/null +++ b/src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java @@ -0,0 +1,76 @@ +/* + * This file is part of Dependency-Track. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.dependencytrack.persistence; + +import org.dependencytrack.PersistenceCapableTest; +import org.dependencytrack.model.Tag; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TagQueryManagerTest extends PersistenceCapableTest { + + @Test + public void testTagIsCreated() { + assertThat(qm.createTag("test-tag")).satisfies( + tagCreated -> assertThat(tagCreated.getName()).isEqualTo("test-tag") + ); + } + + @Test + public void testShouldGetTagByName() { + Tag tag = new Tag(); + tag.setName("test-tag"); + Tag result = qm.persist(tag); + assertThat(qm.getTagByName(result.getName())).satisfies( + tagFetched -> assertThat(tagFetched.getName()).isEqualTo("test-tag") + ); + } + + @Test + public void testShouldGetNullWhenTagNotPresent() { + assertThat(qm.getTagByName("test-tag")).isNull(); + } + + @Test + public void testTagsAreResolved() { + + // Resolve empty list of tags + assertThat(qm.resolveTags(Collections.emptyList())).isEmpty(); + + Tag tag1 = qm.createTag("test-tag-1"); + Tag tag2 = new Tag(); + tag2.setName("test-tag-2"); + + assertThat(qm.resolveTags(List.of(tag1, tag2))).satisfiesExactlyInAnyOrder( + tag -> assertThat(tag.getName()).isEqualTo(tag1.getName()), + tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) + ); + + // Update name of one tag and resolve again. + tag1.setName("test-tag-updated"); + assertThat(qm.resolveTags(List.of(tag1, tag2))).satisfiesExactlyInAnyOrder( + tag -> assertThat(tag.getName()).isEqualTo(tag1.getName()), + tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) + ); + } +} diff --git a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java index 93e2a6e0b..eebb5719c 100644 --- a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java @@ -512,6 +512,7 @@ public void updateVulnerabilityTest() throws Exception { .add("cvssV2Vector", "(AV:N/AC:M/Au:S/C:P/I:P/A:P)") .add("cvssV3Vector", "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L") .add("owaspRRVector", "SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3") + .add("tags", Json.createArrayBuilder().add(Json.createObjectBuilder().add("name", "vuln-tag"))) .add("uuid", vuln.getUuid().toString()) .build(); Response response = target(V1_VULNERABILITY).request() @@ -538,6 +539,8 @@ public void updateVulnerabilityTest() throws Exception { Assert.assertEquals("MEDIUM", json.getString("severity")); Assert.assertEquals(1, json.getJsonArray("cwes").size()); Assert.assertEquals(80, json.getJsonArray("cwes").getJsonObject(0).getInt("cweId")); + Assert.assertEquals(1, json.getJsonArray("tags").size()); + Assert.assertEquals("vuln-tag", json.getJsonArray("tags").getJsonObject(0).getString("name")); Assert.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonArray("cwes").getJsonObject(0).getString("name")); Assert.assertTrue(UuidUtil.isValidUUID(json.getString("uuid"))); } From 19c4a0bf46bb03ba75388a8d0ecbe428c4256c4f Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 18 Apr 2024 12:59:52 +0100 Subject: [PATCH 5/9] Update VulnerabilityQueryManagerTest.java --- .../VulnerabilityQueryManagerTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java b/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java index d0fbfabe7..a0ed1ec3a 100644 --- a/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java +++ b/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java @@ -754,4 +754,48 @@ public void getVulnerabilitiesByComponentTest() { ); } } + + public static class VulnerabilityTagTest extends PersistenceCapableTest { + + @Test + public void testBindTagsToVulnerability() { + + var vulnA = new Vulnerability(); + vulnA.setVulnId("INT-001"); + vulnA.setSource(Vulnerability.Source.INTERNAL); + vulnA.setSeverity(Severity.HIGH); + + var vulnB = new Vulnerability(); + vulnB.setVulnId("INT-002"); + vulnB.setSource(Vulnerability.Source.INTERNAL); + vulnB.setSeverity(Severity.LOW); + + qm.persist(List.of(vulnA, vulnB)); + + var tag1 = qm.createTag("vuln-tag-1"); + var tag2 = qm.createTag("vuln-tag-2"); + + qm.bind(vulnA, List.of(tag1, tag2)); + qm.bind(vulnB, List.of(tag2)); + + var tagsBound = qm.getVulnerabilityByVulnId(vulnA.getSource(), vulnA.getVulnId()); + assertThat(tagsBound.getTags()).isNotNull(); + assertThat(tagsBound.getTags()).satisfiesExactlyInAnyOrder( + tag -> assertThat(tag.getName()).isEqualTo(tag1.getName()), + tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) + ); + + tagsBound = qm.getVulnerabilityByVulnId(vulnB.getSource(), vulnB.getVulnId()); + assertThat(tagsBound.getTags()).isNotNull(); + assertThat(tagsBound.getTags()).satisfiesExactlyInAnyOrder( + tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) + ); + + var tag1Bound = qm.getTagByName(tag1.getName()); + assertThat(tag1Bound.getVulnerabilities().size()).isEqualTo(1); + + var tag2Bound = qm.getTagByName(tag2.getName()); + assertThat(tag2Bound.getVulnerabilities().size()).isEqualTo(2); + } + } } \ No newline at end of file From 90752b20a0436474d1b2be5e668e365552af92ca Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 18 Apr 2024 14:29:40 +0100 Subject: [PATCH 6/9] new endpoint to update tags for a vulnerability --- .../resources/v1/VulnerabilityResource.java | 31 ++++++++++++++++++ .../v1/VulnerabilityResourceTest.java | 32 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java index c3cfa6f9e..2b5a3bf85 100644 --- a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java @@ -623,4 +623,35 @@ public Response unassignVulnerability(@ApiParam(value = "The UUID of the vulnera } } } + + @POST + @Path("/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation( + value = "Updates tags for a vulnerability" + ) + @ApiResponses(value = { + @ApiResponse(code = 401, message = "Unauthorized"), + @ApiResponse(code = 404, message = "The vulnerability could not be found") + }) + @PermissionRequired(Permissions.Constants.VULNERABILITY_MANAGEMENT) + public Response updateVulnerabilityTags(Vulnerability jsonVuln) { + final Validator validator = super.getValidator(); + failOnValidationError( + validator.validateProperty(jsonVuln, "uuid"), + validator.validateProperty(jsonVuln, "tags") + ); + try (QueryManager qm = new QueryManager()) { + Vulnerability vulnerability = qm.getObjectByUuid(Vulnerability.class, jsonVuln.getUuid()); + if (vulnerability != null) { + final List resolvedTags = qm.resolveTags(jsonVuln.getTags()); + qm.bind(vulnerability, resolvedTags); + qm.persist(vulnerability); + return Response.ok(vulnerability).build(); + } else { + return Response.status(Response.Status.NOT_FOUND).entity("The vulnerability could not be found.").build(); + } + } + } } diff --git a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java index eebb5719c..40e47fb12 100644 --- a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java @@ -31,6 +31,7 @@ import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.Project; import org.dependencytrack.model.Severity; +import org.dependencytrack.model.Tag; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.persistence.CweImporter; @@ -902,4 +903,35 @@ private class SampleData { qm.makeAnalysis(c3, v5, AnalysisState.NOT_AFFECTED, AnalysisJustification.CODE_NOT_REACHABLE, AnalysisResponse.WILL_NOT_FIX, "Analysis details here", true); } } + + @Test + public void updateVulnerabilityTagsTest() { + Vulnerability vuln = new Vulnerability(); + vuln.setVulnId("ACME-1"); + vuln.setSource(Vulnerability.Source.INTERNAL); + Tag existingTag = qm.createTag("old-tag"); + vuln.setTags(List.of(existingTag)); + vuln = qm.createVulnerability(vuln, false); + + JsonObject payload = Json.createObjectBuilder() + .add("uuid", vuln.getUuid().toString()) + .add("tags", Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("name", "new-tag-1")) + .add(Json.createObjectBuilder().add("name", "new-tag-2"))) + .build(); + + Response response = target(V1_VULNERABILITY + "/tags").request() + .header(X_API_KEY, apiKey) + .post(Entity.json(payload.toString())); + + Assert.assertEquals(200, response.getStatus(), 0); + JsonObject json = parseJsonObject(response); + Assert.assertNotNull(json); + Assert.assertEquals("ACME-1", json.getString("vulnId")); + Assert.assertEquals("INTERNAL", json.getString("source")); + Assert.assertEquals(2, json.getJsonArray("tags").size()); + Assert.assertEquals("new-tag-1", json.getJsonArray("tags").getJsonObject(0).getString("name")); + Assert.assertEquals("new-tag-2", json.getJsonArray("tags").getJsonObject(1).getString("name")); + Assert.assertTrue(UuidUtil.isValidUUID(json.getString("uuid"))); + } } From c5e1ca1a574c277bbb06becfd1227643d66d0c93 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Mon, 22 Apr 2024 14:29:22 +0100 Subject: [PATCH 7/9] add endpoint to get vulnerabilities for tag --- .../persistence/QueryManager.java | 4 ++ .../VulnerabilityQueryManager.java | 19 +++++++++ .../resources/v1/VulnerabilityResource.java | 23 +++++++++++ .../VulnerabilityQueryManagerTest.java | 40 +++++++++++++------ .../v1/VulnerabilityResourceTest.java | 21 ++++++++++ 5 files changed, 95 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 4098d1eea..44d797576 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -1942,4 +1942,8 @@ public List resolveTags(final List tags) { public void bind(Vulnerability vulnerability, List tags) { getVulnerabilityQueryManager().bind(vulnerability, tags); } + + public PaginatedResult getVulnerabilities(final Tag tag) { + return getVulnerabilityQueryManager().getVulnerabilities(tag); + } } diff --git a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java index 05f168562..8937d0901 100644 --- a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java @@ -914,4 +914,23 @@ public void bind(Vulnerability vulnerability, List tags) { } pm.currentTransaction().commit(); } + + /** + * 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 PaginatedResult result; + final Query query = pm.newQuery(Vulnerability.class); + if (orderBy == null) { + query.setOrdering("vulnId asc, id asc"); + } + query.setFilter("(tags.contains(:tag))"); + Map params = new HashMap<>(); + params.put("tag", tag); + result = execute(query, params); + return result; + } } diff --git a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java index 2b5a3bf85..8d41949b2 100644 --- a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java @@ -654,4 +654,27 @@ public Response updateVulnerabilityTags(Vulnerability jsonVuln) { } } } + + @GET + @Path("/tag/{tag}") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation( + value = "Returns a list of all vulnerabilities by tag", + response = Vulnerability.class, + responseContainer = "List", + responseHeaders = @ResponseHeader(name = TOTAL_COUNT_HEADER, response = Long.class, description = "The total number of vulnerabilities with the tag") + ) + @ApiResponses(value = { + @ApiResponse(code = 401, message = "Unauthorized") + }) + @PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO) + public Response getVulnerabilitiesByTag( + @ApiParam(value = "The tag to query on", required = true) + @PathParam("tag") String tagString) { + try (QueryManager qm = new QueryManager(getAlpineRequest())) { + final Tag tag = qm.getTagByName(tagString); + final PaginatedResult result = qm.getVulnerabilities(tag); + return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build(); + } + } } diff --git a/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java b/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java index a0ed1ec3a..400e6a2d5 100644 --- a/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java +++ b/src/test/java/org/dependencytrack/persistence/VulnerabilityQueryManagerTest.java @@ -757,9 +757,8 @@ public void getVulnerabilitiesByComponentTest() { public static class VulnerabilityTagTest extends PersistenceCapableTest { - @Test - public void testBindTagsToVulnerability() { - + @Before + public void setupData() { var vulnA = new Vulnerability(); vulnA.setVulnId("INT-001"); vulnA.setSource(Vulnerability.Source.INTERNAL); @@ -772,30 +771,47 @@ public void testBindTagsToVulnerability() { qm.persist(List.of(vulnA, vulnB)); - var tag1 = qm.createTag("vuln-tag-1"); - var tag2 = qm.createTag("vuln-tag-2"); + var tag1 = qm.createTag("vtag-1"); + var tag2 = qm.createTag("vtag-2"); qm.bind(vulnA, List.of(tag1, tag2)); qm.bind(vulnB, List.of(tag2)); + } - var tagsBound = qm.getVulnerabilityByVulnId(vulnA.getSource(), vulnA.getVulnId()); + @Test + public void testBindTagsToVulnerability() { + + var tagsBound = qm.getVulnerabilityByVulnId(Vulnerability.Source.INTERNAL, "INT-001"); assertThat(tagsBound.getTags()).isNotNull(); assertThat(tagsBound.getTags()).satisfiesExactlyInAnyOrder( - tag -> assertThat(tag.getName()).isEqualTo(tag1.getName()), - tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) + tag -> assertThat(tag.getName()).isEqualTo("vtag-1"), + tag -> assertThat(tag.getName()).isEqualTo("vtag-2") ); - tagsBound = qm.getVulnerabilityByVulnId(vulnB.getSource(), vulnB.getVulnId()); + tagsBound = qm.getVulnerabilityByVulnId(Vulnerability.Source.INTERNAL, "INT-002"); assertThat(tagsBound.getTags()).isNotNull(); assertThat(tagsBound.getTags()).satisfiesExactlyInAnyOrder( - tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) + tag -> assertThat(tag.getName()).isEqualTo("vtag-2") ); - var tag1Bound = qm.getTagByName(tag1.getName()); + var tag1Bound = qm.getTagByName("vtag-1"); assertThat(tag1Bound.getVulnerabilities().size()).isEqualTo(1); - var tag2Bound = qm.getTagByName(tag2.getName()); + var tag2Bound = qm.getTagByName("vtag-2"); assertThat(tag2Bound.getVulnerabilities().size()).isEqualTo(2); } + + @Test + public void testGetVulnerabilitiesByTag() { + // One vulnerability is tagged with "vtag-1" + var taggedVulnerabilities = qm.getVulnerabilities(qm.getTagByName("vtag-1")); + assertThat(taggedVulnerabilities).isNotNull(); + assertThat(taggedVulnerabilities.getTotal()).isEqualTo(1); + + // Two vulnerabilities are tagged with "vtag-2" + taggedVulnerabilities = qm.getVulnerabilities(qm.getTagByName("vtag-2")); + assertThat(taggedVulnerabilities).isNotNull(); + assertThat(taggedVulnerabilities.getTotal()).isEqualTo(2); + } } } \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java index 40e47fb12..493a100e1 100644 --- a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java @@ -934,4 +934,25 @@ public void updateVulnerabilityTagsTest() { Assert.assertEquals("new-tag-2", json.getJsonArray("tags").getJsonObject(1).getString("name")); Assert.assertTrue(UuidUtil.isValidUUID(json.getString("uuid"))); } + + @Test + public void getVulnerabilitiesByTagTest() { + Vulnerability vuln1 = new Vulnerability(); + vuln1.setVulnId("ACME-1"); + vuln1.setSource(Vulnerability.Source.INTERNAL); + Vulnerability vuln2 = new Vulnerability(); + vuln2.setVulnId("ACME-2"); + vuln2.setSource(Vulnerability.Source.INTERNAL); + Tag tag = qm.createTag("v-tag"); + qm.bind(vuln1, List.of(tag)); + qm.bind(vuln2, List.of(tag)); + + Response response = target(V1_VULNERABILITY + "/tag/" + tag.getName()).request() + .header(X_API_KEY, apiKey) + .get(); + Assert.assertEquals(200, response.getStatus(), 0); + JsonArray json = parseJsonArray(response); + Assert.assertNotNull(json); + Assert.assertEquals(json.size(), 2); + } } From fef52431106e55eaff92b95ae660d65e0ac6b842 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 24 Apr 2024 16:08:58 +0100 Subject: [PATCH 8/9] change request body for api call --- .../persistence/QueryManager.java | 4 +++ .../persistence/TagQueryManager.java | 9 ++++-- .../VulnerabilityQueryManager.java | 32 +++++++++---------- .../resources/v1/VulnerabilityResource.java | 15 ++++----- .../persistence/TagQueryManagerTest.java | 16 ++++++++++ .../v1/VulnerabilityResourceTest.java | 12 ++----- 6 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 44d797576..2f01da85c 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -1939,6 +1939,10 @@ public List resolveTags(final List tags) { return getTagQueryManager().resolveTags(tags); } + public List resolveTagsByName(final List tagNames) { + return getTagQueryManager().resolveTagsByName(tagNames); + } + public void bind(Vulnerability vulnerability, List tags) { getVulnerabilityQueryManager().bind(vulnerability, tags); } diff --git a/src/main/java/org/dependencytrack/persistence/TagQueryManager.java b/src/main/java/org/dependencytrack/persistence/TagQueryManager.java index 007f99953..a4bbf7f00 100644 --- a/src/main/java/org/dependencytrack/persistence/TagQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/TagQueryManager.java @@ -89,13 +89,18 @@ public PaginatedResult getTags(String policyUuid) { * @return List of resolved Tags */ public synchronized List resolveTags(final List tags) { + List tagNames = tags.stream().map(tag -> tag.getName()).toList(); + return resolveTagsByName(tagNames); + } + + public synchronized List resolveTagsByName(final List tags) { if (tags == null) { return new ArrayList<>(); } final List resolvedTags = new ArrayList<>(); final List unresolvedTags = new ArrayList<>(); - for (final Tag tag : tags) { - final String trimmedTag = StringUtils.trimToNull(tag.getName()); + for (final String tag : tags) { + final String trimmedTag = StringUtils.trimToNull(tag); if (trimmedTag != null) { final Tag resolvedTag = getTagByName(trimmedTag); if (resolvedTag != null) { diff --git a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java index 8937d0901..6d29bd01a 100644 --- a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java @@ -897,22 +897,22 @@ public void deleteAffectedVersionAttributions(final Vulnerability vulnerability) */ @SuppressWarnings("unchecked") public void bind(Vulnerability vulnerability, List tags) { - final Query query = pm.newQuery(Tag.class, "vulnerabilities.contains(:vulnerability)"); - final List currentVulnerabilityTags = (List) query.execute(vulnerability); - pm.currentTransaction().begin(); - for (final Tag tag : currentVulnerabilityTags) { - if (!tags.contains(tag)) { - tag.getVulnerabilities().remove(vulnerability); + runInTransaction(() -> { + final Query query = pm.newQuery(Tag.class, "vulnerabilities.contains(:vulnerability)"); + final List currentVulnerabilityTags = (List) 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 vulnerabilities = tag.getVulnerabilities(); - if (!vulnerabilities.contains(vulnerability)) { - vulnerabilities.add(vulnerability); + vulnerability.setTags(tags); + for (final Tag tag : tags) { + final List vulnerabilities = tag.getVulnerabilities(); + if (!vulnerabilities.contains(vulnerability)) { + vulnerabilities.add(vulnerability); + } } - } - pm.currentTransaction().commit(); + }); } /** @@ -922,7 +922,6 @@ public void bind(Vulnerability vulnerability, List tags) { * @return a List of vulnerabilities that contain the tag */ public PaginatedResult getVulnerabilities(final Tag tag) { - final PaginatedResult result; final Query query = pm.newQuery(Vulnerability.class); if (orderBy == null) { query.setOrdering("vulnId asc, id asc"); @@ -930,7 +929,6 @@ public PaginatedResult getVulnerabilities(final Tag tag) { query.setFilter("(tags.contains(:tag))"); Map params = new HashMap<>(); params.put("tag", tag); - result = execute(query, params); - return result; + return execute(query, params); } } diff --git a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java index 8d41949b2..e595a5246 100644 --- a/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/VulnerabilityResource.java @@ -625,7 +625,7 @@ public Response unassignVulnerability(@ApiParam(value = "The UUID of the vulnera } @POST - @Path("/tags") + @Path("/{uuid}/tags") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @ApiOperation( @@ -636,16 +636,13 @@ public Response unassignVulnerability(@ApiParam(value = "The UUID of the vulnera @ApiResponse(code = 404, message = "The vulnerability could not be found") }) @PermissionRequired(Permissions.Constants.VULNERABILITY_MANAGEMENT) - public Response updateVulnerabilityTags(Vulnerability jsonVuln) { - final Validator validator = super.getValidator(); - failOnValidationError( - validator.validateProperty(jsonVuln, "uuid"), - validator.validateProperty(jsonVuln, "tags") - ); + public Response updateVulnerabilityTags(List tags, + @ApiParam(value = "UUID of the vulnerability", required = true) + @PathParam("uuid") String uuid) { try (QueryManager qm = new QueryManager()) { - Vulnerability vulnerability = qm.getObjectByUuid(Vulnerability.class, jsonVuln.getUuid()); + Vulnerability vulnerability = qm.getObjectByUuid(Vulnerability.class, uuid); if (vulnerability != null) { - final List resolvedTags = qm.resolveTags(jsonVuln.getTags()); + final List resolvedTags = qm.resolveTagsByName(tags); qm.bind(vulnerability, resolvedTags); qm.persist(vulnerability); return Response.ok(vulnerability).build(); diff --git a/src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java b/src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java index 25893d68f..d0192694c 100644 --- a/src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java +++ b/src/test/java/org/dependencytrack/persistence/TagQueryManagerTest.java @@ -73,4 +73,20 @@ public void testTagsAreResolved() { tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) ); } + + @Test + public void testTagsAreResolvedByName() { + + // Resolve empty list of tags + assertThat(qm.resolveTagsByName(Collections.emptyList())).isEmpty(); + + Tag tag1 = qm.createTag("test-tag-1"); + Tag tag2 = new Tag(); + tag2.setName("test-tag-2"); + + assertThat(qm.resolveTagsByName(List.of("test-tag-1", "test-tag-2"))).satisfiesExactlyInAnyOrder( + tag -> assertThat(tag.getName()).isEqualTo(tag1.getName()), + tag -> assertThat(tag.getName()).isEqualTo(tag2.getName()) + ); + } } diff --git a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java index 493a100e1..0908c56a9 100644 --- a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java @@ -913,16 +913,10 @@ public void updateVulnerabilityTagsTest() { vuln.setTags(List.of(existingTag)); vuln = qm.createVulnerability(vuln, false); - JsonObject payload = Json.createObjectBuilder() - .add("uuid", vuln.getUuid().toString()) - .add("tags", Json.createArrayBuilder() - .add(Json.createObjectBuilder().add("name", "new-tag-1")) - .add(Json.createObjectBuilder().add("name", "new-tag-2"))) - .build(); - - Response response = target(V1_VULNERABILITY + "/tags").request() + Response response = target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/tags") + .request() .header(X_API_KEY, apiKey) - .post(Entity.json(payload.toString())); + .post(Entity.json(List.of("new-tag-1", "new-tag-2"))); Assert.assertEquals(200, response.getStatus(), 0); JsonObject json = parseJsonObject(response); From 75d9e8178f31cf72922bc60befd9fdbfd256b94b Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 24 Apr 2024 17:18:45 +0100 Subject: [PATCH 9/9] Update TagQueryManager.java --- .../java/org/dependencytrack/persistence/TagQueryManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/dependencytrack/persistence/TagQueryManager.java b/src/main/java/org/dependencytrack/persistence/TagQueryManager.java index a4bbf7f00..b9645cf80 100644 --- a/src/main/java/org/dependencytrack/persistence/TagQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/TagQueryManager.java @@ -89,6 +89,9 @@ public PaginatedResult getTags(String policyUuid) { * @return List of resolved Tags */ public synchronized List resolveTags(final List tags) { + if (tags == null) { + return new ArrayList<>(); + } List tagNames = tags.stream().map(tag -> tag.getName()).toList(); return resolveTagsByName(tagNames); }