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