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