diff --git a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java index 6aa172ff9..808c0fe52 100644 --- a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java @@ -583,7 +583,8 @@ public Project clone(UUID from, String newVersion, boolean includeTags, boolean // Add vulnerabilties and finding attribution from the source component to the cloned component for (Vulnerability vuln : sourceComponent.getVulnerabilities()) { final FindingAttribution sourceAttribution = this.getFindingAttribution(vuln, sourceComponent); - this.addVulnerability(vuln, clonedComponent, sourceAttribution.getAnalyzerIdentity(), sourceAttribution.getAlternateIdentifier(), sourceAttribution.getReferenceUrl()); + this.addVulnerability(vuln, clonedComponent, sourceAttribution.getAnalyzerIdentity(), sourceAttribution.getAlternateIdentifier(), + sourceAttribution.getReferenceUrl(), sourceAttribution.getAttributedOn()); } clonedComponents.put(sourceComponent.getId(), clonedComponent); } diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 9cb5721cc..6a0850305 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -881,6 +881,11 @@ public void addVulnerability(Vulnerability vulnerability, Component component, A getVulnerabilityQueryManager().addVulnerability(vulnerability, component, analyzerIdentity, alternateIdentifier, referenceUrl); } + public void addVulnerability(Vulnerability vulnerability, Component component, AnalyzerIdentity analyzerIdentity, + String alternateIdentifier, String referenceUrl, Date attributedOn) { + getVulnerabilityQueryManager().addVulnerability(vulnerability, component, analyzerIdentity, alternateIdentifier, referenceUrl, attributedOn); + } + public void removeVulnerability(Vulnerability vulnerability, Component component) { getVulnerabilityQueryManager().removeVulnerability(vulnerability, component); } diff --git a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java index e4e70d943..a7c7896fa 100644 --- a/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java @@ -37,7 +37,6 @@ import javax.jdo.PersistenceManager; import javax.jdo.Query; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -47,9 +46,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import java.util.UUID; import java.util.function.Function; +import java.util.stream.Collectors; final class VulnerabilityQueryManager extends QueryManager implements IQueryManager { @@ -200,7 +199,7 @@ public List getVulnerabilitiesForNpmModule(String module) { * @param analyzerIdentity the identify of the analyzer */ public void addVulnerability(Vulnerability vulnerability, Component component, AnalyzerIdentity analyzerIdentity) { - this.addVulnerability(vulnerability, component, analyzerIdentity, null, null); + this.addVulnerability(vulnerability, component, analyzerIdentity, null, null, null); } /** @@ -213,10 +212,28 @@ public void addVulnerability(Vulnerability vulnerability, Component component, A */ public void addVulnerability(Vulnerability vulnerability, Component component, AnalyzerIdentity analyzerIdentity, String alternateIdentifier, String referenceUrl) { + this.addVulnerability(vulnerability, component, analyzerIdentity, alternateIdentifier, referenceUrl, null); + } + + /** + * Adds a vulnerability to a component. + * @param vulnerability the vulnerability to add + * @param component the component affected by the vulnerability + * @param analyzerIdentity the identify of the analyzer + * @param alternateIdentifier the optional identifier if the analyzer refers to the vulnerability by an alternative identifier + * @param referenceUrl the optional URL that references the occurrence of the vulnerability if uniquely identified + * @param attributedOn the optional attribution date of the vulnerability. Used primarily when cloning projects, leave null when adding a new one. + */ + public void addVulnerability(Vulnerability vulnerability, Component component, AnalyzerIdentity analyzerIdentity, + String alternateIdentifier, String referenceUrl, Date attributedOn) { if (!contains(vulnerability, component)) { component.addVulnerability(vulnerability); component = persist(component); - persist(new FindingAttribution(component, vulnerability, analyzerIdentity, alternateIdentifier, referenceUrl)); + FindingAttribution findingAttribution = new FindingAttribution(component, vulnerability, analyzerIdentity, alternateIdentifier, referenceUrl); + if (attributedOn != null) { + findingAttribution.setAttributedOn(attributedOn); + } + persist(findingAttribution); } } diff --git a/src/test/java/org/dependencytrack/persistence/ProjectQueryManagerTest.java b/src/test/java/org/dependencytrack/persistence/ProjectQueryManagerTest.java index 3d9ec3ca4..32ab91b35 100644 --- a/src/test/java/org/dependencytrack/persistence/ProjectQueryManagerTest.java +++ b/src/test/java/org/dependencytrack/persistence/ProjectQueryManagerTest.java @@ -28,6 +28,7 @@ import org.dependencytrack.model.Bom; import org.dependencytrack.model.Component; import org.dependencytrack.model.DependencyMetrics; +import org.dependencytrack.model.Finding; import org.dependencytrack.model.IntegrityAnalysis; import org.dependencytrack.model.IntegrityMatchStatus; import org.dependencytrack.model.NotificationPublisher; @@ -39,6 +40,7 @@ import org.dependencytrack.model.Project; import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.ProjectMetrics; +import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vex; import org.dependencytrack.model.ViolationAnalysis; import org.dependencytrack.model.ViolationAnalysisState; @@ -201,4 +203,27 @@ public void recursivelyDeleteTest() { assertThat(policy.getProjects()).isEmpty(); } + @Test + public void testCloneProjectPreservesVulnerabilityAttributionDate() throws Exception { + Project project = qm.createProject("Example Project 1", "Description 1", "1.0", null, null, null, true, false); + Component comp = new Component(); + comp.setId(111L); + comp.setName("name"); + comp.setProject(project); + comp.setVersion("1.0"); + comp.setCopyright("Copyright Acme"); + qm.createComponent(comp, true); + Vulnerability vuln = new Vulnerability(); + vuln.setVulnId("INT-123"); + vuln.setSource(Vulnerability.Source.INTERNAL); + vuln.setSeverity(Severity.HIGH); + qm.persist(vuln); + qm.addVulnerability(vuln, comp, AnalyzerIdentity.INTERNAL_ANALYZER, "Vuln1", "http://vuln.com/vuln1", new Date()); + Project clonedProject = qm.clone(project.getUuid(), "1.1.0", false, false, true, false, false, false, false); + List findings = qm.getFindings(clonedProject); + assertThat(findings.size()).isEqualTo(1); + Finding finding = findings.get(0); + assertThat(finding).isNotNull(); + assertThat(finding.getAttribution().isEmpty()).isFalse(); + } } \ No newline at end of file