diff --git a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java index 38529bb73d..8ec4aabea8 100644 --- a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java @@ -186,6 +186,33 @@ public synchronized PolicyViolation addPolicyViolationIfNotExist(final PolicyVio return result; } + /** + * clones a policy violation + * @param sourcePolicyViolation the policy violation to clone + * @param destinationComponent the corresponding component + */ + public PolicyViolation clonePolicyViolation(PolicyViolation sourcePolicyViolation, Component destinationComponent){ + //cloning PolicyViolation + final PolicyViolation policyViolation = new PolicyViolation(); + policyViolation.setType(sourcePolicyViolation.getType()); + policyViolation.setComponent(destinationComponent); + policyViolation.setPolicyCondition(sourcePolicyViolation.getPolicyCondition()); + policyViolation.setTimestamp(sourcePolicyViolation.getTimestamp()); + policyViolation.setText(sourcePolicyViolation.getText()); + policyViolation.setType(sourcePolicyViolation.getType()); + //cloning ViolatioAnalysis + ViolationAnalysis violationAnalysis = cloneViolationAnalysis(destinationComponent, sourcePolicyViolation); + //cloning ViolationAnalysisComments + List comments = cloneViolationAnalysisComments(sourcePolicyViolation, violationAnalysis); + if(comments != null){ + violationAnalysis.setAnalysisComments(comments); + } + policyViolation.setAnalysis(violationAnalysis); + policyViolation.getAnalysis().setPolicyViolation(policyViolation); + policyViolation.setUuid(sourcePolicyViolation.getUuid()); + return policyViolation; + } + /** * Returns a List of all Policy objects. * This method if designed NOT to provide paginated results. @@ -340,6 +367,24 @@ public PaginatedResult getPolicyViolations(boolean includeSuppressed) { return result; } + /** + * clones a ViolationAnalysis + * @param destinationComponent the destinationComponent + * @param sourcePolicyViolation the PolicyViolation to clone from + * @return the cloned violationAnalysis + */ + public ViolationAnalysis cloneViolationAnalysis(Component destinationComponent, PolicyViolation sourcePolicyViolation){ + ViolationAnalysis violationAnalysis = new ViolationAnalysis(); + violationAnalysis.setComponent(destinationComponent); + if(sourcePolicyViolation.getAnalysis() != null){ + violationAnalysis.setSuppressed(sourcePolicyViolation.getAnalysis().isSuppressed()); + violationAnalysis.setViolationAnalysisState(sourcePolicyViolation.getAnalysis().getAnalysisState()); + } else { + violationAnalysis.setViolationAnalysisState(ViolationAnalysisState.NOT_SET); + } + return violationAnalysis; + } + /** * Returns a ViolationAnalysis for the specified Component and PolicyViolation. * @param component the Component @@ -378,6 +423,28 @@ public ViolationAnalysis makeViolationAnalysis(Component component, PolicyViolat return getViolationAnalysis(violationAnalysis.getComponent(), violationAnalysis.getPolicyViolation()); } + /** + * clones ViolationAnalysisComments + * @param sourcePolicyViolation the source PolicyViolation + * @param violationAnalysis the ViolationAnalysis to clone from + * @return the cloned ViolationAnalysisComments + */ + public List cloneViolationAnalysisComments(PolicyViolation sourcePolicyViolation, ViolationAnalysis violationAnalysis){ + List comments = new ArrayList(); + if(sourcePolicyViolation.getAnalysis() != null && sourcePolicyViolation.getAnalysis().getAnalysisComments() != null){ + for(ViolationAnalysisComment c : sourcePolicyViolation.getAnalysis().getAnalysisComments()){ + ViolationAnalysisComment comment = new ViolationAnalysisComment(); + comment.setViolationAnalysis(violationAnalysis); + comment.setComment(c.getComment()); + comment.setCommenter(c.getCommenter()); + comment.setTimestamp(c.getTimestamp()); + comments.add(comment); + } + } + + return comments; + } + /** * Adds a new violation analysis comment to the specified violation analysis. * @param violationAnalysis the violation analysis object to add a comment to diff --git a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java index 46c94da85d..861f3d4591 100644 --- a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java @@ -39,6 +39,7 @@ import org.dependencytrack.model.Component; import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.FindingAttribution; +import org.dependencytrack.model.PolicyViolation; import org.dependencytrack.model.Project; import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.ProjectProperty; @@ -568,7 +569,7 @@ public Project updateProject(Project transientProject, boolean commitIndex) { @Override public Project clone(UUID from, String newVersion, boolean includeTags, boolean includeProperties, boolean includeComponents, boolean includeServices, boolean includeAuditHistory, - boolean includeACL) { + boolean includeACL, boolean includePolicyViolations) { final Project source = getObjectByUuid(Project.class, from, Project.FetchGroup.ALL.name()); if (source == null) { LOGGER.warn("Project with UUID %s was supposed to be cloned, but it does not exist anymore".formatted(from)); @@ -694,6 +695,19 @@ public Project clone(UUID from, String newVersion, boolean includeTags, boolean } } + + if(includeComponents && includePolicyViolations){ + final List sourcePolicyViolations = getAllPolicyViolations(source); + if(sourcePolicyViolations != null){ + for(final PolicyViolation policyViolation: sourcePolicyViolations){ + final Component destinationComponent = clonedComponents.get(policyViolation.getComponent().getId()); + final PolicyViolation clonedPolicyViolation = clonePolicyViolation(policyViolation, destinationComponent); + persist(clonedPolicyViolation); + } + } + } + + project = getObjectById(Project.class, project.getId()); Event.dispatch(new IndexEvent(IndexEvent.Action.CREATE, project)); commitSearchIndex(true, Project.class); diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 52b3949c68..1aff4ca060 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -440,9 +440,9 @@ public boolean updateNewProjectACL(Project transientProject, Principal principal public Project clone(UUID from, String newVersion, boolean includeTags, boolean includeProperties, boolean includeComponents, boolean includeServices, boolean includeAuditHistory, - boolean includeACL) { + boolean includeACL, boolean includePolicyViolations) { return getProjectQueryManager().clone(from, newVersion, includeTags, includeProperties, - includeComponents, includeServices, includeAuditHistory, includeACL); + includeComponents, includeServices, includeAuditHistory, includeACL, includePolicyViolations); } public Project updateLastBomImport(Project p, Date date, String bomFormat) { @@ -609,6 +609,10 @@ public synchronized PolicyViolation addPolicyViolationIfNotExist(final PolicyVio return getPolicyQueryManager().addPolicyViolationIfNotExist(pv); } + public PolicyViolation clonePolicyViolation(PolicyViolation sourcePolicyViolation, Component destinationComponent){ + return getPolicyQueryManager().clonePolicyViolation(sourcePolicyViolation, destinationComponent); + } + public List getAllPolicyViolations() { return getPolicyQueryManager().getAllPolicyViolations(); } diff --git a/src/main/java/org/dependencytrack/resources/v1/vo/CloneProjectRequest.java b/src/main/java/org/dependencytrack/resources/v1/vo/CloneProjectRequest.java index ba629773b9..8754613552 100644 --- a/src/main/java/org/dependencytrack/resources/v1/vo/CloneProjectRequest.java +++ b/src/main/java/org/dependencytrack/resources/v1/vo/CloneProjectRequest.java @@ -59,6 +59,8 @@ public class CloneProjectRequest { private final boolean includeACL; + private final boolean includePolicyViolations; + @JsonCreator public CloneProjectRequest(@JsonProperty(value = "project", required = true) String project, @JsonProperty(value = "version", required = true) String version, @@ -68,7 +70,8 @@ public CloneProjectRequest(@JsonProperty(value = "project", required = true) Str @JsonProperty(value = "includeComponents") boolean includeComponents, @JsonProperty(value = "includeServices") boolean includeServices, @JsonProperty(value = "includeAuditHistory") boolean includeAuditHistory, - @JsonProperty(value = "includeACL") boolean includeACL) { + @JsonProperty(value = "includeACL") boolean includeACL, + @JsonProperty(value = "includePolicyViolations") boolean includePolicyViolations) { if (includeDependencies) { // For backward compatibility includeComponents = true; } @@ -81,6 +84,7 @@ public CloneProjectRequest(@JsonProperty(value = "project", required = true) Str this.includeServices = includeServices; this.includeAuditHistory = includeAuditHistory; this.includeACL = includeACL; + this.includePolicyViolations = includePolicyViolations; } public String getProject() { @@ -119,4 +123,8 @@ public boolean includeACL() { return includeACL; } + public boolean includePolicyViolations() { + return includePolicyViolations; + } + } diff --git a/src/main/java/org/dependencytrack/tasks/CloneProjectTask.java b/src/main/java/org/dependencytrack/tasks/CloneProjectTask.java index 3bdbe5a00d..c7f895a1d9 100644 --- a/src/main/java/org/dependencytrack/tasks/CloneProjectTask.java +++ b/src/main/java/org/dependencytrack/tasks/CloneProjectTask.java @@ -43,7 +43,7 @@ public void inform(final Event e) { try (QueryManager qm = new QueryManager()) { final Project project = qm.clone(UUID.fromString(request.getProject()), request.getVersion(), request.includeTags(), request.includeProperties(), - request.includeComponents(), request.includeServices(), request.includeAuditHistory(), request.includeACL()); + request.includeComponents(), request.includeServices(), request.includeAuditHistory(), request.includeACL(), request.includePolicyViolations()); LOGGER.info("Cloned project: " + request.getProject() + " to " + project.getUuid()); } } diff --git a/src/test/java/org/dependencytrack/event/CloneProjectEventTest.java b/src/test/java/org/dependencytrack/event/CloneProjectEventTest.java index 7421e9a5df..3c93ddf081 100644 --- a/src/test/java/org/dependencytrack/event/CloneProjectEventTest.java +++ b/src/test/java/org/dependencytrack/event/CloneProjectEventTest.java @@ -29,7 +29,7 @@ public class CloneProjectEventTest { @Test public void testEvent() { UUID uuid = UUID.randomUUID(); - CloneProjectRequest request = new CloneProjectRequest(uuid.toString(), "1.0", true, true, true, true, true, true, true); + CloneProjectRequest request = new CloneProjectRequest(uuid.toString(), "1.0", true, true, true, true, true, true, true, true); CloneProjectEvent event = new CloneProjectEvent(request); Assert.assertEquals(request, event.getRequest()); } diff --git a/src/test/java/org/dependencytrack/persistence/PolicyQueryManagerTest.java b/src/test/java/org/dependencytrack/persistence/PolicyQueryManagerTest.java index 2753e3308a..4feed20377 100644 --- a/src/test/java/org/dependencytrack/persistence/PolicyQueryManagerTest.java +++ b/src/test/java/org/dependencytrack/persistence/PolicyQueryManagerTest.java @@ -19,12 +19,32 @@ package org.dependencytrack.persistence; import org.dependencytrack.PersistenceCapableTest; +import org.dependencytrack.model.Classifier; +import org.dependencytrack.model.Component; import org.dependencytrack.model.Policy; +import org.dependencytrack.model.PolicyViolation; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ViolationAnalysis; +import org.dependencytrack.model.ViolationAnalysisComment; +import org.dependencytrack.model.ViolationAnalysisState; import org.junit.Test; +import org.junit.Assert; +import com.github.packageurl.PackageURL; +import com.github.packageurl.PackageURLBuilder; + +import ch.qos.logback.core.subst.Token.Type; +import us.springett.parsers.cpe.Cpe; +import us.springett.parsers.cpe.CpeParser; +import us.springett.parsers.cpe.exceptions.CpeParsingException; + +import java.util.ArrayList; +import java.util.Date; import java.util.List; +import javax.jdo.PersistenceManager; + +import static java.util.Collections.newSetFromMap; import static org.assertj.core.api.Assertions.assertThat; public class PolicyQueryManagerTest extends PersistenceCapableTest { @@ -47,4 +67,53 @@ public void testRemoveProjectFromPolicies() { assertThat(qm.getObjectById(Policy.class, policy2.getId()).getProjects()).isEmpty(); } + @Test + public void testclonePolicyViolation() throws Exception{ + PolicyViolation policyViolation = new PolicyViolation(); + policyViolation.setId(1); + + // Component for cloning + Component component = new Component(); + component.setId(111L); + component.setName("name"); + component.setVersion("1.0"); + component.setCopyright("Copyright Acme"); + + policyViolation.setComponent(component); + policyViolation.setText("policyViolation"); + policyViolation.setTimestamp(new Date()); + policyViolation.setType(PolicyViolation.Type.LICENSE); + + // ViolationAnalysis for cloning + ViolationAnalysis violationAnalysis = new ViolationAnalysis(); + violationAnalysis.setSuppressed(true); + violationAnalysis.setViolationAnalysisState(ViolationAnalysisState.APPROVED); + + // ViolationAnalysisComments + List violationAnalysisComments = new ArrayList<>(); + ViolationAnalysisComment violationAnalysisComment = new ViolationAnalysisComment(); + violationAnalysisComment.setComment("testComment"); + violationAnalysisComment.setCommenter("admin"); + violationAnalysisComment.setTimestamp(new Date()); + violationAnalysisComment.setViolationAnalysis(violationAnalysis); + violationAnalysisComments.add(violationAnalysisComment); + violationAnalysis.setAnalysisComments(violationAnalysisComments); + + policyViolation.setAnalysis(violationAnalysis); + + PolicyViolation clonedPolicyViolation = qm.clonePolicyViolation(policyViolation, component); + Assert.assertEquals(policyViolation.getText(), clonedPolicyViolation.getText()); + Assert.assertEquals(policyViolation.getType(), clonedPolicyViolation.getType()); + Assert.assertEquals(policyViolation.getTimestamp(), clonedPolicyViolation.getTimestamp()); + Assert.assertEquals(policyViolation.getAnalysis().isSuppressed(), clonedPolicyViolation.getAnalysis().isSuppressed()); + Assert.assertEquals(policyViolation.getAnalysis().getAnalysisState(), clonedPolicyViolation.getAnalysis().getAnalysisState()); + Assert.assertEquals(policyViolation.getAnalysis().getAnalysisComments().get(0).getComment(), clonedPolicyViolation.getAnalysis().getAnalysisComments().get(0).getComment()); + Assert.assertEquals(policyViolation.getAnalysis().getAnalysisComments().get(0).getCommenter(), clonedPolicyViolation.getAnalysis().getAnalysisComments().get(0).getCommenter()); + Assert.assertEquals(policyViolation.getAnalysis().getAnalysisComments().get(0).getTimestamp(), clonedPolicyViolation.getAnalysis().getAnalysisComments().get(0).getTimestamp()); + Assert.assertEquals(policyViolation.getComponent().getId(), clonedPolicyViolation.getComponent().getId()); + Assert.assertEquals(policyViolation.getComponent().getName(), clonedPolicyViolation.getComponent().getName()); + Assert.assertEquals(policyViolation.getComponent().getCopyright(), clonedPolicyViolation.getComponent().getCopyright()); + Assert.assertEquals(policyViolation.getComponent().getVersion(), clonedPolicyViolation.getComponent().getVersion()); + } + } \ No newline at end of file