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