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: *
    - *
  1. projectName
  2. - *
  3. projectVersion
  4. - *
  5. dependencyTrackBaseUrl
  6. - *
  7. apiKey
  8. - *
  9. failOnError
  10. - *
  11. skip
  12. - *
  13. verifySsl
  14. + *
  15. projectName
  16. + *
  17. projectVersion
  18. + *
  19. dependencyTrackBaseUrl
  20. + *
  21. apiKey
  22. + *
  23. failOnError
  24. + *
  25. skip
  26. + *
  27. verifySsl
  28. *
* * @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: - * - */ - @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: + * + */ + @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: *
    - *
  1. bomLocation
  2. + *
  3. bomLocation
  4. *
* * @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())