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