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