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.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.UnsupportedEncodingException;
24  import java.net.URLEncoder;
25  import java.text.DateFormat;
26  import java.util.Date;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Set;
30  import java.util.logging.Level;
31  import java.util.logging.Logger;
32  import org.apache.maven.artifact.Artifact;
33  import org.apache.maven.doxia.sink.Sink;
34  import org.apache.maven.doxia.sink.SinkFactory;
35  import org.apache.maven.plugin.AbstractMojo;
36  import org.apache.maven.plugin.MojoExecutionException;
37  import org.apache.maven.plugin.MojoFailureException;
38  import org.apache.maven.plugins.annotations.Component;
39  import org.apache.maven.plugins.annotations.LifecyclePhase;
40  import org.apache.maven.plugins.annotations.Mojo;
41  import org.apache.maven.plugins.annotations.Parameter;
42  import org.apache.maven.plugins.annotations.ResolutionScope;
43  import org.apache.maven.project.MavenProject;
44  import org.apache.maven.reporting.MavenMultiPageReport;
45  import org.apache.maven.reporting.MavenReport;
46  import org.apache.maven.reporting.MavenReportException;
47  import org.owasp.dependencycheck.Engine;
48  import org.owasp.dependencycheck.data.nvdcve.CveDB;
49  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
50  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
51  import org.owasp.dependencycheck.dependency.Dependency;
52  import org.owasp.dependencycheck.dependency.Evidence;
53  import org.owasp.dependencycheck.dependency.Identifier;
54  import org.owasp.dependencycheck.dependency.Reference;
55  import org.owasp.dependencycheck.dependency.Vulnerability;
56  import org.owasp.dependencycheck.dependency.VulnerableSoftware;
57  import org.owasp.dependencycheck.reporting.ReportGenerator;
58  import org.owasp.dependencycheck.utils.LogUtils;
59  import org.owasp.dependencycheck.utils.Settings;
60  
61  /**
62   * Maven Plugin that checks project dependencies to see if they have any known published vulnerabilities.
63   *
64   * @author Jeremy Long <jeremy.long@owasp.org>
65   */
66  @Mojo(name = "check", defaultPhase = LifecyclePhase.COMPILE, threadSafe = true,
67          requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM,
68          requiresOnline = true)
69  public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageReport {
70  
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      // <editor-fold defaultstate="collapsed" desc="Maven bound parameters and components">
84      /**
85       * The Maven Project Object.
86       */
87      @Component
88      private MavenProject project;
89      /**
90       * The name of the site report destination.
91       */
92      @Parameter(property = "report-name", defaultValue = "dependency-check-report")
93      private String reportName;
94      /**
95       * The path to the verbose log
96       */
97      @Parameter(property = "logfile", defaultValue = "")
98      private String logFile;
99      /**
100      * The name of the report to be displayed in the Maven Generated Reports page
101      */
102     @Parameter(property = "name", defaultValue = "Dependency-Check")
103     private String name;
104     /**
105      * The description of the Dependency-Check report to be displayed in the Maven Generated Reports page
106      */
107     @Parameter(property = "description", defaultValue = "A report providing details on any published "
108             + "vulnerabilities within project dependencies. This report is a best effort but may contain "
109             + "false positives and false negatives.")
110     private String description;
111     /**
112      * Specifies the destination directory for the generated Dependency-Check report.
113      */
114     @Parameter(property = "reportOutputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true)
115     private File reportOutputDirectory;
116     /**
117      * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11
118      * which means since the CVSS scores are 0-10, by default the build will never fail.
119      */
120     @Parameter(property = "failBuildOnCVSS", defaultValue = "11", required = true)
121     private float failBuildOnCVSS = 11;
122     /**
123      * The output directory.
124      */
125     @Parameter(defaultValue = "${project.build.directory}", required = true)
126     private File outputDirectory;
127     /**
128      * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to
129      * false. Default is true.
130      */
131     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
132     @Parameter(property = "autoupdate", defaultValue = "true", required = true)
133     private boolean autoUpdate = true;
134     /**
135      * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this
136      * within the Site plugin unless the externalReport is set to true. Default is HTML.
137      */
138     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
139     @Parameter(property = "format", defaultValue = "HTML", required = true)
140     private String format = "HTML";
141     /**
142      * Sets whether or not the external report format should be used.
143      */
144     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
145     @Parameter(property = "externalReport", defaultValue = "false", required = true)
146     private boolean externalReport = false;
147     /**
148      * The Proxy URL.
149      */
150     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
151     @Parameter(property = "proxyUrl", defaultValue = "", required = false)
152     private String proxyUrl = null;
153     /**
154      * The Proxy Port.
155      */
156     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
157     @Parameter(property = "proxyPort", defaultValue = "", required = false)
158     private String proxyPort = null;
159     /**
160      * The Proxy username.
161      */
162     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
163     @Parameter(property = "proxyUsername", defaultValue = "", required = false)
164     private String proxyUsername = null;
165     /**
166      * The Proxy password.
167      */
168     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
169     @Parameter(property = "proxyPassword", defaultValue = "", required = false)
170     private String proxyPassword = null;
171     /**
172      * The Connection Timeout.
173      */
174     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
175     @Parameter(property = "connectionTimeout", defaultValue = "", required = false)
176     private String connectionTimeout = null;
177     /**
178      * The Connection Timeout.
179      */
180     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
181     @Parameter(property = "suppressionFile", defaultValue = "", required = false)
182     private String suppressionFile = null;
183     /**
184      * Flag indicating whether or not to show a summary in the output.
185      */
186     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
187     @Parameter(property = "showSummary", defaultValue = "true", required = false)
188     private boolean showSummary = true;
189     /**
190      * Whether or not the Nexus Analyzer is enabled.
191      */
192     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
193     @Parameter(property = "nexusAnalyzerEnabled", defaultValue = "true", required = false)
194     private boolean nexusAnalyzerEnabled = true;
195     /**
196      * Whether or not the Nexus Analyzer is enabled.
197      */
198     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
199     @Parameter(property = "nexusUrl", defaultValue = "", required = false)
200     private String nexusUrl;
201     /**
202      * Whether or not the configured proxy is used to connect to Nexus.
203      */
204     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
205     @Parameter(property = "nexusUsesProxy", defaultValue = "true", required = false)
206     private boolean nexusUsesProxy = true;
207     /**
208      * The database connection string.
209      */
210     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
211     @Parameter(property = "connectionString", defaultValue = "", required = false)
212     private String connectionString;
213     /**
214      * The database driver name. An example would be org.h2.Driver.
215      */
216     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
217     @Parameter(property = "databaseDriverName", defaultValue = "", required = false)
218     private String databaseDriverName;
219     /**
220      * The path to the database driver if it is not on the class path.
221      */
222     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
223     @Parameter(property = "databaseDriverPath", defaultValue = "", required = false)
224     private String databaseDriverPath;
225     /**
226      * The database user name.
227      */
228     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
229     @Parameter(property = "databaseUser", defaultValue = "", required = false)
230     private String databaseUser;
231     /**
232      * The password to use when connecting to the database.
233      */
234     @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal"})
235     @Parameter(property = "databasePassword", defaultValue = "", required = false)
236     private String databasePassword;
237     /**
238      * A comma-separated list of file extensions to add to analysis next to jar, zip, ....
239      */
240     @Parameter(property = "zipExtensions", required = false)
241     private String zipExtensions;
242     /**
243      * Skip Analisys for Test Scope Dependencies
244      */
245     @Parameter(property = "skipTestScope", defaultValue = "true", required = false)
246     private boolean skipTestScope = true;
247     /**
248      * Skip Analisys for Runtime Scope Dependencies
249      */
250     @Parameter(property = "skipRuntimeScope", defaultValue = "false", required = false)
251     private boolean skipRuntimeScope = false;
252     /**
253      * Skip Analisys for Provided Scope Dependencies
254      */
255     @Parameter(property = "skipProvidedScope", defaultValue = "false", required = false)
256     private boolean skipProvidedScope = false;
257     /**
258      * The data directory, hold DC SQL DB.
259      */
260     @Parameter(property = "dataDirectory", defaultValue = "", required = false)
261     private String dataDirectory;
262     /**
263      * Data Mirror URL for CVE 1.2
264      */
265     @Parameter(property = "cveUrl12Modified", defaultValue = "", required = false)
266     private String cveUrl12Modified;
267     /**
268      * Data Mirror URL for CVE 2.0
269      */
270     @Parameter(property = "cveUrl20Modified", defaultValue = "", required = false)
271     private String cveUrl20Modified;
272     /**
273      * Base Data Mirror URL for CVE 1.2
274      */
275     @Parameter(property = "cveUrl12Base", defaultValue = "", required = false)
276     private String cveUrl12Base;
277     /**
278      * Data Mirror URL for CVE 2.0
279      */
280     @Parameter(property = "cveUrl20Base", defaultValue = "", required = false)
281     private String cveUrl20Base;
282 
283     // </editor-fold>
284     /**
285      * Executes the Dependency-Check on the dependent libraries.
286      *
287      * @return the Engine used to scan the dependencies.
288      * @throws DatabaseException thrown if there is an exception connecting to the database
289      */
290     private Engine executeDependencyCheck() throws DatabaseException {
291 
292         final InputStream in = DependencyCheckMojo.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE);
293         LogUtils.prepareLogger(in, logFile);
294 
295         populateSettings();
296         Engine engine = null;
297         try {
298             engine = new Engine();
299             final Set<Artifact> artifacts = project.getArtifacts();
300             for (Artifact a : artifacts) {
301                 if (skipTestScope && Artifact.SCOPE_TEST.equals(a.getScope())) {
302                     continue;
303                 }
304 
305                 if (skipProvidedScope && Artifact.SCOPE_PROVIDED.equals(a.getScope())) {
306                     continue;
307                 }
308 
309                 if (skipRuntimeScope && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) {
310                     continue;
311                 }
312 
313                 engine.scan(a.getFile().getAbsolutePath());
314             }
315             engine.analyzeDependencies();
316         } finally {
317             if (engine != null) {
318                 engine.cleanup();
319             }
320         }
321         return engine;
322     }
323 
324     /**
325      * Generates the reports for a given dependency-check engine.
326      *
327      * @param engine a dependency-check engine
328      */
329     private void generateExternalReports(Engine engine) {
330         DatabaseProperties prop = null;
331         CveDB cve = null;
332         try {
333             cve = new CveDB();
334             cve.open();
335             prop = cve.getDatabaseProperties();
336         } catch (DatabaseException ex) {
337             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.FINE, "Unable to retrieve DB Properties", ex);
338         } finally {
339             if (cve != null) {
340                 cve.close();
341             }
342         }
343         final ReportGenerator r = new ReportGenerator(project.getName(), engine.getDependencies(), engine.getAnalyzers(), prop);
344         try {
345             r.generateReports(outputDirectory.getCanonicalPath(), format);
346         } catch (IOException ex) {
347             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.SEVERE,
348                     "Unexpected exception occurred during analysis; please see the verbose error log for more details.");
349             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.FINE, null, ex);
350         } catch (Throwable ex) {
351             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.SEVERE,
352                     "Unexpected exception occurred during analysis; please see the verbose error log for more details.");
353             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.FINE, null, ex);
354         }
355     }
356 
357     /**
358      * Generates a dependency-check report using the Maven Site format.
359      *
360      * @param engine the engine used to scan the dependencies
361      * @param sink the sink to write the data to
362      */
363     private void generateMavenSiteReport(final Engine engine, Sink sink) {
364         final List<Dependency> dependencies = engine.getDependencies();
365 
366         writeSiteReportHeader(sink, project.getName());
367         writeSiteReportTOC(sink, dependencies);
368 
369         int cnt = 0;
370         for (Dependency d : dependencies) {
371             writeSiteReportDependencyHeader(sink, d);
372             cnt = writeSiteReportDependencyEvidenceUsed(d, cnt, sink);
373             cnt = writeSiteReportDependencyRelatedDependencies(d, cnt, sink);
374             writeSiteReportDependencyIdentifiers(d, sink);
375             writeSiteReportDependencyVulnerabilities(d, sink, cnt);
376         }
377         sink.body_();
378     }
379 
380     // <editor-fold defaultstate="collapsed" desc="various writeXXXXX methods to generate the Site Report">
381     /**
382      * Writes the vulnerabilities to the site report.
383      *
384      * @param d the dependency
385      * @param sink the sink to write the data to
386      * @param collapsibleHeaderCount the collapsible header count
387      */
388     private void writeSiteReportDependencyVulnerabilities(Dependency d, Sink sink, int collapsibleHeaderCount) {
389         int cnt = collapsibleHeaderCount;
390         if (d.getVulnerabilities() != null && !d.getVulnerabilities().isEmpty()) {
391             for (Vulnerability v : d.getVulnerabilities()) {
392 
393                 sink.paragraph();
394                 sink.bold();
395                 try {
396                     sink.link("http://web.nvd.nist.gov/view/vuln/detail?vulnId=" + URLEncoder.encode(v.getName(), "US-ASCII"));
397                     sink.text(v.getName());
398                     sink.link_();
399                     sink.bold_();
400                 } catch (UnsupportedEncodingException ex) {
401                     sink.text(v.getName());
402                     sink.bold_();
403                     sink.lineBreak();
404                     sink.text("http://web.nvd.nist.gov/view/vuln/detail?vulnId=" + v.getName());
405                 }
406                 sink.paragraph_();
407                 sink.paragraph();
408                 sink.text("Severity: ");
409                 if (v.getCvssScore() < 4.0) {
410                     sink.text("Low");
411                 } else {
412                     if (v.getCvssScore() >= 7.0) {
413                         sink.text("High");
414                     } else {
415                         sink.text("Medium");
416                     }
417                 }
418                 sink.lineBreak();
419                 sink.text("CVSS Score: " + v.getCvssScore());
420                 if (v.getCwe() != null && !v.getCwe().isEmpty()) {
421                     sink.lineBreak();
422                     sink.text("CWE: ");
423                     sink.text(v.getCwe());
424                 }
425                 sink.paragraph_();
426                 sink.paragraph();
427                 sink.text(v.getDescription());
428                 if (v.getReferences() != null && !v.getReferences().isEmpty()) {
429                     sink.list();
430                     for (Reference ref : v.getReferences()) {
431                         sink.listItem();
432                         sink.text(ref.getSource());
433                         sink.text(" - ");
434                         sink.link(ref.getUrl());
435                         sink.text(ref.getName());
436                         sink.link_();
437                         sink.listItem_();
438                     }
439                     sink.list_();
440                 }
441                 sink.paragraph_();
442                 if (v.getVulnerableSoftware() != null && !v.getVulnerableSoftware().isEmpty()) {
443                     sink.paragraph();
444 
445                     cnt += 1;
446                     sink.rawText("Vulnerable Software <a href=\"javascript:toggleElement(this, 'vulnSoft" + cnt + "')\">[-]</a>");
447                     sink.rawText("<div id=\"vulnSoft" + cnt + "\" style=\"display:block\">");
448                     sink.list();
449                     for (VulnerableSoftware vs : v.getVulnerableSoftware()) {
450                         sink.listItem();
451                         try {
452                             sink.link("http://web.nvd.nist.gov/view/vuln/search-results?cpe=" + URLEncoder.encode(vs.getName(), "US-ASCII"));
453                             sink.text(vs.getName());
454                             sink.link_();
455                             if (vs.hasPreviousVersion()) {
456                                 sink.text(" and all previous versions.");
457                             }
458                         } catch (UnsupportedEncodingException ex) {
459                             sink.text(vs.getName());
460                             if (vs.hasPreviousVersion()) {
461                                 sink.text(" and all previous versions.");
462                             }
463                             sink.text(" (http://web.nvd.nist.gov/view/vuln/search-results?cpe=" + vs.getName() + ")");
464                         }
465 
466                         sink.listItem_();
467                     }
468                     sink.list_();
469                     sink.rawText("</div>");
470                     sink.paragraph_();
471                 }
472             }
473         }
474     }
475 
476     /**
477      * Writes the identifiers to the site report.
478      *
479      * @param d the dependency
480      * @param sink the sink to write the data to
481      */
482     private void writeSiteReportDependencyIdentifiers(Dependency d, Sink sink) {
483         if (d.getIdentifiers() != null && !d.getIdentifiers().isEmpty()) {
484             sink.sectionTitle4();
485             sink.text("Identifiers");
486             sink.sectionTitle4_();
487             sink.list();
488             for (Identifier i : d.getIdentifiers()) {
489                 sink.listItem();
490                 sink.text(i.getType());
491                 sink.text(": ");
492                 if (i.getUrl() != null && i.getUrl().length() > 0) {
493                     sink.link(i.getUrl());
494                     sink.text(i.getValue());
495                     sink.link_();
496                 } else {
497                     sink.text(i.getValue());
498                 }
499                 if (i.getDescription() != null && i.getDescription().length() > 0) {
500                     sink.lineBreak();
501                     sink.text(i.getDescription());
502                 }
503                 sink.listItem_();
504             }
505             sink.list_();
506         }
507     }
508 
509     /**
510      * Writes the related dependencies to the site report.
511      *
512      * @param d the dependency
513      * @param sink the sink to write the data to
514      * @param collapsibleHeaderCount the collapsible header count
515      * @return the collapsible header count
516      */
517     private int writeSiteReportDependencyRelatedDependencies(Dependency d, int collapsibleHeaderCount, Sink sink) {
518         int cnt = collapsibleHeaderCount;
519         if (d.getRelatedDependencies() != null && !d.getRelatedDependencies().isEmpty()) {
520             cnt += 1;
521             sink.sectionTitle4();
522             sink.rawText("Related Dependencies <a href=\"javascript:toggleElement(this, 'related" + cnt + "')\">[+]</a>");
523             sink.sectionTitle4_();
524             sink.rawText("<div id=\"related" + cnt + "\" style=\"display:none\">");
525             sink.list();
526             for (Dependency r : d.getRelatedDependencies()) {
527                 sink.listItem();
528                 sink.text(r.getFileName());
529                 sink.list();
530                 writeListItem(sink, "File Path: " + r.getFilePath());
531                 writeListItem(sink, "SHA1: " + r.getSha1sum());
532                 writeListItem(sink, "MD5: " + r.getMd5sum());
533                 sink.list_();
534                 sink.listItem_();
535             }
536             sink.list_();
537             sink.rawText("</div>");
538         }
539         return cnt;
540     }
541 
542     /**
543      * Writes the evidence used to the site report.
544      *
545      * @param d the dependency
546      * @param sink the sink to write the data to
547      * @param collapsibleHeaderCount the collapsible header count
548      * @return the collapsible header count
549      */
550     private int writeSiteReportDependencyEvidenceUsed(Dependency d, int collapsibleHeaderCount, Sink sink) {
551         int cnt = collapsibleHeaderCount;
552         if (d.getEvidenceUsed() != null && d.getEvidenceUsed().size() > 0) {
553             cnt += 1;
554             sink.sectionTitle4();
555             sink.rawText("Evidence Collected <a href=\"javascript:toggleElement(this, 'evidence" + cnt + "')\">[+]</a>");
556             sink.sectionTitle4_();
557             sink.rawText("<div id=\"evidence" + cnt + "\" style=\"display:none\">");
558             sink.table();
559             sink.tableRow();
560             writeTableHeaderCell(sink, "Source");
561             writeTableHeaderCell(sink, "Name");
562             writeTableHeaderCell(sink, "Value");
563             sink.tableRow_();
564             for (Evidence e : d.getEvidenceUsed()) {
565                 sink.tableRow();
566                 writeTableCell(sink, e.getSource());
567                 writeTableCell(sink, e.getName());
568                 writeTableCell(sink, e.getValue());
569                 sink.tableRow_();
570             }
571             sink.table_();
572             sink.rawText("</div>");
573         }
574         return cnt;
575     }
576 
577     /**
578      * Writes the dependency header to the site report.
579      *
580      * @param d the dependency
581      * @param sink the sink to write the data to
582      */
583     private void writeSiteReportDependencyHeader(Sink sink, Dependency d) {
584         sink.sectionTitle2();
585         sink.anchor("sha1" + d.getSha1sum());
586         sink.text(d.getFileName());
587         sink.anchor_();
588         sink.sectionTitle2_();
589         if (d.getDescription() != null && d.getDescription().length() > 0) {
590             sink.paragraph();
591             sink.bold();
592             sink.text("Description: ");
593             sink.bold_();
594             sink.text(d.getDescription());
595             sink.paragraph_();
596         }
597         if (d.getLicense() != null && d.getLicense().length() > 0) {
598             sink.paragraph();
599             sink.bold();
600             sink.text("License: ");
601             sink.bold_();
602             if (d.getLicense().startsWith("http://") && !d.getLicense().contains(" ")) {
603                 sink.link(d.getLicense());
604                 sink.text(d.getLicense());
605                 sink.link_();
606             } else {
607                 sink.text(d.getLicense());
608             }
609             sink.paragraph_();
610         }
611     }
612 
613     /**
614      * Adds a list item to the site report.
615      *
616      * @param sink the sink to write the data to
617      * @param text the text to write
618      */
619     private void writeListItem(Sink sink, String text) {
620         sink.listItem();
621         sink.text(text);
622         sink.listItem_();
623     }
624 
625     /**
626      * Adds a table cell to the site report.
627      *
628      * @param sink the sink to write the data to
629      * @param text the text to write
630      */
631     private void writeTableCell(Sink sink, String text) {
632         sink.tableCell();
633         sink.text(text);
634         sink.tableCell_();
635     }
636 
637     /**
638      * Adds a table header cell to the site report.
639      *
640      * @param sink the sink to write the data to
641      * @param text the text to write
642      */
643     private void writeTableHeaderCell(Sink sink, String text) {
644         sink.tableHeaderCell();
645         sink.text(text);
646         sink.tableHeaderCell_();
647     }
648 
649     /**
650      * Writes the TOC for the site report.
651      *
652      * @param sink the sink to write the data to
653      * @param dependencies the dependencies that are being reported on
654      */
655     private void writeSiteReportTOC(Sink sink, final List<Dependency> dependencies) {
656         sink.list();
657         for (Dependency d : dependencies) {
658             sink.listItem();
659             sink.link("#sha1" + d.getSha1sum());
660             sink.text(d.getFileName());
661             sink.link_();
662             if (!d.getVulnerabilities().isEmpty()) {
663                 sink.rawText(" <font style=\"color:red\">•</font>");
664             }
665             if (!d.getRelatedDependencies().isEmpty()) {
666                 sink.list();
667                 for (Dependency r : d.getRelatedDependencies()) {
668                     writeListItem(sink, r.getFileName());
669                 }
670                 sink.list_();
671             }
672             sink.listItem_();
673         }
674         sink.list_();
675     }
676 
677     /**
678      * Writes the site report header.
679      *
680      * @param sink the sink to write the data to
681      * @param projectName the name of the project
682      */
683     private void writeSiteReportHeader(Sink sink, String projectName) {
684         sink.head();
685         sink.title();
686         sink.text("Dependency-Check Report: " + projectName);
687         sink.title_();
688         sink.head_();
689         sink.body();
690         sink.rawText("<script type=\"text/javascript\">");
691         sink.rawText("function toggleElement(el, targetId) {");
692         sink.rawText("if (el.innerText == '[+]') {");
693         sink.rawText("    el.innerText = '[-]';");
694         sink.rawText("    document.getElementById(targetId).style.display='block';");
695         sink.rawText("} else {");
696         sink.rawText("    el.innerText = '[+]';");
697         sink.rawText("    document.getElementById(targetId).style.display='none';");
698         sink.rawText("}");
699 
700         sink.rawText("}");
701         sink.rawText("</script>");
702         sink.section1();
703         sink.sectionTitle1();
704         sink.text("Project: " + projectName);
705         sink.sectionTitle1_();
706         sink.date();
707         final Date now = new Date();
708         sink.text(DateFormat.getDateTimeInstance().format(now));
709         sink.date_();
710         sink.section1_();
711     }
712     // </editor-fold>
713 
714     /**
715      * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system
716      * properties required to change the proxy url, port, and connection timeout.
717      */
718     private void populateSettings() {
719         InputStream mojoProperties = null;
720         try {
721             mojoProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
722             Settings.mergeProperties(mojoProperties);
723         } catch (IOException ex) {
724             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.WARNING, "Unable to load the dependency-check ant task.properties file.");
725             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.FINE, null, ex);
726         } finally {
727             if (mojoProperties != null) {
728                 try {
729                     mojoProperties.close();
730                 } catch (IOException ex) {
731                     Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.FINEST, null, ex);
732                 }
733             }
734         }
735 
736         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
737 
738         if (proxyUrl != null && !proxyUrl.isEmpty()) {
739             Settings.setString(Settings.KEYS.PROXY_URL, proxyUrl);
740         }
741         if (proxyPort != null && !proxyPort.isEmpty()) {
742             Settings.setString(Settings.KEYS.PROXY_PORT, proxyPort);
743         }
744         if (proxyUsername != null && !proxyUsername.isEmpty()) {
745             Settings.setString(Settings.KEYS.PROXY_USERNAME, proxyUsername);
746         }
747         if (proxyPassword != null && !proxyPassword.isEmpty()) {
748             Settings.setString(Settings.KEYS.PROXY_PASSWORD, proxyPassword);
749         }
750         if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
751             Settings.setString(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
752         }
753         if (suppressionFile != null && !suppressionFile.isEmpty()) {
754             Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
755         }
756         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled);
757         if (nexusUrl != null && !nexusUrl.isEmpty()) {
758             Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
759         }
760         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_PROXY, nexusUsesProxy);
761         if (databaseDriverName != null && !databaseDriverName.isEmpty()) {
762             Settings.setString(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
763         }
764         if (databaseDriverPath != null && !databaseDriverPath.isEmpty()) {
765             Settings.setString(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
766         }
767         if (connectionString != null && !connectionString.isEmpty()) {
768             Settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
769         }
770         if (databaseUser != null && !databaseUser.isEmpty()) {
771             Settings.setString(Settings.KEYS.DB_USER, databaseUser);
772         }
773         if (databasePassword != null && !databasePassword.isEmpty()) {
774             Settings.setString(Settings.KEYS.DB_PASSWORD, databasePassword);
775         }
776         if (zipExtensions != null && !zipExtensions.isEmpty()) {
777             Settings.setString(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, zipExtensions);
778         }
779 
780         // Scope Exclusion
781         Settings.setBoolean(Settings.KEYS.SKIP_TEST_SCOPE, skipTestScope);
782         Settings.setBoolean(Settings.KEYS.SKIP_RUNTIME_SCOPE, skipRuntimeScope);
783         Settings.setBoolean(Settings.KEYS.SKIP_PROVIDED_SCOPE, skipProvidedScope);
784 
785         // Data Directory
786         if (dataDirectory != null && !dataDirectory.isEmpty()) {
787             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
788         }
789 
790         // CVE Data Mirroring
791         if (cveUrl12Modified != null && !cveUrl12Modified.isEmpty()) {
792             Settings.setString(Settings.KEYS.CVE_MODIFIED_12_URL, cveUrl12Modified);
793         }
794         if (cveUrl20Modified != null && !cveUrl20Modified.isEmpty()) {
795             Settings.setString(Settings.KEYS.CVE_MODIFIED_20_URL, cveUrl20Modified);
796         }
797         if (cveUrl12Base != null && !cveUrl12Base.isEmpty()) {
798             Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, cveUrl12Base);
799         }
800         if (cveUrl20Base != null && !cveUrl20Base.isEmpty()) {
801             Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveUrl20Base);
802         }
803     }
804 
805     /**
806      * Executes the dependency-check and generates the report.
807      *
808      * @throws MojoExecutionException if a maven exception occurs
809      * @throws MojoFailureException thrown if a CVSS score is found that is higher then the configured level
810      */
811     public void execute() throws MojoExecutionException, MojoFailureException {
812         Engine engine = null;
813         try {
814             engine = executeDependencyCheck();
815             generateExternalReports(engine);
816             if (this.showSummary) {
817                 showSummary(engine.getDependencies());
818             }
819             if (this.failBuildOnCVSS <= 10) {
820                 checkForFailure(engine.getDependencies());
821             }
822         } catch (DatabaseException ex) {
823             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.SEVERE,
824                     "Unable to connect to the dependency-check database; analysis has stopped");
825             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.FINE, "", ex);
826         } finally {
827             if (engine != null) {
828                 engine.cleanup();
829             }
830         }
831     }
832 
833     /**
834      * Generates the Dependency-Check Site Report.
835      *
836      * @param sink the sink to write the report to
837      * @param locale the locale to use when generating the report
838      * @throws MavenReportException if a Maven report exception occurs
839      */
840     public void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink,
841             Locale locale) throws MavenReportException {
842         generate((Sink) sink, null, locale);
843     }
844 
845     /**
846      * Generates the Dependency-Check Site Report.
847      *
848      * @param sink the sink to write the report to
849      * @param sinkFactory the sink factory
850      * @param locale the locale to use when generating the report
851      * @throws MavenReportException if a maven report exception occurs
852      */
853     public void generate(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException {
854         Engine engine = null;
855         try {
856             engine = executeDependencyCheck();
857             generateMavenSiteReport(engine, sink);
858         } catch (DatabaseException ex) {
859             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.SEVERE,
860                     "Unable to connect to the dependency-check database; analysis has stopped");
861             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.FINE, "", ex);
862         } finally {
863             if (engine != null) {
864                 engine.cleanup();
865             }
866         }
867     }
868 
869     // <editor-fold defaultstate="collapsed" desc="required setter/getter methods">
870     /**
871      * Returns the output name.
872      *
873      * @return the output name
874      */
875     public String getOutputName() {
876         return reportName;
877     }
878 
879     /**
880      * Returns the category name.
881      *
882      * @return the category name
883      */
884     public String getCategoryName() {
885         return MavenReport.CATEGORY_PROJECT_REPORTS;
886     }
887 
888     /**
889      * Returns the report name.
890      *
891      * @param locale the location
892      * @return the report name
893      */
894     public String getName(Locale locale) {
895         return name;
896     }
897 
898     /**
899      * Sets the Reporting output directory.
900      *
901      * @param directory the output directory
902      */
903     public void setReportOutputDirectory(File directory) {
904         reportOutputDirectory = directory;
905     }
906 
907     /**
908      * Returns the output directory.
909      *
910      * @return the output directory
911      */
912     public File getReportOutputDirectory() {
913         return reportOutputDirectory;
914     }
915 
916     /**
917      * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page.
918      *
919      * @param locale The Locale to get the description for
920      * @return the description
921      */
922     public String getDescription(Locale locale) {
923         return description;
924     }
925 
926     /**
927      * Returns whether this is an external report.
928      *
929      * @return true or false;
930      */
931     public boolean isExternalReport() {
932         return externalReport;
933     }
934 
935     /**
936      * Returns whether or not the plugin can generate a report.
937      *
938      * @return true
939      */
940     public boolean canGenerateReport() {
941         return true;
942     }
943     // </editor-fold>
944 
945     /**
946      * Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the
947      * configuration.
948      *
949      * @param dependencies the list of dependency objects
950      * @throws MojoFailureException thrown if a CVSS score is found that is higher then the threshold set
951      */
952     private void checkForFailure(List<Dependency> dependencies) throws MojoFailureException {
953         final StringBuilder ids = new StringBuilder();
954         for (Dependency d : dependencies) {
955             boolean addName = true;
956             for (Vulnerability v : d.getVulnerabilities()) {
957                 if (v.getCvssScore() >= failBuildOnCVSS) {
958                     if (addName) {
959                         addName = false;
960                         ids.append(NEW_LINE).append(d.getFileName()).append(": ");
961                         ids.append(v.getName());
962                     } else {
963                         ids.append(", ").append(v.getName());
964                     }
965                 }
966             }
967         }
968         if (ids.length() > 0) {
969             final String msg = String.format("%n%nDependency-Check Failure:%n"
970                     + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %s%n"
971                     + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString());
972             throw new MojoFailureException(msg);
973         }
974     }
975 
976     /**
977      * Generates a warning message listing a summary of dependencies and their associated CPE and CVE entries.
978      *
979      * @param dependencies a list of dependency objects
980      */
981     private void showSummary(List<Dependency> dependencies) {
982         final StringBuilder summary = new StringBuilder();
983         for (Dependency d : dependencies) {
984             boolean firstEntry = true;
985             final StringBuilder ids = new StringBuilder();
986             for (Vulnerability v : d.getVulnerabilities()) {
987                 if (firstEntry) {
988                     firstEntry = false;
989                 } else {
990                     ids.append(", ");
991                 }
992                 ids.append(v.getName());
993             }
994             if (ids.length() > 0) {
995                 summary.append(d.getFileName()).append(" (");
996                 firstEntry = true;
997                 for (Identifier id : d.getIdentifiers()) {
998                     if (firstEntry) {
999                         firstEntry = false;
1000                     } else {
1001                         summary.append(", ");
1002                     }
1003                     summary.append(id.getValue());
1004                 }
1005                 summary.append(") : ").append(ids).append(NEW_LINE);
1006             }
1007         }
1008         if (summary.length() > 0) {
1009             final String msg = String.format("%n%n"
1010                     + "One or more dependencies were identified with known vulnerabilities:%n%n%s"
1011                     + "%n%nSee the dependency-check report for more details.%n%n", summary.toString());
1012             Logger.getLogger(DependencyCheckMojo.class.getName()).log(Level.WARNING, msg);
1013         }
1014     }
1015 }