View Javadoc
1   /*
2    * This file is part of dependency-check-maven.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2014 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.maven;
19  
20  import java.io.BufferedOutputStream;
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.ObjectOutputStream;
27  import java.util.List;
28  import java.util.Locale;
29  import org.eclipse.aether.artifact.Artifact;
30  import org.apache.maven.doxia.sink.Sink;
31  import org.apache.maven.plugin.AbstractMojo;
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.MojoFailureException;
34  import org.apache.maven.plugins.annotations.Component;
35  import org.apache.maven.plugins.annotations.Parameter;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.reporting.MavenReport;
38  import org.apache.maven.reporting.MavenReportException;
39  import org.apache.maven.settings.Proxy;
40  import org.apache.maven.settings.Server;
41  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
42  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
43  import org.apache.maven.shared.dependency.graph.DependencyNode;
44  import org.eclipse.aether.RepositorySystem;
45  import org.eclipse.aether.RepositorySystemSession;
46  import org.eclipse.aether.artifact.DefaultArtifact;
47  import org.eclipse.aether.repository.RemoteRepository;
48  import org.eclipse.aether.resolution.ArtifactRequest;
49  import org.eclipse.aether.resolution.ArtifactResolutionException;
50  import org.eclipse.aether.resolution.ArtifactResult;
51  import org.owasp.dependencycheck.Engine;
52  import org.owasp.dependencycheck.data.nexus.MavenArtifact;
53  import org.owasp.dependencycheck.data.nvdcve.CveDB;
54  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
55  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
56  import org.owasp.dependencycheck.dependency.Confidence;
57  import org.owasp.dependencycheck.dependency.Dependency;
58  import org.owasp.dependencycheck.dependency.Identifier;
59  import org.owasp.dependencycheck.dependency.Vulnerability;
60  import org.owasp.dependencycheck.exception.ExceptionCollection;
61  import org.owasp.dependencycheck.exception.ReportException;
62  import org.owasp.dependencycheck.reporting.ReportGenerator;
63  import org.owasp.dependencycheck.utils.Settings;
64  import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
65  import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
66  import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
67  
68  /**
69   *
70   * @author Jeremy Long
71   */
72  public abstract class BaseDependencyCheckMojo extends AbstractMojo implements MavenReport {
73  
74      //<editor-fold defaultstate="collapsed" desc="Private fields">
75      /**
76       * The properties file location.
77       */
78      private static final String PROPERTIES_FILE = "mojo.properties";
79      /**
80       * System specific new line character.
81       */
82      private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern();
83      //</editor-fold>
84      // <editor-fold defaultstate="collapsed" desc="Maven bound parameters and components">
85      /**
86       * Sets whether or not the external report format should be used.
87       */
88      @Parameter(property = "metaFileName", defaultValue = "dependency-check.ser", required = true)
89      private String dataFileName;
90      /**
91       * Sets whether or not the external report format should be used.
92       */
93      @Parameter(property = "failOnError", defaultValue = "true", required = true)
94      private boolean failOnError;
95  
96      /**
97       * Returns if the mojo should fail the build if an exception occurs.
98       *
99       * @return whether or not the mojo should fail the build
100      */
101     protected boolean isFailOnError() {
102         return failOnError;
103     }
104 
105     /**
106      * The Maven Project Object.
107      */
108     @Parameter(property = "project", required = true, readonly = true)
109     private MavenProject project;
110     /**
111      * List of Maven project of the current build
112      */
113     @Parameter(readonly = true, required = true, property = "reactorProjects")
114     private List<MavenProject> reactorProjects;
115     /**
116      * The entry point to Aether, i.e. the component doing all the work.
117      */
118     @Component
119     private RepositorySystem repoSystem;
120 
121     /**
122      * The current repository/network configuration of Maven.
123      */
124     @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
125     private RepositorySystemSession repoSession;
126 
127     /**
128      * The project's remote repositories to use for the resolution of plug-ins
129      * and their dependencies.
130      */
131     @Parameter(defaultValue = "${project.remotePluginRepositories}", readonly = true)
132     private List<RemoteRepository> remoteRepos;
133 
134     /**
135      * Component within Maven to build the dependency graph.
136      */
137     @Component
138     private DependencyGraphBuilder dependencyGraphBuilder;
139 
140     /**
141      * The output directory. This generally maps to "target".
142      */
143     @Parameter(defaultValue = "${project.build.directory}", required = true)
144     private File outputDirectory;
145     /**
146      * Specifies the destination directory for the generated Dependency-Check
147      * report. This generally maps to "target/site".
148      */
149     @Parameter(property = "project.reporting.outputDirectory", required = true)
150     private File reportOutputDirectory;
151     /**
152      * Specifies if the build should be failed if a CVSS score above a specified
153      * level is identified. The default is 11 which means since the CVSS scores
154      * are 0-10, by default the build will never fail.
155      */
156     @SuppressWarnings("CanBeFinal")
157     @Parameter(property = "failBuildOnCVSS", defaultValue = "11", required = true)
158     private float failBuildOnCVSS = 11;
159     /**
160      * Fail the build if any dependency has a vulnerability listed.
161      */
162     @SuppressWarnings("CanBeFinal")
163     @Parameter(property = "failBuildOnAnyVulnerability", defaultValue = "false", required = true)
164     private boolean failBuildOnAnyVulnerability = false;
165     /**
166      * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not
167      * recommended that this be turned to false. Default is true.
168      */
169     @Parameter(property = "autoUpdate")
170     private Boolean autoUpdate;
171     /**
172      * Sets whether Experimental analyzers are enabled. Default is false.
173      */
174     @Parameter(property = "enableExperimental")
175     private Boolean enableExperimental;
176     /**
177      * Generate aggregate reports in multi-module projects.
178      *
179      * @deprecated use the aggregate goal instead
180      */
181     @Parameter(property = "aggregate")
182     @Deprecated
183     private Boolean aggregate;
184     /**
185      * The report format to be generated (HTML, XML, VULN, ALL). This
186      * configuration option has no affect if using this within the Site plug-in
187      * unless the externalReport is set to true. Default is HTML.
188      */
189     @SuppressWarnings("CanBeFinal")
190     @Parameter(property = "format", defaultValue = "HTML", required = true)
191     private String format = "HTML";
192     /**
193      * The Maven settings.
194      */
195     @Parameter(property = "mavenSettings", defaultValue = "${settings}", required = false)
196     private org.apache.maven.settings.Settings mavenSettings;
197 
198     /**
199      * The maven settings proxy id.
200      */
201     @Parameter(property = "mavenSettingsProxyId", required = false)
202     private String mavenSettingsProxyId;
203 
204     /**
205      * The Connection Timeout.
206      */
207     @Parameter(property = "connectionTimeout", defaultValue = "", required = false)
208     private String connectionTimeout;
209     /**
210      * The path to the suppression file.
211      */
212     @Parameter(property = "suppressionFile", defaultValue = "", required = false)
213     private String suppressionFile;
214 
215     /**
216      * The path to the hints file.
217      */
218     @Parameter(property = "hintsFile", defaultValue = "", required = false)
219     private String hintsFile;
220 
221     /**
222      * Flag indicating whether or not to show a summary in the output.
223      */
224     @SuppressWarnings("CanBeFinal")
225     @Parameter(property = "showSummary", defaultValue = "true", required = false)
226     private boolean showSummary = true;
227 
228     /**
229      * Whether or not the Jar Analyzer is enabled.
230      */
231     @Parameter(property = "jarAnalyzerEnabled", required = false)
232     private Boolean jarAnalyzerEnabled;
233 
234     /**
235      * Whether or not the Archive Analyzer is enabled.
236      */
237     @Parameter(property = "archiveAnalyzerEnabled", required = false)
238     private Boolean archiveAnalyzerEnabled;
239 
240     /**
241      * Sets whether the Python Distribution Analyzer will be used.
242      */
243     @Parameter(property = "pyDistributionAnalyzerEnabled", required = false)
244     private Boolean pyDistributionAnalyzerEnabled;
245     /**
246      * Sets whether the Python Package Analyzer will be used.
247      */
248     @Parameter(property = "pyPackageAnalyzerEnabled", required = false)
249     private Boolean pyPackageAnalyzerEnabled;
250     /**
251      * Sets whether the Ruby Gemspec Analyzer will be used.
252      */
253     @Parameter(property = "rubygemsAnalyzerEnabled", required = false)
254     private Boolean rubygemsAnalyzerEnabled;
255     /**
256      * Sets whether or not the openssl Analyzer should be used.
257      */
258     @Parameter(property = "opensslAnalyzerEnabled", required = false)
259     private Boolean opensslAnalyzerEnabled;
260     /**
261      * Sets whether or not the CMake Analyzer should be used.
262      */
263     @Parameter(property = "cmakeAnalyzerEnabled", required = false)
264     private Boolean cmakeAnalyzerEnabled;
265     /**
266      * Sets whether or not the autoconf Analyzer should be used.
267      */
268     @Parameter(property = "autoconfAnalyzerEnabled", required = false)
269     private Boolean autoconfAnalyzerEnabled;
270     /**
271      * Sets whether or not the PHP Composer Lock File Analyzer should be used.
272      */
273     @Parameter(property = "composerAnalyzerEnabled", required = false)
274     private Boolean composerAnalyzerEnabled;
275     /**
276      * Sets whether or not the Node.js Analyzer should be used.
277      */
278     @Parameter(property = "nodeAnalyzerEnabled", required = false)
279     private Boolean nodeAnalyzerEnabled;
280 
281     /**
282      * Whether or not the .NET Assembly Analyzer is enabled.
283      */
284     @Parameter(property = "assemblyAnalyzerEnabled", required = false)
285     private Boolean assemblyAnalyzerEnabled;
286 
287     /**
288      * Whether or not the .NET Nuspec Analyzer is enabled.
289      */
290     @Parameter(property = "nuspecAnalyzerEnabled", required = false)
291     private Boolean nuspecAnalyzerEnabled;
292 
293     /**
294      * Whether or not the Central Analyzer is enabled.
295      */
296     @Parameter(property = "centralAnalyzerEnabled", required = false)
297     private Boolean centralAnalyzerEnabled;
298 
299     /**
300      * Whether or not the Nexus Analyzer is enabled.
301      */
302     @Parameter(property = "nexusAnalyzerEnabled", required = false)
303     private Boolean nexusAnalyzerEnabled;
304 
305     /**
306      * Whether or not the Ruby Bundle Audit Analyzer is enabled.
307      */
308     @Parameter(property = "bundleAuditAnalyzerEnabled", required = false)
309     private Boolean bundleAuditAnalyzerEnabled;
310 
311     /**
312      * Sets the path for the bundle-audit binary.
313      */
314     @Parameter(property = "bundleAuditPath", defaultValue = "", required = false)
315     private String bundleAuditPath;
316 
317     /**
318      * Whether or not the CocoaPods Analyzer is enabled.
319      */
320     @Parameter(property = "cocoapodsAnalyzerEnabled", required = false)
321     private Boolean cocoapodsAnalyzerEnabled;
322 
323     /**
324      * Whether or not the Swift package Analyzer is enabled.
325      */
326     @Parameter(property = "swiftPackageManagerAnalyzerEnabled", required = false)
327     private Boolean swiftPackageManagerAnalyzerEnabled;
328 
329     /**
330      * The URL of a Nexus server's REST API end point
331      * (http://domain/nexus/service/local).
332      */
333     @Parameter(property = "nexusUrl", required = false)
334     private String nexusUrl;
335     /**
336      * Whether or not the configured proxy is used to connect to Nexus.
337      */
338     @Parameter(property = "nexusUsesProxy", required = false)
339     private Boolean nexusUsesProxy;
340     /**
341      * The database connection string.
342      */
343     @Parameter(property = "connectionString", defaultValue = "", required = false)
344     private String connectionString;
345 
346     /**
347      * Returns the connection string.
348      *
349      * @return the connection string
350      */
351     protected String getConnectionString() {
352         return connectionString;
353     }
354     /**
355      * The database driver name. An example would be org.h2.Driver.
356      */
357     @Parameter(property = "databaseDriverName", defaultValue = "", required = false)
358     private String databaseDriverName;
359     /**
360      * The path to the database driver if it is not on the class path.
361      */
362     @Parameter(property = "databaseDriverPath", defaultValue = "", required = false)
363     private String databaseDriverPath;
364     /**
365      * The server id in the settings.xml; used to retrieve encrypted passwords
366      * from the settings.xml.
367      */
368     @Parameter(property = "serverId", defaultValue = "", required = false)
369     private String serverId;
370     /**
371      * A reference to the settings.xml settings.
372      */
373     @Parameter(defaultValue = "${settings}", readonly = true, required = true)
374     private org.apache.maven.settings.Settings settingsXml;
375     /**
376      * The security dispatcher that can decrypt passwords in the settings.xml.
377      */
378     @Component(role = SecDispatcher.class, hint = "default")
379     private SecDispatcher securityDispatcher;
380     /**
381      * The database user name.
382      */
383     @Parameter(property = "databaseUser", defaultValue = "", required = false)
384     private String databaseUser;
385     /**
386      * The password to use when connecting to the database.
387      */
388     @Parameter(property = "databasePassword", defaultValue = "", required = false)
389     private String databasePassword;
390     /**
391      * A comma-separated list of file extensions to add to analysis next to jar,
392      * zip, ....
393      */
394     @Parameter(property = "zipExtensions", required = false)
395     private String zipExtensions;
396     /**
397      * Skip Dependency Check altogether.
398      */
399     @SuppressWarnings("CanBeFinal")
400     @Parameter(property = "dependency-check.skip", defaultValue = "false", required = false)
401     private boolean skip = false;
402     /**
403      * Skip Analysis for Test Scope Dependencies.
404      */
405     @SuppressWarnings("CanBeFinal")
406     @Parameter(property = "skipTestScope", defaultValue = "true", required = false)
407     private boolean skipTestScope = true;
408     /**
409      * Skip Analysis for Runtime Scope Dependencies.
410      */
411     @SuppressWarnings("CanBeFinal")
412     @Parameter(property = "skipRuntimeScope", defaultValue = "false", required = false)
413     private boolean skipRuntimeScope = false;
414     /**
415      * Skip Analysis for Provided Scope Dependencies.
416      */
417     @SuppressWarnings("CanBeFinal")
418     @Parameter(property = "skipProvidedScope", defaultValue = "false", required = false)
419     private boolean skipProvidedScope = false;
420     /**
421      * The data directory, hold DC SQL DB.
422      */
423     @Parameter(property = "dataDirectory", defaultValue = "", required = false)
424     private String dataDirectory;
425     /**
426      * Data Mirror URL for CVE 1.2.
427      */
428     @Parameter(property = "cveUrl12Modified", defaultValue = "", required = false)
429     private String cveUrl12Modified;
430     /**
431      * Data Mirror URL for CVE 2.0.
432      */
433     @Parameter(property = "cveUrl20Modified", defaultValue = "", required = false)
434     private String cveUrl20Modified;
435     /**
436      * Base Data Mirror URL for CVE 1.2.
437      */
438     @Parameter(property = "cveUrl12Base", defaultValue = "", required = false)
439     private String cveUrl12Base;
440     /**
441      * Data Mirror URL for CVE 2.0.
442      */
443     @Parameter(property = "cveUrl20Base", defaultValue = "", required = false)
444     private String cveUrl20Base;
445     /**
446      * Optionally skip excessive CVE update checks for a designated duration in
447      * hours.
448      */
449     @Parameter(property = "cveValidForHours", defaultValue = "", required = false)
450     private Integer cveValidForHours;
451 
452     /**
453      * The path to mono for .NET Assembly analysis on non-windows systems.
454      */
455     @Parameter(property = "pathToMono", defaultValue = "", required = false)
456     private String pathToMono;
457 
458     /**
459      * The Proxy URL.
460      *
461      * @deprecated Please use mavenSettings instead
462      */
463     @SuppressWarnings("CanBeFinal")
464     @Parameter(property = "proxyUrl", defaultValue = "", required = false)
465     @Deprecated
466     private String proxyUrl = null;
467     /**
468      * Sets whether or not the external report format should be used.
469      *
470      * @deprecated the internal report is no longer supported
471      */
472     @SuppressWarnings("CanBeFinal")
473     @Parameter(property = "externalReport")
474     @Deprecated
475     private String externalReport = null;
476     // </editor-fold>
477     //<editor-fold defaultstate="collapsed" desc="Base Maven implementation">
478 
479     /**
480      * Executes dependency-check.
481      *
482      * @throws MojoExecutionException thrown if there is an exception executing
483      * the mojo
484      * @throws MojoFailureException thrown if dependency-check failed the build
485      */
486     @Override
487     public void execute() throws MojoExecutionException, MojoFailureException {
488         generatingSite = false;
489         if (skip) {
490             getLog().info("Skipping " + getName(Locale.US));
491         } else {
492             validateAggregate();
493             project.setContextValue(getOutputDirectoryContextKey(), this.outputDirectory);
494             runCheck();
495         }
496     }
497 
498     /**
499      * Checks if the aggregate configuration parameter has been set to true. If
500      * it has a MojoExecutionException is thrown because the aggregate
501      * configuration parameter is no longer supported.
502      *
503      * @throws MojoExecutionException thrown if aggregate is set to true
504      */
505     private void validateAggregate() throws MojoExecutionException {
506         if (aggregate != null && aggregate) {
507             final String msg = "Aggregate configuration detected - as of dependency-check 1.2.8 this no longer supported. "
508                     + "Please use the aggregate goal instead.";
509             throw new MojoExecutionException(msg);
510         }
511     }
512 
513     /**
514      * Generates the Dependency-Check Site Report.
515      *
516      * @param sink the sink to write the report to
517      * @param locale the locale to use when generating the report
518      * @throws MavenReportException if a maven report exception occurs
519      * @deprecated use
520      * {@link #generate(org.apache.maven.doxia.sink.Sink, java.util.Locale)}
521      * instead.
522      */
523     @Override
524     @Deprecated
525     public final void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException {
526         generate((Sink) sink, locale);
527     }
528 
529     /**
530      * A flag indicating whether or not the maven site is being generated.
531      */
532     private boolean generatingSite = false;
533 
534     /**
535      * Returns true if the Maven site is being generated.
536      *
537      * @return true if the Maven site is being generated
538      */
539     protected boolean isGeneratingSite() {
540         return generatingSite;
541     }
542 
543     /**
544      * Generates the Dependency-Check Site Report.
545      *
546      * @param sink the sink to write the report to
547      * @param locale the locale to use when generating the report
548      * @throws MavenReportException if a maven report exception occurs
549      */
550     public void generate(Sink sink, Locale locale) throws MavenReportException {
551         generatingSite = true;
552         try {
553             validateAggregate();
554         } catch (MojoExecutionException ex) {
555             throw new MavenReportException(ex.getMessage());
556         }
557         project.setContextValue(getOutputDirectoryContextKey(), getReportOutputDirectory());
558         try {
559             runCheck();
560         } catch (MojoExecutionException ex) {
561             throw new MavenReportException(ex.getMessage(), ex);
562         } catch (MojoFailureException ex) {
563             getLog().warn("Vulnerabilities were identifies that exceed the CVSS threshold for failing the build");
564         }
565     }
566 
567     /**
568      * Returns the correct output directory depending on if a site is being
569      * executed or not.
570      *
571      * @return the directory to write the report(s)
572      * @throws MojoExecutionException thrown if there is an error loading the
573      * file path
574      */
575     protected File getCorrectOutputDirectory() throws MojoExecutionException {
576         return getCorrectOutputDirectory(this.project);
577     }
578 
579     /**
580      * Returns the correct output directory depending on if a site is being
581      * executed or not.
582      *
583      * @param current the Maven project to get the output directory from
584      * @return the directory to write the report(s)
585      */
586     protected File getCorrectOutputDirectory(MavenProject current) {
587         final Object obj = current.getContextValue(getOutputDirectoryContextKey());
588         if (obj != null && obj instanceof File) {
589             return (File) obj;
590         }
591         File target = new File(current.getBuild().getDirectory());
592         if (target.getParentFile() != null && "target".equals(target.getParentFile().getName())) {
593             target = target.getParentFile();
594         }
595         return target;
596     }
597 
598     /**
599      * Returns the correct output directory depending on if a site is being
600      * executed or not.
601      *
602      * @param current the Maven project to get the output directory from
603      * @return the directory to write the report(s)
604      */
605     protected File getDataFile(MavenProject current) {
606         if (getLog().isDebugEnabled()) {
607             getLog().debug(String.format("Getting data filefor %s using key '%s'", current.getName(), getDataFileContextKey()));
608         }
609         final Object obj = current.getContextValue(getDataFileContextKey());
610         if (obj != null) {
611             if (obj instanceof String) {
612                 final File f = new File((String) obj);
613                 return f;
614             }
615         } else if (getLog().isDebugEnabled()) {
616             getLog().debug("Context value not found");
617         }
618         return null;
619     }
620 
621     /**
622      * Scans the project's artifacts and adds them to the engine's dependency
623      * list.
624      *
625      * @param project the project to scan the dependencies of
626      * @param engine the engine to use to scan the dependencies
627      * @return a collection of exceptions that may have occurred while resolving
628      * and scanning the dependencies
629      */
630     protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine) {
631         try {
632             final DependencyNode dn = dependencyGraphBuilder.buildDependencyGraph(project, null, reactorProjects);
633             return collectDependencies(engine, project, dn.getChildren());
634         } catch (DependencyGraphBuilderException ex) {
635             final String msg = String.format("Unable to build dependency graph on project %s", project.getName());
636             getLog().debug(msg, ex);
637             return new ExceptionCollection(msg, ex);
638         }
639     }
640 
641     /**
642      * Resolves the projects artifacts using Aether and scans the resulting
643      * dependencies.
644      *
645      * @param engine the core dependency-check engine
646      * @param project the project being scanned
647      * @param nodes the list of dependency nodes, generally obtained via the
648      * DependencyGraphBuilder
649      * @return a collection of exceptions that may have occurred while resolving
650      * and scanning the dependencies
651      */
652     private ExceptionCollection collectDependencies(Engine engine, MavenProject project, List<DependencyNode> nodes) {
653         ExceptionCollection exCol = null;
654         for (DependencyNode dependencyNode : nodes) {
655             exCol = collectDependencies(engine, project, dependencyNode.getChildren());
656             if (excludeFromScan(dependencyNode.getArtifact().getScope())) {
657                 continue;
658             }
659             try {
660                 //an alternative request method is documented here
661                 // https://www.mirkosertic.de/wordpress/2015/12/how-to-download-maven-artifacts-with-maven-3-1-and-eclipse-aether/
662                 final ArtifactRequest request = new ArtifactRequest();
663                 request.setArtifact(new DefaultArtifact(dependencyNode.getArtifact().getId()));
664                 request.setRepositories(remoteRepos);
665                 final ArtifactResult result = repoSystem.resolveArtifact(repoSession, request);
666                 if (result.isResolved() && result.getArtifact() != null && result.getArtifact().getFile() != null) {
667                     final List<Dependency> deps = engine.scan(result.getArtifact().getFile().getAbsoluteFile(),
668                             project.getName() + ":" + dependencyNode.getArtifact().getScope());
669                     if (deps != null) {
670                         if (deps.size() == 1) {
671                             final Dependency d = deps.get(0);
672                             if (d != null) {
673                                 final Artifact a = result.getArtifact();
674                                 final MavenArtifact ma = new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion());
675                                 d.addAsEvidence("pom", ma, Confidence.HIGHEST);
676                                 if (getLog().isDebugEnabled()) {
677                                     getLog().debug(String.format("Adding project reference %s on dependency %s",
678                                             project.getName(), d.getDisplayFileName()));
679                                 }
680                             }
681                         } else if (getLog().isDebugEnabled()) {
682                             final String msg = String.format("More then 1 dependency was identified in first pass scan of '%s' in project %s",
683                                     dependencyNode.getArtifact().getId(), project.getName());
684                             getLog().debug(msg);
685                         }
686                     } else {
687                         final String msg = String.format("Error resolving '%s' in project %s",
688                                 dependencyNode.getArtifact().getId(), project.getName());
689                         if (exCol == null) {
690                             exCol = new ExceptionCollection();
691                         }
692                         getLog().error(msg);
693                         for (Exception ex : result.getExceptions()) {
694                             exCol.addException(ex);
695                         }
696                     }
697                 } else {
698                     final String msg = String.format("Unable to resolve '%s' in project %s",
699                             dependencyNode.getArtifact().getId(), project.getName());
700                     getLog().debug(msg);
701                     if (exCol == null) {
702                         exCol = new ExceptionCollection();
703                     }
704                     for (Exception ex : result.getExceptions()) {
705                         exCol.addException(ex);
706                     }
707                 }
708             } catch (ArtifactResolutionException ex) {
709                 if (exCol == null) {
710                     exCol = new ExceptionCollection();
711                 }
712                 exCol.addException(ex);
713             }
714         }
715         return exCol;
716     }
717 
718     /**
719      * Executes the dependency-check scan and generates the necassary report.
720      *
721      * @throws MojoExecutionException thrown if there is an exception running
722      * the scan
723      * @throws MojoFailureException thrown if dependency-check is configured to
724      * fail the build
725      */
726     public abstract void runCheck() throws MojoExecutionException, MojoFailureException;
727 
728     /**
729      * Sets the Reporting output directory.
730      *
731      * @param directory the output directory
732      */
733     @Override
734     public void setReportOutputDirectory(File directory) {
735         reportOutputDirectory = directory;
736     }
737 
738     /**
739      * Returns the report output directory.
740      *
741      * @return the report output directory
742      */
743     @Override
744     public File getReportOutputDirectory() {
745         return reportOutputDirectory;
746     }
747 
748     /**
749      * Returns the output directory.
750      *
751      * @return the output directory
752      */
753     public File getOutputDirectory() {
754         return outputDirectory;
755     }
756 
757     /**
758      * Returns whether this is an external report. This method always returns
759      * true.
760      *
761      * @return <code>true</code>
762      */
763     @Override
764     public final boolean isExternalReport() {
765         return true;
766     }
767 
768     /**
769      * Returns the output name.
770      *
771      * @return the output name
772      */
773     @Override
774     public String getOutputName() {
775         if ("HTML".equalsIgnoreCase(this.format) || "ALL".equalsIgnoreCase(this.format)) {
776             return "dependency-check-report";
777         } else if ("XML".equalsIgnoreCase(this.format)) {
778             return "dependency-check-report.xml#";
779         } else if ("VULN".equalsIgnoreCase(this.format)) {
780             return "dependency-check-vulnerability";
781         } else {
782             getLog().warn("Unknown report format used during site generation.");
783             return "dependency-check-report";
784         }
785     }
786 
787     /**
788      * Returns the category name.
789      *
790      * @return the category name
791      */
792     @Override
793     public String getCategoryName() {
794         return MavenReport.CATEGORY_PROJECT_REPORTS;
795     }
796     //</editor-fold>
797 
798     /**
799      * Initializes a new <code>Engine</code> that can be used for scanning.
800      *
801      * @return a newly instantiated <code>Engine</code>
802      * @throws DatabaseException thrown if there is a database exception
803      */
804     protected Engine initializeEngine() throws DatabaseException {
805         populateSettings();
806         return new Engine();
807     }
808 
809     /**
810      * Takes the properties supplied and updates the dependency-check settings.
811      * Additionally, this sets the system properties required to change the
812      * proxy url, port, and connection timeout.
813      */
814     protected void populateSettings() {
815         Settings.initialize();
816         InputStream mojoProperties = null;
817         try {
818             mojoProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
819             Settings.mergeProperties(mojoProperties);
820         } catch (IOException ex) {
821             getLog().warn("Unable to load the dependency-check ant task.properties file.");
822             if (getLog().isDebugEnabled()) {
823                 getLog().debug("", ex);
824             }
825         } finally {
826             if (mojoProperties != null) {
827                 try {
828                     mojoProperties.close();
829                 } catch (IOException ex) {
830                     if (getLog().isDebugEnabled()) {
831                         getLog().debug("", ex);
832                     }
833                 }
834             }
835         }
836         Settings.setBooleanIfNotNull(Settings.KEYS.AUTO_UPDATE, autoUpdate);
837 
838         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_EXPERIMENTAL_ENABLED, enableExperimental);
839 
840         if (externalReport != null) {
841             getLog().warn("The 'externalReport' option was set; this configuration option has been removed. "
842                     + "Please update the dependency-check-maven plugin's configuration");
843         }
844 
845         if (proxyUrl != null && !proxyUrl.isEmpty()) {
846             getLog().warn("Deprecated configuration detected, proxyUrl will be ignored; use the maven settings to configure the proxy instead");
847         }
848         final Proxy proxy = getMavenProxy();
849         if (proxy != null) {
850             Settings.setString(Settings.KEYS.PROXY_SERVER, proxy.getHost());
851             Settings.setString(Settings.KEYS.PROXY_PORT, Integer.toString(proxy.getPort()));
852             final String userName = proxy.getUsername();
853             final String password = proxy.getPassword();
854             Settings.setStringIfNotNull(Settings.KEYS.PROXY_USERNAME, userName);
855             Settings.setStringIfNotNull(Settings.KEYS.PROXY_PASSWORD, password);
856             Settings.setStringIfNotNull(Settings.KEYS.PROXY_NON_PROXY_HOSTS, proxy.getNonProxyHosts());
857         }
858 
859         Settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
860         Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
861         Settings.setStringIfNotEmpty(Settings.KEYS.HINTS_FILE, hintsFile);
862 
863         //File Type Analyzer Settings
864         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_JAR_ENABLED, jarAnalyzerEnabled);
865         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, nuspecAnalyzerEnabled);
866         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, centralAnalyzerEnabled);
867         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled);
868         Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
869         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
870         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, assemblyAnalyzerEnabled);
871         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, archiveAnalyzerEnabled);
872         Settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, zipExtensions);
873         Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono);
874 
875         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED, pyDistributionAnalyzerEnabled);
876         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED, pyPackageAnalyzerEnabled);
877         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED, rubygemsAnalyzerEnabled);
878         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_OPENSSL_ENABLED, opensslAnalyzerEnabled);
879         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_CMAKE_ENABLED, cmakeAnalyzerEnabled);
880         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_AUTOCONF_ENABLED, autoconfAnalyzerEnabled);
881         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, composerAnalyzerEnabled);
882         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, nodeAnalyzerEnabled);
883         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_ENABLED, bundleAuditAnalyzerEnabled);
884         Settings.setStringIfNotNull(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH, bundleAuditPath);
885         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COCOAPODS_ENABLED, cocoapodsAnalyzerEnabled);
886         Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED, swiftPackageManagerAnalyzerEnabled);
887 
888         //Database configuration
889         Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
890         Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
891         Settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
892 
893         if (databaseUser == null && databasePassword == null && serverId != null) {
894             final Server server = settingsXml.getServer(serverId);
895             if (server != null) {
896                 databaseUser = server.getUsername();
897                 try {
898                     //The following fix was copied from:
899                     //   https://github.com/bsorrentino/maven-confluence-plugin/blob/master/maven-confluence-reporting-plugin/src/main/java/org/bsc/maven/confluence/plugin/AbstractBaseConfluenceMojo.java
900                     //
901                     // FIX to resolve
902                     // org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException:
903                     // java.io.FileNotFoundException: ~/.settings-security.xml (No such file or directory)
904                     //
905                     if (securityDispatcher instanceof DefaultSecDispatcher) {
906                         ((DefaultSecDispatcher) securityDispatcher).setConfigurationFile("~/.m2/settings-security.xml");
907                     }
908 
909                     databasePassword = securityDispatcher.decrypt(server.getPassword());
910                 } catch (SecDispatcherException ex) {
911                     if (ex.getCause() instanceof FileNotFoundException
912                             || (ex.getCause() != null && ex.getCause().getCause() instanceof FileNotFoundException)) {
913                         //maybe its not encrypted?
914                         final String tmp = server.getPassword();
915                         if (tmp.startsWith("{") && tmp.endsWith("}")) {
916                             getLog().error(String.format(
917                                     "Unable to decrypt the server password for server id '%s' in settings.xml%n\tCause: %s",
918                                     serverId, ex.getMessage()));
919                         } else {
920                             databasePassword = tmp;
921                         }
922                     } else {
923                         getLog().error(String.format(
924                                 "Unable to decrypt the server password for server id '%s' in settings.xml%n\tCause: %s",
925                                 serverId, ex.getMessage()));
926                     }
927                 }
928             } else {
929                 getLog().error(String.format("Server '%s' not found in the settings.xml file", serverId));
930             }
931         }
932 
933         Settings.setStringIfNotEmpty(Settings.KEYS.DB_USER, databaseUser);
934         Settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD, databasePassword);
935         Settings.setStringIfNotEmpty(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
936 
937         Settings.setStringIfNotEmpty(Settings.KEYS.CVE_MODIFIED_12_URL, cveUrl12Modified);
938         Settings.setStringIfNotEmpty(Settings.KEYS.CVE_MODIFIED_20_URL, cveUrl20Modified);
939         Settings.setStringIfNotEmpty(Settings.KEYS.CVE_SCHEMA_1_2, cveUrl12Base);
940         Settings.setStringIfNotEmpty(Settings.KEYS.CVE_SCHEMA_2_0, cveUrl20Base);
941         Settings.setIntIfNotNull(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, cveValidForHours);
942 
943     }
944 
945     /**
946      * Returns the maven proxy.
947      *
948      * @return the maven proxy
949      */
950     private Proxy getMavenProxy() {
951         if (mavenSettings != null) {
952             final List<Proxy> proxies = mavenSettings.getProxies();
953             if (proxies != null && !proxies.isEmpty()) {
954                 if (mavenSettingsProxyId != null) {
955                     for (Proxy proxy : proxies) {
956                         if (mavenSettingsProxyId.equalsIgnoreCase(proxy.getId())) {
957                             return proxy;
958                         }
959                     }
960                 } else if (proxies.size() == 1) {
961                     return proxies.get(0);
962                 } else {
963                     getLog().warn("Multiple proxy definitions exist in the Maven settings. In the dependency-check "
964                             + "configuration set the mavenSettingsProxyId so that the correct proxy will be used.");
965                     throw new IllegalStateException("Ambiguous proxy definition");
966                 }
967             }
968         }
969         return null;
970     }
971 
972     /**
973      * Tests is the artifact should be included in the scan (i.e. is the
974      * dependency in a scope that is being scanned).
975      *
976      * @param scope the scope of the artifact to test
977      * @return <code>true</code> if the artifact is in an excluded scope;
978      * otherwise <code>false</code>
979      */
980     protected boolean excludeFromScan(String scope) {
981         if (skipTestScope && org.apache.maven.artifact.Artifact.SCOPE_TEST.equals(scope)) {
982             return true;
983         }
984         if (skipProvidedScope && org.apache.maven.artifact.Artifact.SCOPE_PROVIDED.equals(scope)) {
985             return true;
986         }
987         if (skipRuntimeScope && !org.apache.maven.artifact.Artifact.SCOPE_RUNTIME.equals(scope)) {
988             return true;
989         }
990         return false;
991     }
992 
993     /**
994      * Returns a reference to the current project. This method is used instead
995      * of auto-binding the project via component annotation in concrete
996      * implementations of this. If the child has a
997      * <code>@Component MavenProject project;</code> defined then the abstract
998      * class (i.e. this class) will not have access to the current project (just
999      * the way Maven works with the binding).
1000      *
1001      * @return returns a reference to the current project
1002      */
1003     protected MavenProject getProject() {
1004         return project;
1005     }
1006 
1007     /**
1008      * Returns the list of Maven Projects in this build.
1009      *
1010      * @return the list of Maven Projects in this build
1011      */
1012     protected List<MavenProject> getReactorProjects() {
1013         return reactorProjects;
1014     }
1015 
1016     /**
1017      * Returns the report format.
1018      *
1019      * @return the report format
1020      */
1021     protected String getFormat() {
1022         return format;
1023     }
1024 
1025     /**
1026      * Generates the reports for a given dependency-check engine.
1027      *
1028      * @param engine a dependency-check engine
1029      * @param p the Maven project
1030      * @param outputDir the directory path to write the report(s)
1031      * @throws ReportException thrown if there is an error writing the report
1032      */
1033     protected void writeReports(Engine engine, MavenProject p, File outputDir) throws ReportException {
1034         DatabaseProperties prop = null;
1035         CveDB cve = null;
1036         try {
1037             cve = new CveDB();
1038             cve.open();
1039             prop = cve.getDatabaseProperties();
1040         } catch (DatabaseException ex) {
1041             if (getLog().isDebugEnabled()) {
1042                 getLog().debug("Unable to retrieve DB Properties", ex);
1043             }
1044         } finally {
1045             if (cve != null) {
1046                 cve.close();
1047             }
1048         }
1049         final ReportGenerator r = new ReportGenerator(p.getName(), engine.getDependencies(), engine.getAnalyzers(), prop);
1050         try {
1051             r.generateReports(outputDir.getAbsolutePath(), format);
1052         } catch (ReportException ex) {
1053             final String msg = String.format("Error generating the report for %s", p.getName());
1054             throw new ReportException(msg, ex);
1055         }
1056 
1057     }
1058 
1059     //<editor-fold defaultstate="collapsed" desc="Methods to fail build or show summary">
1060     /**
1061      * Checks to see if a vulnerability has been identified with a CVSS score
1062      * that is above the threshold set in the configuration.
1063      *
1064      * @param dependencies the list of dependency objects
1065      * @throws MojoFailureException thrown if a CVSS score is found that is
1066      * higher then the threshold set
1067      */
1068     protected void checkForFailure(List<Dependency> dependencies) throws MojoFailureException {
1069         final StringBuilder ids = new StringBuilder();
1070         for (Dependency d : dependencies) {
1071             boolean addName = true;
1072             for (Vulnerability v : d.getVulnerabilities()) {
1073                 if (failBuildOnAnyVulnerability || v.getCvssScore() >= failBuildOnCVSS) {
1074                     if (addName) {
1075                         addName = false;
1076                         ids.append(NEW_LINE).append(d.getFileName()).append(": ");
1077                         ids.append(v.getName());
1078                     } else {
1079                         ids.append(", ").append(v.getName());
1080                     }
1081                 }
1082             }
1083         }
1084         if (ids.length() > 0) {
1085             final String msg;
1086             if (failBuildOnAnyVulnerability) {
1087                 msg = String.format("%n%nOne or more dependencies were identified with vulnerabilities: %n%s%n%n"
1088                         + "See the dependency-check report for more details.%n%n", ids.toString());
1089             } else {
1090                 msg = String.format("%n%nOne or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %n%s%n%n"
1091                         + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString());
1092             }
1093 
1094             throw new MojoFailureException(msg);
1095         }
1096     }
1097 
1098     /**
1099      * Generates a warning message listing a summary of dependencies and their
1100      * associated CPE and CVE entries.
1101      *
1102      * @param mp the Maven project for which the summary is shown
1103      * @param dependencies a list of dependency objects
1104      */
1105     protected void showSummary(MavenProject mp, List<Dependency> dependencies) {
1106         if (showSummary) {
1107             final StringBuilder summary = new StringBuilder();
1108             for (Dependency d : dependencies) {
1109                 boolean firstEntry = true;
1110                 final StringBuilder ids = new StringBuilder();
1111                 for (Vulnerability v : d.getVulnerabilities()) {
1112                     if (firstEntry) {
1113                         firstEntry = false;
1114                     } else {
1115                         ids.append(", ");
1116                     }
1117                     ids.append(v.getName());
1118                 }
1119                 if (ids.length() > 0) {
1120                     summary.append(d.getFileName()).append(" (");
1121                     firstEntry = true;
1122                     for (Identifier id : d.getIdentifiers()) {
1123                         if (firstEntry) {
1124                             firstEntry = false;
1125                         } else {
1126                             summary.append(", ");
1127                         }
1128                         summary.append(id.getValue());
1129                     }
1130                     summary.append(") : ").append(ids).append(NEW_LINE);
1131                 }
1132             }
1133             if (summary.length() > 0) {
1134                 final String msg = String.format("%n%n" + "One or more dependencies were identified with known vulnerabilities in %s:%n%n%s"
1135                         + "%n%nSee the dependency-check report for more details.%n%n", mp.getName(), summary.toString());
1136                 getLog().warn(msg);
1137             }
1138         }
1139     }
1140 
1141     //</editor-fold>
1142     //<editor-fold defaultstate="collapsed" desc="Methods to read/write the serialized data file">
1143     /**
1144      * Returns the key used to store the path to the data file that is saved by
1145      * <code>writeDataFile()</code>. This key is used in the
1146      * <code>MavenProject.(set|get)ContextValue</code>.
1147      *
1148      * @return the key used to store the path to the data file
1149      */
1150     protected String getDataFileContextKey() {
1151         return "dependency-check-path-" + dataFileName;
1152     }
1153 
1154     /**
1155      * Returns the key used to store the path to the output directory. When
1156      * generating the report in the <code>executeAggregateReport()</code> the
1157      * output directory should be obtained by using this key.
1158      *
1159      * @return the key used to store the path to the output directory
1160      */
1161     protected String getOutputDirectoryContextKey() {
1162         return "dependency-output-dir-" + dataFileName;
1163     }
1164 
1165     /**
1166      * Writes the scan data to disk. This is used to serialize the scan data
1167      * between the "check" and "aggregate" phase.
1168      *
1169      * @param mp the mMven project for which the data file was created
1170      * @param writeTo the directory to write the data file
1171      * @param dependencies the list of dependencies to serialize
1172      */
1173     protected void writeDataFile(MavenProject mp, File writeTo, List<Dependency> dependencies) {
1174         File file;
1175         //check to see if this was already written out
1176         if (mp.getContextValue(this.getDataFileContextKey()) == null) {
1177             if (writeTo == null) {
1178                 file = new File(mp.getBuild().getDirectory());
1179                 file = new File(file, dataFileName);
1180             } else {
1181                 file = new File(writeTo, dataFileName);
1182             }
1183             final File parent = file.getParentFile();
1184             if (!parent.isDirectory() && !parent.mkdirs()) {
1185                 getLog().error(String.format("Directory '%s' does not exist and cannot be created; unable to write data file.",
1186                         parent.getAbsolutePath()));
1187             }
1188 
1189             ObjectOutputStream out = null;
1190             try {
1191                 if (dependencies != null) {
1192                     out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
1193                     out.writeObject(dependencies);
1194                 }
1195                 if (getLog().isDebugEnabled()) {
1196                     getLog().debug(String.format("Serialized data file written to '%s' for %s, referenced by key %s",
1197                             file.getAbsolutePath(), mp.getName(), this.getDataFileContextKey()));
1198                 }
1199                 mp.setContextValue(this.getDataFileContextKey(), file.getAbsolutePath());
1200             } catch (IOException ex) {
1201                 getLog().warn("Unable to create data file used for report aggregation; "
1202                         + "if report aggregation is being used the results may be incomplete.");
1203                 if (getLog().isDebugEnabled()) {
1204                     getLog().debug(ex.getMessage(), ex);
1205                 }
1206             } finally {
1207                 if (out != null) {
1208                     try {
1209                         out.close();
1210                     } catch (IOException ex) {
1211                         if (getLog().isDebugEnabled()) {
1212                             getLog().debug("ignore", ex);
1213                         }
1214                     }
1215                 }
1216             }
1217         }
1218     }
1219     //</editor-fold>
1220 
1221 }