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