diff --git a/src/main/java/io/github/pmckeown/dependencytrack/AbstractDependencyTrackMojo.java b/src/main/java/io/github/pmckeown/dependencytrack/AbstractDependencyTrackMojo.java
index b24a98fe..a7edb2a8 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/AbstractDependencyTrackMojo.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/AbstractDependencyTrackMojo.java
@@ -1,8 +1,9 @@
package io.github.pmckeown.dependencytrack;
-import io.github.pmckeown.util.Logger;
-import kong.unirest.Unirest;
-import kong.unirest.jackson.JacksonObjectMapper;
+import static io.github.pmckeown.dependencytrack.ObjectMapperFactory.relaxedObjectMapper;
+import static kong.unirest.HeaderNames.ACCEPT;
+import static kong.unirest.HeaderNames.ACCEPT_ENCODING;
+
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.AbstractMojo;
@@ -10,159 +11,155 @@
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
-import static io.github.pmckeown.dependencytrack.ObjectMapperFactory.relaxedObjectMapper;
-import static kong.unirest.HeaderNames.ACCEPT;
-import static kong.unirest.HeaderNames.ACCEPT_ENCODING;
+import io.github.pmckeown.util.Logger;
+import kong.unirest.Unirest;
+import kong.unirest.jackson.JacksonObjectMapper;
/**
- * Base class for Mojos in this project.
- *
- * Provides common configuration options:
+ * Base class for Mojos in this project. Provides common configuration options:
*
- * - projectName
- * - projectVersion
- * - dependencyTrackBaseUrl
- * - apiKey
- * - failOnError
- * - skip
- * - verifySsl
+ * - projectName
+ * - projectVersion
+ * - dependencyTrackBaseUrl
+ * - apiKey
+ * - failOnError
+ * - skip
+ * - verifySsl
*
*
* @author Paul McKeown
*/
public abstract class AbstractDependencyTrackMojo extends AbstractMojo {
- @Parameter(required = true, defaultValue = "${project.artifactId}", property = "dependency-track.projectName")
- protected String projectName;
-
- @Parameter(required = true, defaultValue = "${project.version}", property = "dependency-track.projectVersion")
- protected String projectVersion;
-
- @Parameter(required = true, property = "dependency-track.dependencyTrackBaseUrl")
- private String dependencyTrackBaseUrl;
-
- @Parameter(required = true, property = "dependency-track.apiKey")
- private String apiKey;
-
- @Parameter(defaultValue = "false", property = "dependency-track.failOnError")
- private boolean failOnError;
-
- /**
- * Set this to 'true' to bypass dependencyTrack plugin
- * It's not a real boolean as it can have more than 2 values:
- *
- * true
: will skip as usual
- * releases
: will skip if current version of the project is a release
- * snapshots
: will skip if current version of the project is a snapshot
- * - any other values will be considered as
false
- *
- */
- @Parameter(defaultValue = "false", property = "dependency-track.skip", alias = "dependency-track.skip")
- private String skip = Boolean.FALSE.toString();
-
- @Parameter(defaultValue = "true", property = "dependency-track.verifySsl")
- private boolean verifySsl;
-
- @Parameter
- private PollingConfig pollingConfig;
-
- protected Logger logger;
-
- protected CommonConfig commonConfig;
-
- protected AbstractDependencyTrackMojo(CommonConfig commonConfig, Logger logger) {
- this.logger = logger;
- this.commonConfig = commonConfig;
- }
-
- /**
- * Initialises the {@link Logger} and {@link CommonConfig} instances that were injected by the SISU inversion of
- * control container (using Guice under the hood) by providing the data provided by the Plexus IOC container.
- *
- * Then performs the action defined by the subclass.
- */
- @Override
- public final void execute() throws MojoExecutionException, MojoFailureException {
- // Set up Mojo environment
- this.logger.setLog(getLog());
- this.commonConfig.setProjectName(projectName);
- this.commonConfig.setProjectVersion(projectVersion);
- this.commonConfig.setDependencyTrackBaseUrl(dependencyTrackBaseUrl);
- this.commonConfig.setApiKey(apiKey);
- this.commonConfig.setPollingConfig(this.pollingConfig != null ? this.pollingConfig : PollingConfig.defaults());
-
- // Configure Unirest with additional user-supplied configuration
- configureUnirest();
-
- // Perform the requested action
- if (getSkip()) {
- logger.info("dependency-track.skip = true: Skipping analysis.");
- return;
- }
- this.performAction();
- }
-
- /**
- * Template method to be implemented by subclasses.
- *
- * @throws MojoExecutionException when an error is encountered during Mojo execution
- * @throws MojoFailureException when the Mojo fails
- */
- protected abstract void performAction() throws MojoExecutionException, MojoFailureException;
-
- public void setProjectName(String projectName) {
- this.projectName = projectName;
- }
-
- public void setProjectVersion(String projectVersion) {
- this.projectVersion = projectVersion;
- }
-
- public void setDependencyTrackBaseUrl(String url) {
- this.dependencyTrackBaseUrl = url;
- }
-
- public void setApiKey(String apiKey) {
- this.apiKey = apiKey;
- }
-
- public void setFailOnError(boolean fail) {
- this.failOnError = fail;
- }
-
- public void setVerifySsl(boolean verifySsl) {
- this.verifySsl = verifySsl;
- }
-
- public void setSkip(String skip) {
- this.skip = skip;
- }
-
- public void setPollingConfig(PollingConfig commonConfig) {
- this.pollingConfig = commonConfig;
- }
-
- protected void handleFailure(String message) throws MojoFailureException {
- getLog().error(message);
- if (failOnError) {
- throw new MojoFailureException(message);
- }
- }
-
- protected void handleFailure(String message, Throwable ex) throws MojoExecutionException {
- getLog().debug(message, ex);
- if (failOnError) {
- throw new MojoExecutionException(message);
- }
- }
-
- private boolean getSkip() {
- return Boolean.parseBoolean(skip)
- || ("releases".equals(skip) && !ArtifactUtils.isSnapshot(projectVersion))
- || ("snapshots".equals(skip) && ArtifactUtils.isSnapshot(projectVersion));
- }
-
- /**
+ @Parameter(required = true, defaultValue = "${project.artifactId}", property = "dependency-track.projectName")
+ protected String projectName;
+
+ @Parameter(required = true, defaultValue = "${project.version}", property = "dependency-track.projectVersion")
+ protected String projectVersion;
+
+ @Parameter(required = true, property = "dependency-track.dependencyTrackBaseUrl")
+ private String dependencyTrackBaseUrl;
+
+ @Parameter(required = true, property = "dependency-track.apiKey")
+ private String apiKey;
+
+ @Parameter(defaultValue = "false", property = "dependency-track.failOnError")
+ private boolean failOnError;
+
+ /**
+ * Set this to 'true' to bypass dependencyTrack plugin It's not a real boolean as it can have more than 2 values:
+ *
+ * true
: will skip as usual
+ * releases
: will skip if current version of the project is a release
+ * snapshots
: will skip if current version of the project is a snapshot
+ * - any other values will be considered as
false
+ *
+ */
+ @Parameter(defaultValue = "false", property = "dependency-track.skip", alias = "dependency-track.skip")
+ private String skip = Boolean.FALSE.toString();
+
+ @Parameter(defaultValue = "true", property = "dependency-track.verifySsl")
+ private boolean verifySsl;
+
+ @Parameter
+ private PollingConfig pollingConfig;
+
+ protected Logger logger;
+
+ protected CommonConfig commonConfig;
+
+ protected AbstractDependencyTrackMojo(final CommonConfig commonConfig, final Logger logger) {
+ this.logger = logger;
+ this.commonConfig = commonConfig;
+ }
+
+ /**
+ * Initialises the {@link Logger} and {@link CommonConfig} instances that were injected by the SISU inversion of
+ * control container (using Guice under the hood) by providing the data provided by the Plexus IOC container. Then
+ * performs the action defined by the subclass.
+ */
+ @Override
+ public final void execute() throws MojoExecutionException, MojoFailureException {
+ // Set up Mojo environment
+ logger.setLog(getLog());
+ commonConfig.setProjectName(projectName);
+ commonConfig.setProjectVersion(projectVersion);
+ commonConfig.setDependencyTrackBaseUrl(dependencyTrackBaseUrl);
+ commonConfig.setApiKey(apiKey);
+ commonConfig.setPollingConfig(pollingConfig != null ? pollingConfig : PollingConfig.defaults());
+
+ // Configure Unirest with additional user-supplied configuration
+ configureUnirest();
+
+ // Perform the requested action
+ if (getSkip()) {
+ logger.info("dependency-track.skip = true: Skipping analysis.");
+ return;
+ }
+ performAction();
+ }
+
+ /**
+ * Template method to be implemented by subclasses.
+ *
+ * @throws MojoExecutionException when an error is encountered during Mojo execution
+ * @throws MojoFailureException when the Mojo fails
+ */
+ protected abstract void performAction() throws MojoExecutionException, MojoFailureException;
+
+ public void setProjectName(final String projectName) {
+ this.projectName = projectName;
+ }
+
+ public void setProjectVersion(final String projectVersion) {
+ this.projectVersion = projectVersion;
+ }
+
+ public void setDependencyTrackBaseUrl(final String url) {
+ dependencyTrackBaseUrl = url;
+ }
+
+ public void setApiKey(final String apiKey) {
+ this.apiKey = apiKey;
+ }
+
+ public void setFailOnError(final boolean fail) {
+ failOnError = fail;
+ }
+
+ public void setVerifySsl(final boolean verifySsl) {
+ this.verifySsl = verifySsl;
+ }
+
+ public void setSkip(final String skip) {
+ this.skip = skip;
+ }
+
+ public void setPollingConfig(final PollingConfig commonConfig) {
+ pollingConfig = commonConfig;
+ }
+
+ protected void handleFailure(final String message) throws MojoFailureException {
+ getLog().error(message);
+ if (failOnError) {
+ throw new MojoFailureException(message);
+ }
+ }
+
+ protected void handleFailure(final String message, final Throwable ex) throws MojoExecutionException {
+ getLog().debug(message, ex);
+ if (failOnError) {
+ throw new MojoExecutionException(message);
+ }
+ }
+
+ private boolean getSkip() {
+ return Boolean.parseBoolean(skip) || ("releases".equals(skip) && !ArtifactUtils.isSnapshot(projectVersion))
+ || ("snapshots".equals(skip) && ArtifactUtils.isSnapshot(projectVersion));
+ }
+
+
+ /**
* Unirest is configured globally using a static `Unirest.config()` method. Doing so here allows for user-supplied
* configuration.
*/
@@ -173,10 +170,10 @@ private void configureUnirest() {
.setDefaultHeader(ACCEPT, "application/json")
.verifySsl(this.verifySsl);
- // Debug all Unirest config
- logger.debug("Unirest Configuration: %s", ToStringBuilder.reflectionToString(Unirest.config()));
+ // Debug all Unirest config
+ logger.debug("Unirest Configuration: %s", ToStringBuilder.reflectionToString(Unirest.config()));
- // Info print user specified
- logger.info("SSL Verification enabled: %b", this.verifySsl);
- }
+ // Info print user specified
+ logger.info("SSL Verification enabled: %b", verifySsl);
+ }
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/ResourceConstants.java b/src/main/java/io/github/pmckeown/dependencytrack/ResourceConstants.java
index 723a67a5..b272b76b 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/ResourceConstants.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/ResourceConstants.java
@@ -7,16 +7,17 @@
*/
public final class ResourceConstants {
- public static final String V1_BOM = "/api/v1/bom";
- public static final String V1_BOM_TOKEN_UUID = "/api/v1/bom/token/{uuid}";
- public static final String V1_PROJECT = "/api/v1/project?limit=1000000&offset=0";
- public static final String V1_PROJECT_UUID = "/api/v1/project/{uuid}";
- public static final String V1_FINDING_PROJECT_UUID = "/api/v1/finding/project/{uuid}";
- public static final String V1_METRICS_PROJECT_UUID_CURRENT = "/api/v1/metrics/project/{uuid}/current";
- public static final String V1_METRICS_PROJECT_UUID_REFRESH = "/api/v1/metrics/project/{uuid}/refresh";
- public static final String V1_POLICY_VIOLATION_PROJECT_UUID = "/api/v1/violation/project/{uuid}";
+ public static final String V1_ANALYSIS = "/api/v1/analysis";
+ public static final String V1_BOM = "/api/v1/bom";
+ public static final String V1_BOM_TOKEN_UUID = "/api/v1/bom/token/{uuid}";
+ public static final String V1_PROJECT = "/api/v1/project?limit=1000000&offset=0";
+ public static final String V1_PROJECT_UUID = "/api/v1/project/{uuid}";
+ public static final String V1_FINDING_PROJECT_UUID = "/api/v1/finding/project/{uuid}";
+ public static final String V1_METRICS_PROJECT_UUID_CURRENT = "/api/v1/metrics/project/{uuid}/current";
+ public static final String V1_METRICS_PROJECT_UUID_REFRESH = "/api/v1/metrics/project/{uuid}/refresh";
+ public static final String V1_POLICY_VIOLATION_PROJECT_UUID = "/api/v1/violation/project/{uuid}";
- private ResourceConstants() {
- // Constants file
- }
+ private ResourceConstants() {
+ // Constants file
+ }
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/finding/Analysis.java b/src/main/java/io/github/pmckeown/dependencytrack/finding/Analysis.java
index fb4303eb..c298f108 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/finding/Analysis.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/finding/Analysis.java
@@ -1,43 +1,67 @@
package io.github.pmckeown.dependencytrack.finding;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlElement;
+
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
-import javax.xml.bind.annotation.XmlElement;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
public class Analysis {
- public enum State {
- NOT_AFFECTED,
- FALSE_POSITIVE,
- IN_TRIAGE,
- EXPLOITABLE,
- NOT_SET
- }
-
- private boolean isSuppressed;
- private State state;
-
- @JsonCreator
- public Analysis(@JsonProperty("isSuppressed") boolean isSuppressed, @JsonProperty("state") State state) {
- this.isSuppressed = isSuppressed;
- this.state = state;
- }
-
- @XmlElement
- public boolean isSuppressed() {
- return isSuppressed;
- }
-
- @XmlElement
- public State getState() {
- return state;
- }
-
- @Override
- public String toString() {
- return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
- }
+ public enum AnalysisState {
+ NOT_AFFECTED,
+ FALSE_POSITIVE,
+ IN_TRIAGE,
+ EXPLOITABLE,
+ NOT_SET,
+ RESOLVED
+ }
+
+ public enum AnalysisJustification {
+ CODE_NOT_PRESENT,
+ CODE_NOT_REACHABLE,
+ REQUIRES_CONFIGURATION,
+ REQUIRES_DEPENDENCY,
+ REQUIRES_ENVIRONMENT,
+ PROTECTED_BY_COMPILER,
+ PROTECTED_AT_RUNTIME,
+ PROTECTED_AT_PERIMETER,
+ PROTECTED_BY_MITIGATING_CONTROL,
+ NOT_SET
+ }
+
+ private boolean isSuppressed;
+ private AnalysisState analysisState;
+ private AnalysisJustification analysisJustification;
+
+ @JsonCreator
+ public Analysis(@JsonProperty("isSuppressed") final boolean isSuppressed,
+ @JsonProperty("analysisState") final AnalysisState analysisState,
+ @JsonProperty("analysisJustification") final AnalysisJustification analysisJustification) {
+ this.isSuppressed = isSuppressed;
+ this.analysisState = analysisState;
+ this.analysisJustification = analysisJustification;
+ }
+
+ @XmlElement
+ public boolean isSuppressed() {
+ return isSuppressed;
+ }
+
+ @XmlElement
+ public AnalysisState getAnalysisState() {
+ return analysisState;
+ }
+
+ @XmlElement
+ public AnalysisJustification getAnalysisJustification() {
+ return analysisJustification;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+ }
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/finding/AnalysisDecisionRequest.java b/src/main/java/io/github/pmckeown/dependencytrack/finding/AnalysisDecisionRequest.java
new file mode 100644
index 00000000..d003b55d
--- /dev/null
+++ b/src/main/java/io/github/pmckeown/dependencytrack/finding/AnalysisDecisionRequest.java
@@ -0,0 +1,66 @@
+package io.github.pmckeown.dependencytrack.finding;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisJustification;
+import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisState;
+
+public class AnalysisDecisionRequest {
+
+ /*
+ * For project, component and vulnerability the according UUID must be used
+ */
+ private String project;
+ private String component;
+ private String vulnerability;
+ private AnalysisState analysisState;
+ private AnalysisJustification analysisJustification;
+ private String analysisDetails;
+ private boolean isSuppressed;
+
+ public AnalysisDecisionRequest(final String project, final String component, final String vulnerability,
+ final AnalysisState analysisState, final AnalysisJustification analysisJustification,
+ final String analysisDetails, final boolean isSuppressed) {
+ this.project = project;
+ this.component = component;
+ this.vulnerability = vulnerability;
+ this.analysisState = analysisState;
+ this.isSuppressed = isSuppressed;
+ this.analysisJustification = analysisJustification;
+ this.analysisDetails = analysisDetails;
+ }
+
+ public String getProject() {
+ return project;
+ }
+
+ public String getComponent() {
+ return component;
+ }
+
+ public String getVulnerability() {
+ return vulnerability;
+ }
+
+ public AnalysisState getAnalysisState() {
+ return analysisState;
+ }
+
+ public AnalysisJustification getAnalysisJustification() {
+ return analysisJustification;
+ }
+
+ public String getAnalysisDetails() {
+ return analysisDetails;
+ }
+
+ public boolean isSuppressed() {
+ return isSuppressed;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+ }
+}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAction.java b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAction.java
index c3cfa525..7b711fa1 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAction.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAction.java
@@ -1,50 +1,103 @@
package io.github.pmckeown.dependencytrack.finding;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
import io.github.pmckeown.dependencytrack.DependencyTrackException;
import io.github.pmckeown.dependencytrack.Response;
+import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisJustification;
+import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisState;
import io.github.pmckeown.dependencytrack.project.Project;
import io.github.pmckeown.util.Logger;
import kong.unirest.UnirestException;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
@Singleton
public class FindingsAction {
- private FindingsClient findingClient;
-
- private Logger logger;
-
- @Inject
- public FindingsAction(FindingsClient findingClient, Logger logger) {
- this.findingClient = findingClient;
- this.logger = logger;
- }
-
- List getFindings(Project project) throws DependencyTrackException {
- logger.info("Getting findings for project %s-%s", project.getName(), project.getVersion());
-
- try {
- Response> response = findingClient.getFindingsForProject(project);
- Optional> body = response.getBody();
- if (response.isSuccess()) {
- if (body.isPresent()) {
- return body.get();
- } else {
- logger.info("No findings available for project %s-%s", project.getName(),
- project.getVersion());
- return Collections.emptyList();
- }
- } else {
- throw new DependencyTrackException("Error received from server");
- }
- } catch (UnirestException ex) {
- logger.error(ex.getMessage());
- throw new DependencyTrackException(ex.getMessage());
- }
- }
+ private FindingsClient findingClient;
+
+ private Logger logger;
+
+ @Inject
+ public FindingsAction(final FindingsClient findingClient, final Logger logger) {
+ this.findingClient = findingClient;
+ this.logger = logger;
+ }
+
+ public List getFindings(final Project project) throws DependencyTrackException {
+ logger.info("Getting findings for project %s-%s", project.getName(), project.getVersion());
+
+ try {
+ final Response> response = findingClient.getFindingsForProject(project);
+ final Optional> body = response.getBody();
+ if (response.isSuccess()) {
+ if (body.isPresent()) {
+ return body.get();
+ } else {
+ logger.info("No findings available for project %s-%s", project.getName(), project.getVersion());
+ return Collections.emptyList();
+ }
+ } else {
+ throw new DependencyTrackException("Error received from server");
+ }
+ } catch (final UnirestException ex) {
+ logger.error(ex.getMessage());
+ throw new DependencyTrackException(ex.getMessage());
+ }
+ }
+
+ public void suppressFindings(final Project project, final List suppressions)
+ throws DependencyTrackException {
+ logger.info("Suppression list is present");
+ final List findings = getFindings(project);
+
+ if (findings.isEmpty()) {
+ logger.info("Skipping suppression");
+ } else {
+ suppressions.forEach((suppression) -> {
+ final List relevantFindings = findings.stream().filter(
+ finding -> Objects.equals(suppression.getCve(), finding.getVulnerability().getVulnId()))
+ .collect(Collectors.toList());
+ if (relevantFindings.isEmpty()) {
+ logger.debug("Findings do not include vulnerability %s",
+ Objects.requireNonNullElse(suppression.getCve(), "?"));
+ } else {
+ logger.info("Suppressing vulnerability %s", suppression.getCve());
+ logger.debug("%s", suppression);
+
+ final AnalysisState state = parseState(suppression.getState());
+ relevantFindings.forEach(finding -> findingClient.recordNewAnalysisDecision(
+ new AnalysisDecisionRequest(project.getUuid(), finding.getComponent().getUuid(),
+ finding.getVulnerability().getUuid(), parseState(suppression.getState()),
+ parseJustification(suppression.getJustification(), state), suppression.getDetails(),
+ true)));
+ }
+ });
+ }
+ }
+
+ private AnalysisState parseState(final String state) {
+ try {
+ return AnalysisState.valueOf(Objects.toString(state, "").toUpperCase());
+ } catch (final RuntimeException ex) {
+ return AnalysisState.NOT_AFFECTED;
+ }
+ }
+
+ private AnalysisJustification parseJustification(final String justification, final AnalysisState state) {
+ try {
+ return AnalysisState.NOT_AFFECTED.equals(state)
+ ? AnalysisJustification.valueOf(Objects.toString(justification, "").toUpperCase())
+ : AnalysisJustification.NOT_SET;
+ } catch (final RuntimeException ex) {
+ return AnalysisJustification.NOT_SET;
+ }
+ }
+
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyser.java b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyser.java
index 6288eb60..7f8aeec1 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyser.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyser.java
@@ -1,74 +1,76 @@
package io.github.pmckeown.dependencytrack.finding;
-import io.github.pmckeown.dependencytrack.Constants;
-import io.github.pmckeown.util.Logger;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.List;
-
import static io.github.pmckeown.dependencytrack.finding.Severity.CRITICAL;
import static io.github.pmckeown.dependencytrack.finding.Severity.HIGH;
-import static io.github.pmckeown.dependencytrack.finding.Severity.MEDIUM;
import static io.github.pmckeown.dependencytrack.finding.Severity.LOW;
+import static io.github.pmckeown.dependencytrack.finding.Severity.MEDIUM;
import static io.github.pmckeown.dependencytrack.finding.Severity.UNASSIGNED;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.github.pmckeown.dependencytrack.Constants;
+import io.github.pmckeown.util.Logger;
+
@Singleton
public class FindingsAnalyser {
- private static final String ERROR_TEMPLATE = "Number of %s issues [%d] exceeds the maximum allowed [%d]";
-
- private Logger logger;
-
- @Inject
- public FindingsAnalyser(Logger logger) {
- this.logger = logger;
- }
-
- boolean doNumberOfFindingsBreachPolicy(List findings, FindingThresholds findingThresholds) {
- logger.info("Comparing findings against defined thresholds");
-
- if (findingThresholds == null) {
- return false;
- }
-
- boolean policyBreached = false;
-
- long critical = getCount(findings, CRITICAL);
- long high = getCount(findings, HIGH);
- long medium = getCount(findings, MEDIUM);
- long low = getCount(findings, LOW);
- long unassigned = getCount(findings, UNASSIGNED);
-
- if (findingThresholds.getCritical() != null && critical > findingThresholds.getCritical()) {
- logger.warn(ERROR_TEMPLATE, Constants.CRITICAL, critical, findingThresholds.getCritical());
- policyBreached = true;
- }
-
- if (findingThresholds.getHigh() != null && high > findingThresholds.getHigh()) {
- logger.warn(ERROR_TEMPLATE, Constants.HIGH, high, findingThresholds.getHigh());
- policyBreached = true;
- }
-
- if (findingThresholds.getMedium() != null && medium > findingThresholds.getMedium()) {
- logger.warn(ERROR_TEMPLATE, Constants.MEDIUM, medium, findingThresholds.getMedium());
- policyBreached = true;
- }
-
- if (findingThresholds.getLow() != null && low > findingThresholds.getLow()) {
- logger.warn(ERROR_TEMPLATE, Constants.LOW, low, findingThresholds.getLow());
- policyBreached = true;
- }
- if (findingThresholds.getUnassigned() != null && unassigned > findingThresholds.getUnassigned()) {
- logger.warn(ERROR_TEMPLATE, Constants.UNASSIGNED, unassigned, findingThresholds.getUnassigned());
- policyBreached = true;
- }
-
- return policyBreached;
- }
-
- private long getCount(List findings, Severity severity) {
- return findings.stream().filter(f -> f.getVulnerability().getSeverity() == severity
- && !f.getAnalysis().isSuppressed()).count();
- }
+ private static final String ERROR_TEMPLATE = "Number of %s issues [%d] exceeds the maximum allowed [%d]";
+
+ private Logger logger;
+
+ @Inject
+ public FindingsAnalyser(final Logger logger) {
+ this.logger = logger;
+ }
+
+ public boolean doNumberOfFindingsBreachPolicy(final List findings,
+ final FindingThresholds findingThresholds) {
+ logger.info("Comparing findings against defined thresholds");
+
+ if (findingThresholds == null) {
+ return false;
+ }
+
+ boolean policyBreached = false;
+
+ final long critical = getCount(findings, CRITICAL);
+ final long high = getCount(findings, HIGH);
+ final long medium = getCount(findings, MEDIUM);
+ final long low = getCount(findings, LOW);
+ final long unassigned = getCount(findings, UNASSIGNED);
+
+ if (findingThresholds.getCritical() != null && critical > findingThresholds.getCritical()) {
+ logger.warn(ERROR_TEMPLATE, Constants.CRITICAL, critical, findingThresholds.getCritical());
+ policyBreached = true;
+ }
+
+ if (findingThresholds.getHigh() != null && high > findingThresholds.getHigh()) {
+ logger.warn(ERROR_TEMPLATE, Constants.HIGH, high, findingThresholds.getHigh());
+ policyBreached = true;
+ }
+
+ if (findingThresholds.getMedium() != null && medium > findingThresholds.getMedium()) {
+ logger.warn(ERROR_TEMPLATE, Constants.MEDIUM, medium, findingThresholds.getMedium());
+ policyBreached = true;
+ }
+
+ if (findingThresholds.getLow() != null && low > findingThresholds.getLow()) {
+ logger.warn(ERROR_TEMPLATE, Constants.LOW, low, findingThresholds.getLow());
+ policyBreached = true;
+ }
+ if (findingThresholds.getUnassigned() != null && unassigned > findingThresholds.getUnassigned()) {
+ logger.warn(ERROR_TEMPLATE, Constants.UNASSIGNED, unassigned, findingThresholds.getUnassigned());
+ policyBreached = true;
+ }
+
+ return policyBreached;
+ }
+
+ private long getCount(final List findings, final Severity severity) {
+ return findings.stream()
+ .filter(f -> f.getVulnerability().getSeverity() == severity && !f.getAnalysis().isSuppressed()).count();
+ }
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsClient.java b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsClient.java
index bc640aaa..cab89bb3 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsClient.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsClient.java
@@ -1,50 +1,66 @@
package io.github.pmckeown.dependencytrack.finding;
+import static io.github.pmckeown.dependencytrack.ResourceConstants.V1_ANALYSIS;
+import static io.github.pmckeown.dependencytrack.ResourceConstants.V1_FINDING_PROJECT_UUID;
+import static kong.unirest.HeaderNames.CONTENT_TYPE;
+import static kong.unirest.Unirest.get;
+
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
import io.github.pmckeown.dependencytrack.CommonConfig;
import io.github.pmckeown.dependencytrack.Response;
import io.github.pmckeown.dependencytrack.project.Project;
import io.github.pmckeown.util.Logger;
import kong.unirest.GenericType;
import kong.unirest.HttpResponse;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.List;
-import java.util.Optional;
-
-import static io.github.pmckeown.dependencytrack.ResourceConstants.V1_FINDING_PROJECT_UUID;
-import static kong.unirest.Unirest.get;
+import kong.unirest.Unirest;
@Singleton
class FindingsClient {
- private CommonConfig commonConfig;
+ private CommonConfig commonConfig;
+
+ private Logger logger;
+
+ @Inject
+ FindingsClient(final CommonConfig commonConfig, final Logger logger) {
+ this.commonConfig = commonConfig;
+ this.logger = logger;
+ }
- private Logger logger;
+ Response> getFindingsForProject(final Project project) {
+ logger.debug("Getting findings for project: %s-%s", project.getName(), project.getVersion());
+ final HttpResponse> httpResponse = get(
+ commonConfig.getDependencyTrackBaseUrl() + V1_FINDING_PROJECT_UUID)
+ .header("X-Api-Key", commonConfig.getApiKey()).routeParam("uuid", project.getUuid())
+ .asObject(new GenericType>() {
+ });
- @Inject
- FindingsClient(CommonConfig commonConfig, Logger logger) {
- this.commonConfig = commonConfig;
- this.logger = logger;
- }
+ Optional> body;
+ if (httpResponse.isSuccess()) {
+ body = Optional.of(httpResponse.getBody());
+ } else {
+ body = Optional.empty();
+ }
+ return new Response<>(httpResponse.getStatus(), httpResponse.getStatusText(), httpResponse.isSuccess(), body);
+ }
- Response> getFindingsForProject(Project project) {
- logger.debug("Getting findings for project: %s-%s", project.getName(), project.getVersion());
- final HttpResponse> httpResponse = get(
- commonConfig.getDependencyTrackBaseUrl() + V1_FINDING_PROJECT_UUID)
- .header("X-Api-Key", commonConfig.getApiKey())
- .routeParam("uuid", project.getUuid())
- .asObject(new GenericType>(){});
+ Response recordNewAnalysisDecision(final AnalysisDecisionRequest analysisDecision) {
+ logger.debug("Recording analysis decision: %s", analysisDecision.toString());
+ final HttpResponse> httpResponse = Unirest.put(commonConfig.getDependencyTrackBaseUrl() + V1_ANALYSIS)
+ .header(CONTENT_TYPE, "application/json").header("X-Api-Key", commonConfig.getApiKey())
+ .body(analysisDecision).asEmpty();
- Optional> body;
- if (httpResponse.isSuccess()) {
- body = Optional.of(httpResponse.getBody());
- } else {
- body = Optional.empty();
- }
+ if (!httpResponse.isSuccess()) {
+ logger.error("An error occurred while suppressing vulnerability: %d-%s", httpResponse.getStatus(),
+ httpResponse.getStatusText());
+ }
- return new Response<>(httpResponse.getStatus(), httpResponse.getStatusText(),
- httpResponse.isSuccess(), body);
- }
+ return new Response<>(httpResponse.getStatus(), httpResponse.getStatusText(), httpResponse.isSuccess());
+ }
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinter.java b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinter.java
index 57c58954..e7ae0600 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinter.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinter.java
@@ -1,75 +1,77 @@
package io.github.pmckeown.dependencytrack.finding;
-import io.github.pmckeown.dependencytrack.project.Project;
-import io.github.pmckeown.util.Logger;
-import org.apache.commons.lang3.StringUtils;
+import static io.github.pmckeown.dependencytrack.Constants.DELIMITER;
+import static java.util.stream.Collectors.toList;
+import static org.apache.commons.lang3.StringUtils.joinWith;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
-import static io.github.pmckeown.dependencytrack.Constants.DELIMITER;
-import static java.util.stream.Collectors.toList;
-import static org.apache.commons.lang3.StringUtils.joinWith;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.apache.commons.lang3.StringUtils;
+
+import io.github.pmckeown.dependencytrack.project.Project;
+import io.github.pmckeown.util.Logger;
@Singleton
class FindingsPrinter {
- private Logger logger;
+ private Logger logger;
- @Inject
- public FindingsPrinter(Logger logger) {
- this.logger = logger;
- }
+ @Inject
+ public FindingsPrinter(final Logger logger) {
+ this.logger = logger;
+ }
- void printFindings(Project project, List findings) {
- if (findings == null || findings.isEmpty()) {
- logger.info("No findings were retrieved for project: %s", project.getName());
- return;
- }
- logger.info("%d finding(s) were retrieved for project: %s", findings.size(), project.getName());
- logger.info("Printing findings for project %s-%s", project.getName(), project.getVersion());
- findings.forEach(finding -> {
- Vulnerability vulnerability = finding.getVulnerability();
- logger.info(DELIMITER);
- logger.info("%s (%s)", vulnerability.getVulnId(), vulnerability.getSource());
- logger.info("%s: %s", vulnerability.getSeverity().name(), getComponentDetails(finding));
- logger.info(""); // Spacer
- List wrappedDescriptionParts = splitString(vulnerability.getDescription());
- if (wrappedDescriptionParts != null && !wrappedDescriptionParts.isEmpty()) {
- wrappedDescriptionParts.forEach(s -> logger.info(s));
- }
- if (finding.getAnalysis().isSuppressed()) {
- logger.info("");
- logger.info("Suppressed - %s", finding.getAnalysis().getState().name());
- }
- });
- }
+ void printFindings(final Project project, final List findings) {
+ if (findings == null || findings.isEmpty()) {
+ logger.info("No findings were retrieved for project: %s", project.getName());
+ return;
+ }
+ logger.info("%d finding(s) were retrieved for project: %s", findings.size(), project.getName());
+ logger.info("Printing findings for project %s-%s", project.getName(), project.getVersion());
+ findings.forEach(finding -> {
+ final Vulnerability vulnerability = finding.getVulnerability();
+ logger.info(DELIMITER);
+ logger.info("%s (%s)", vulnerability.getVulnId(), vulnerability.getSource());
+ logger.info("%s: %s", vulnerability.getSeverity().name(), getComponentDetails(finding));
+ logger.info(""); // Spacer
+ final List wrappedDescriptionParts = splitString(vulnerability.getDescription());
+ if (wrappedDescriptionParts != null && !wrappedDescriptionParts.isEmpty()) {
+ wrappedDescriptionParts.forEach(s -> logger.info(s));
+ }
+ if (finding.getAnalysis().isSuppressed()) {
+ logger.info("");
+ logger.info("Suppressed - %s", finding.getAnalysis().getAnalysisState().name());
+ }
+ });
+ }
- int getPrintWidth() {
- // We wrap printed lines to match the delimiter string width
- return DELIMITER.length();
- }
+ int getPrintWidth() {
+ // We wrap printed lines to match the delimiter string width
+ return DELIMITER.length();
+ }
- private List splitString(final String string) {
- if (StringUtils.isEmpty(string)) {
- return Collections.emptyList();
- }
+ private List splitString(final String string) {
+ if (StringUtils.isEmpty(string)) {
+ return Collections.emptyList();
+ }
- String percentEscaped = StringUtils.replace(string, "%", "%%");
- String cleaned = StringUtils.replace(percentEscaped, "\n", "");
- int chunkSize = getPrintWidth();
- final int numberOfChunks = (cleaned.length() + chunkSize - 1) / chunkSize;
- return IntStream.range(0, numberOfChunks)
- .mapToObj(i -> cleaned.substring(i * chunkSize, Math.min((i + 1) * chunkSize, cleaned.length())))
- .collect(toList());
- }
+ final String percentEscaped = StringUtils.replace(string, "%", "%%");
+ final String cleaned = StringUtils.replace(percentEscaped, "\n", "");
+ final int chunkSize = getPrintWidth();
+ final int numberOfChunks = (cleaned.length() + chunkSize - 1) / chunkSize;
+ return IntStream.range(0, numberOfChunks)
+ .mapToObj(i -> cleaned.substring(i * chunkSize, Math.min((i + 1) * chunkSize, cleaned.length())))
+ .collect(toList());
+ }
- private String getComponentDetails(Finding finding) {
- Component component = finding.getComponent();
- return joinWith(":", component.getGroup(), component.getName(), component.getVersion());
- }
+ private String getComponentDetails(final Finding finding) {
+ final Component component = finding.getComponent();
+ return joinWith(":", component.getGroup(), component.getName(), component.getVersion());
+ }
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/finding/Suppression.java b/src/main/java/io/github/pmckeown/dependencytrack/finding/Suppression.java
new file mode 100644
index 00000000..dc66a695
--- /dev/null
+++ b/src/main/java/io/github/pmckeown/dependencytrack/finding/Suppression.java
@@ -0,0 +1,50 @@
+package io.github.pmckeown.dependencytrack.finding;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+public class Suppression {
+
+ private String cve;
+ private String state = "NOT_AFFECTED";
+ private String justification = "NOT_SET";
+ private String details;
+
+ public String getCve() {
+ return cve;
+ }
+
+ public void setCve(final String cve) {
+ this.cve = cve;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(final String state) {
+ this.state = state;
+ }
+
+ public String getJustification() {
+ return justification;
+ }
+
+ public void setJustification(final String justification) {
+ this.justification = justification;
+ }
+
+ public String getDetails() {
+ return details;
+ }
+
+ public void setDetails(final String details) {
+ this.details = details;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+ }
+
+}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomMojo.java b/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomMojo.java
index f48e33d3..5999ec0b 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomMojo.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomMojo.java
@@ -1,13 +1,10 @@
package io.github.pmckeown.dependencytrack.upload;
-import io.github.pmckeown.dependencytrack.AbstractDependencyTrackMojo;
-import io.github.pmckeown.dependencytrack.CommonConfig;
-import io.github.pmckeown.dependencytrack.DependencyTrackException;
-import io.github.pmckeown.dependencytrack.metrics.MetricsAction;
-import io.github.pmckeown.dependencytrack.project.Project;
-import io.github.pmckeown.dependencytrack.project.ProjectAction;
-import io.github.pmckeown.dependencytrack.project.UpdateRequest;
-import io.github.pmckeown.util.Logger;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@@ -16,18 +13,24 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
-import javax.inject.Inject;
+import io.github.pmckeown.dependencytrack.AbstractDependencyTrackMojo;
+import io.github.pmckeown.dependencytrack.CommonConfig;
+import io.github.pmckeown.dependencytrack.DependencyTrackException;
+import io.github.pmckeown.dependencytrack.finding.FindingsAction;
+import io.github.pmckeown.dependencytrack.finding.Suppression;
+import io.github.pmckeown.dependencytrack.metrics.MetricsAction;
+import io.github.pmckeown.dependencytrack.project.Project;
+import io.github.pmckeown.dependencytrack.project.ProjectAction;
+import io.github.pmckeown.dependencytrack.project.UpdateRequest;
+import io.github.pmckeown.util.Logger;
/**
- * Provides the capability to upload a Bill of Material (BOM) to your Dependency Track server.
- *
- * The BOM may any format supported by your Dependency Track server, has only been tested with the output from the
+ * Provides the capability to upload a Bill of Material (BOM) to your Dependency Track server. The BOM may any format
+ * supported by your Dependency Track server, has only been tested with the output from the
* cyclonedx-maven-plugin in the
- * CycloneDX format
- *
- * Specific configuration options are:
+ * CycloneDX format Specific configuration options are:
*
- * - bomLocation
+ * - bomLocation
*
*
* @author Paul McKeown
@@ -35,120 +38,130 @@
@Mojo(name = "upload-bom", defaultPhase = LifecyclePhase.VERIFY)
public class UploadBomMojo extends AbstractDependencyTrackMojo {
- @Parameter(property = "dependency-track.bomLocation")
- private String bomLocation;
-
- @Parameter(property = "project", readonly = true, required = true)
- private MavenProject mavenProject;
-
- @Parameter(property = "dependency-track.updateProjectInfo")
- private boolean updateProjectInfo;
-
- @Parameter(property = "dependency-track.updateParent")
- private boolean updateParent;
-
- @Parameter(defaultValue = "${project.parent.name}", property = "dependency-track.parentName")
- private String parentName;
-
- @Parameter(property = "dependency-track.parentVersion")
- private String parentVersion;
-
- private final UploadBomAction uploadBomAction;
-
- private final MetricsAction metricsAction;
-
- private final ProjectAction projectAction;
-
- @Inject
- public UploadBomMojo(UploadBomAction uploadBomAction, MetricsAction metricsAction, ProjectAction projectAction,
- CommonConfig commonConfig, Logger logger) {
- super(commonConfig, logger);
- this.uploadBomAction = uploadBomAction;
- this.metricsAction = metricsAction;
- this.projectAction = projectAction;
- }
-
- @Override
- public void performAction() throws MojoExecutionException, MojoFailureException {
- logger.info("Update Project Parent : %s", updateParent);
-
- try {
- if (!uploadBomAction.upload(getBomLocation())) {
- handleFailure("Bom upload failed");
- }
- Project project = projectAction.getProject(projectName, projectVersion);
-
- UpdateRequest updateReq = new UpdateRequest();
- if (updateProjectInfo) {
- updateReq.withBomLocation(getBomLocation());
- }
- if (updateParent) {
- updateReq.withParent(getProjectParent(parentName, parentVersion));
- }
- if (updateProjectInfo || updateParent) {
- boolean projectUpdated = projectAction.updateProject(project, updateReq);
- if (!projectUpdated) {
- logger.error("Failed to update project info");
- throw new DependencyTrackException("Failed to update project info");
- }
- }
-
- metricsAction.refreshMetrics(project);
- } catch (DependencyTrackException ex) {
- handleFailure("Error occurred during upload", ex);
- }
- }
-
- private Project getProjectParent(String parentName, String parentVersion)
- throws DependencyTrackException {
- if (StringUtils.isEmpty(parentName)) {
- logger.error("Parent update requested but no parent found in parent maven project or provided in config");
- throw new DependencyTrackException("No parent found.");
- } else {
- logger.info("Attempting to fetch project parent: '%s-%s'", parentName, parentVersion);
-
- try {
- return projectAction.getProject(parentName, parentVersion);
- } catch (DependencyTrackException ex) {
- logger.error("Failed to find parent project with name ['%s-%s']. Check the update parent " +
- "your settings for this plugin and verify if a matching parent project exists in the " +
- "server.", parentName, parentVersion);
- throw ex;
- }
- }
- }
-
- private String getBomLocation() {
- if (StringUtils.isNotBlank(bomLocation)) {
- return bomLocation;
- } else {
- String defaultLocation = mavenProject.getBasedir() + "/target/bom.xml";
- logger.debug("bomLocation not supplied so using: %s", defaultLocation);
- return defaultLocation;
- }
- }
-
- /*
- * Setters for dependency injection in tests
- */
- void setBomLocation(String bomLocation) {
- this.bomLocation = bomLocation;
- }
-
- void setMavenProject(MavenProject mp) {
- this.mavenProject = mp;
- }
-
- void setUpdateParent(boolean updateParent) {
- this.updateParent = updateParent;
- }
-
- void setParentName(String parentName) {
- this.parentName = parentName;
- }
-
- void setParentVersion(String parentVersion) {
- this.parentVersion = parentVersion;
- }
+ @Parameter(property = "dependency-track.bomLocation")
+ private String bomLocation;
+
+ @Parameter(property = "project", readonly = true, required = true)
+ private MavenProject mavenProject;
+
+ @Parameter(property = "dependency-track.updateProjectInfo")
+ private boolean updateProjectInfo;
+
+ @Parameter(property = "dependency-track.updateParent")
+ private boolean updateParent;
+
+ @Parameter(defaultValue = "${project.parent.name}", property = "dependency-track.parentName")
+ private String parentName;
+
+ @Parameter(property = "dependency-track.parentVersion")
+ private String parentVersion;
+
+ @Parameter(property = "dependency-track.suppressions")
+ private List suppressions = new ArrayList<>();
+
+ private final UploadBomAction uploadBomAction;
+
+ private final MetricsAction metricsAction;
+
+ private final ProjectAction projectAction;
+
+ private final FindingsAction findingsAction;
+
+ @Inject
+ public UploadBomMojo(final UploadBomAction uploadBomAction, final MetricsAction metricsAction,
+ final ProjectAction projectAction, final FindingsAction findingsAction, final CommonConfig commonConfig,
+ final Logger logger) {
+ super(commonConfig, logger);
+ this.uploadBomAction = uploadBomAction;
+ this.metricsAction = metricsAction;
+ this.projectAction = projectAction;
+ this.findingsAction = findingsAction;
+ }
+
+ @Override
+ public void performAction() throws MojoExecutionException, MojoFailureException {
+ logger.info("Update Project Parent : %s", updateParent);
+
+ try {
+ if (!uploadBomAction.upload(getBomLocation())) {
+ handleFailure("Bom upload failed");
+ }
+ final Project project = projectAction.getProject(projectName, projectVersion);
+
+ final UpdateRequest updateReq = new UpdateRequest();
+ if (updateProjectInfo) {
+ updateReq.withBomLocation(getBomLocation());
+ }
+ if (updateParent) {
+ updateReq.withParent(getProjectParent(parentName, parentVersion));
+ }
+ if (updateProjectInfo || updateParent) {
+ final boolean projectUpdated = projectAction.updateProject(project, updateReq);
+ if (!projectUpdated) {
+ logger.error("Failed to update project info");
+ throw new DependencyTrackException("Failed to update project info");
+ }
+ }
+ if (!suppressions.isEmpty()) {
+ findingsAction.suppressFindings(project, suppressions);
+ }
+
+ metricsAction.refreshMetrics(project);
+ } catch (final DependencyTrackException ex) {
+ handleFailure("Error occurred during upload", ex);
+ }
+ }
+
+ private Project getProjectParent(final String parentName, final String parentVersion)
+ throws DependencyTrackException {
+ if (StringUtils.isEmpty(parentName)) {
+ logger.error("Parent update requested but no parent found in parent maven project or provided in config");
+ throw new DependencyTrackException("No parent found.");
+ } else {
+ logger.info("Attempting to fetch project parent: '%s-%s'", parentName, parentVersion);
+
+ try {
+ return projectAction.getProject(parentName, parentVersion);
+ } catch (final DependencyTrackException ex) {
+ logger.error("Failed to find parent project with name ['%s-%s']. Check the update parent "
+ + "your settings for this plugin and verify if a matching parent project exists in the "
+ + "server.", parentName, parentVersion);
+ throw ex;
+ }
+ }
+ }
+
+ private String getBomLocation() {
+ if (StringUtils.isNotBlank(bomLocation)) {
+ return bomLocation;
+ } else {
+ final String defaultLocation = mavenProject.getBasedir() + "/target/bom.xml";
+ logger.debug("bomLocation not supplied so using: %s", defaultLocation);
+ return defaultLocation;
+ }
+ }
+
+ /*
+ * Setters for dependency injection in tests
+ */
+ void setBomLocation(final String bomLocation) {
+ this.bomLocation = bomLocation;
+ }
+
+ void setMavenProject(final MavenProject mp) {
+ mavenProject = mp;
+ }
+
+ void setUpdateParent(final boolean updateParent) {
+ this.updateParent = updateParent;
+ }
+
+ void setParentName(final String parentName) {
+ this.parentName = parentName;
+ }
+
+ void setParentVersion(final String parentVersion) {
+ this.parentVersion = parentVersion;
+ }
}
diff --git a/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomRequest.java b/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomRequest.java
index 436d20c0..a27b9140 100644
--- a/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomRequest.java
+++ b/src/main/java/io/github/pmckeown/dependencytrack/upload/UploadBomRequest.java
@@ -10,35 +10,37 @@
*/
public class UploadBomRequest {
- private String projectName;
- private String projectVersion;
- private boolean autoCreate;
- private String base64EncodedBom;
-
- UploadBomRequest(String projectName, String projectVersion, boolean autoCreate, String base64EncodedBom) {
- this.projectName = projectName;
- this.projectVersion = projectVersion;
- this.autoCreate = autoCreate;
- this.base64EncodedBom = base64EncodedBom;
- }
-
- public String getProjectName() {
- return projectName;
- }
-
- public String getProjectVersion() {
- return projectVersion;
- }
-
- public boolean isAutoCreate() {
- return autoCreate;
- }
-
- public String getBom() {
- return base64EncodedBom;
- }
-
- public String toString() {
- return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
- }
+ private String projectName;
+ private String projectVersion;
+ private boolean autoCreate;
+ private String base64EncodedBom;
+
+ UploadBomRequest(final String projectName, final String projectVersion, final boolean autoCreate,
+ final String base64EncodedBom) {
+ this.projectName = projectName;
+ this.projectVersion = projectVersion;
+ this.autoCreate = autoCreate;
+ this.base64EncodedBom = base64EncodedBom;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public String getProjectVersion() {
+ return projectVersion;
+ }
+
+ public boolean isAutoCreate() {
+ return autoCreate;
+ }
+
+ public String getBom() {
+ return base64EncodedBom;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+ }
}
diff --git a/src/test/java/io/github/pmckeown/dependencytrack/finding/AnalysisBuilder.java b/src/test/java/io/github/pmckeown/dependencytrack/finding/AnalysisBuilder.java
index 9fbb23ce..ef03c205 100644
--- a/src/test/java/io/github/pmckeown/dependencytrack/finding/AnalysisBuilder.java
+++ b/src/test/java/io/github/pmckeown/dependencytrack/finding/AnalysisBuilder.java
@@ -2,28 +2,34 @@
public class AnalysisBuilder {
- private boolean suppressed = false;
- private Analysis.State state = null;
-
- private AnalysisBuilder() {
- // Use builder factory methods
- }
-
- public static AnalysisBuilder anAnalysis() {
- return new AnalysisBuilder();
- }
-
- public AnalysisBuilder withSuppressed(boolean s) {
- this.suppressed = s;
- return this;
- }
-
- public AnalysisBuilder withState(Analysis.State s) {
- this.state = s;
- return this;
- }
-
- public Analysis build() {
- return new Analysis(suppressed, state);
- }
+ private boolean suppressed = false;
+ private Analysis.AnalysisState state = null;
+ private Analysis.AnalysisJustification justification = null;
+
+ private AnalysisBuilder() {
+ // Use builder factory methods
+ }
+
+ public static AnalysisBuilder anAnalysis() {
+ return new AnalysisBuilder();
+ }
+
+ public AnalysisBuilder withSuppressed(final boolean s) {
+ suppressed = s;
+ return this;
+ }
+
+ public AnalysisBuilder withState(final Analysis.AnalysisState s) {
+ state = s;
+ return this;
+ }
+
+ public AnalysisBuilder withState(final Analysis.AnalysisJustification j) {
+ justification = j;
+ return this;
+ }
+
+ public Analysis build() {
+ return new Analysis(suppressed, state, justification);
+ }
}
diff --git a/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyserTest.java b/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyserTest.java
index 4617be2c..bc895a95 100644
--- a/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyserTest.java
+++ b/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsAnalyserTest.java
@@ -10,7 +10,7 @@
import java.util.List;
-import static io.github.pmckeown.dependencytrack.finding.Analysis.State.FALSE_POSITIVE;
+import static io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisState.FALSE_POSITIVE;
import static io.github.pmckeown.dependencytrack.finding.AnalysisBuilder.anAnalysis;
import static io.github.pmckeown.dependencytrack.finding.FindingBuilder.aDefaultFinding;
import static io.github.pmckeown.dependencytrack.finding.FindingListBuilder.aListOfFindings;
diff --git a/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinterTest.java b/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinterTest.java
index 4ad013fa..57cbb1fe 100644
--- a/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinterTest.java
+++ b/src/test/java/io/github/pmckeown/dependencytrack/finding/FindingsPrinterTest.java
@@ -11,7 +11,7 @@
import java.util.List;
import static io.github.pmckeown.dependencytrack.Constants.DELIMITER;
-import static io.github.pmckeown.dependencytrack.finding.Analysis.State.FALSE_POSITIVE;
+import static io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisState.FALSE_POSITIVE;
import static io.github.pmckeown.dependencytrack.finding.AnalysisBuilder.anAnalysis;
import static io.github.pmckeown.dependencytrack.finding.ComponentBuilder.aComponent;
import static io.github.pmckeown.dependencytrack.finding.FindingBuilder.aFinding;
@@ -140,7 +140,7 @@ private List findingsList(boolean isSuppressed, final VulnerabilityBuil
.withAnalysis(
anAnalysis()
.withSuppressed(isSuppressed)
- .withState(Analysis.State.FALSE_POSITIVE))).build();
+ .withState(Analysis.AnalysisState.FALSE_POSITIVE))).build();
}
private List findingsList(String longDescription, String vulnId, boolean isSuppressed) {
diff --git a/src/test/java/io/github/pmckeown/dependencytrack/finding/report/FindingsReportIntegrationTest.java b/src/test/java/io/github/pmckeown/dependencytrack/finding/report/FindingsReportIntegrationTest.java
index 59f5ad71..ec31222e 100644
--- a/src/test/java/io/github/pmckeown/dependencytrack/finding/report/FindingsReportIntegrationTest.java
+++ b/src/test/java/io/github/pmckeown/dependencytrack/finding/report/FindingsReportIntegrationTest.java
@@ -69,7 +69,7 @@ private List findings() {
.withFinding(aFinding()
.withVulnerability(aVulnerability().withSeverity(Severity.CRITICAL))
.withComponent(aComponent().withName("suppressed"))
- .withAnalysis(anAnalysis().withState(Analysis.State.FALSE_POSITIVE).withSuppressed(true)))
+ .withAnalysis(anAnalysis().withState(Analysis.AnalysisState.FALSE_POSITIVE).withSuppressed(true)))
.withFinding(aFinding()
.withVulnerability(aVulnerability().withSeverity(Severity.HIGH))
.withComponent(aComponent())