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