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