View Javadoc
1   /*
2    * This file is part of dependency-check-ant.
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.taskdefs;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.List;
24  import java.util.logging.Level;
25  import java.util.logging.Logger;
26  import org.apache.tools.ant.BuildException;
27  import org.apache.tools.ant.Task;
28  import org.apache.tools.ant.types.EnumeratedAttribute;
29  import org.apache.tools.ant.types.Reference;
30  import org.apache.tools.ant.types.Resource;
31  import org.apache.tools.ant.types.ResourceCollection;
32  import org.apache.tools.ant.types.resources.FileProvider;
33  import org.apache.tools.ant.types.resources.Resources;
34  import org.owasp.dependencycheck.Engine;
35  import org.owasp.dependencycheck.data.nvdcve.CveDB;
36  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
37  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
38  import org.owasp.dependencycheck.dependency.Dependency;
39  import org.owasp.dependencycheck.dependency.Identifier;
40  import org.owasp.dependencycheck.dependency.Vulnerability;
41  import org.owasp.dependencycheck.reporting.ReportGenerator;
42  import org.owasp.dependencycheck.reporting.ReportGenerator.Format;
43  import org.owasp.dependencycheck.utils.LogUtils;
44  import org.owasp.dependencycheck.utils.Settings;
45  
46  /**
47   * An Ant task definition to execute dependency-check during an Ant build.
48   *
49   * @author Jeremy Long <jeremy.long@owasp.org>
50   */
51  public class DependencyCheckTask extends Task {
52  
53      /**
54       * The properties file location.
55       */
56      private static final String PROPERTIES_FILE = "task.properties";
57      /**
58       * Name of the logging properties file.
59       */
60      private static final String LOG_PROPERTIES_FILE = "log.properties";
61      /**
62       * System specific new line character.
63       */
64      private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern();
65  
66      /**
67       * Construct a new DependencyCheckTask.
68       */
69      public DependencyCheckTask() {
70          super();
71      }
72      //The following code was copied Apache Ant PathConvert
73      //BEGIN COPY from org.apache.tools.ant.taskdefs.PathConvert
74      /**
75       * Path to be converted
76       */
77      private Resources path = null;
78      /**
79       * Reference to path/fileset to convert
80       */
81      private Reference refid = null;
82  
83      /**
84       * Add an arbitrary ResourceCollection.
85       *
86       * @param rc the ResourceCollection to add.
87       * @since Ant 1.7
88       */
89      public void add(ResourceCollection rc) {
90          if (isReference()) {
91              throw new BuildException("Nested elements are not allowed when using the refid attribute.");
92          }
93          getPath().add(rc);
94      }
95  
96      /**
97       * Returns the path. If the path has not been initialized yet, this class is synchronized, and will instantiate the
98       * path object.
99       *
100      * @return the path
101      */
102     private synchronized Resources getPath() {
103         if (path == null) {
104             path = new Resources(getProject());
105             path.setCache(true);
106         }
107         return path;
108     }
109 
110     /**
111      * Learn whether the refid attribute of this element been set.
112      *
113      * @return true if refid is valid.
114      */
115     public boolean isReference() {
116         return refid != null;
117     }
118 
119     /**
120      * Add a reference to a Path, FileSet, DirSet, or FileList defined elsewhere.
121      *
122      * @param r the reference to a path, fileset, dirset or filelist.
123      */
124     public void setRefid(Reference r) {
125         if (path != null) {
126             throw new BuildException("Nested elements are not allowed when using the refid attribute.");
127         }
128         refid = r;
129     }
130 
131     /**
132      * If this is a reference, this method will add the referenced resource collection to the collection of paths.
133      *
134      * @throws BuildException if the reference is not to a resource collection
135      */
136     private void dealWithReferences() throws BuildException {
137         if (isReference()) {
138             final Object o = refid.getReferencedObject(getProject());
139             if (!(o instanceof ResourceCollection)) {
140                 throw new BuildException("refid '" + refid.getRefId()
141                         + "' does not refer to a resource collection.");
142             }
143             getPath().add((ResourceCollection) o);
144         }
145     }
146     // END COPY from org.apache.tools.ant.taskdefs
147     /**
148      * The application name for the report.
149      */
150     private String applicationName = "Dependency-Check";
151 
152     /**
153      * Get the value of applicationName.
154      *
155      * @return the value of applicationName
156      */
157     public String getApplicationName() {
158         return applicationName;
159     }
160 
161     /**
162      * Set the value of applicationName.
163      *
164      * @param applicationName new value of applicationName
165      */
166     public void setApplicationName(String applicationName) {
167         this.applicationName = applicationName;
168     }
169     /**
170      * The location of the data directory that contains
171      */
172     private String dataDirectory = null;
173 
174     /**
175      * Get the value of dataDirectory.
176      *
177      * @return the value of dataDirectory
178      */
179     public String getDataDirectory() {
180         return dataDirectory;
181     }
182 
183     /**
184      * Set the value of dataDirectory.
185      *
186      * @param dataDirectory new value of dataDirectory
187      */
188     public void setDataDirectory(String dataDirectory) {
189         this.dataDirectory = dataDirectory;
190     }
191     /**
192      * Specifies the destination directory for the generated Dependency-Check report.
193      */
194     private String reportOutputDirectory = ".";
195 
196     /**
197      * Get the value of reportOutputDirectory.
198      *
199      * @return the value of reportOutputDirectory
200      */
201     public String getReportOutputDirectory() {
202         return reportOutputDirectory;
203     }
204 
205     /**
206      * Set the value of reportOutputDirectory.
207      *
208      * @param reportOutputDirectory new value of reportOutputDirectory
209      */
210     public void setReportOutputDirectory(String reportOutputDirectory) {
211         this.reportOutputDirectory = reportOutputDirectory;
212     }
213     /**
214      * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11
215      * which means since the CVSS scores are 0-10, by default the build will never fail and the CVSS score is set to 11.
216      * The valid range for the fail build on CVSS is 0 to 11, where anything above 10 will not cause the build to fail.
217      */
218     private float failBuildOnCVSS = 11;
219 
220     /**
221      * Get the value of failBuildOnCVSS.
222      *
223      * @return the value of failBuildOnCVSS
224      */
225     public float getFailBuildOnCVSS() {
226         return failBuildOnCVSS;
227     }
228 
229     /**
230      * Set the value of failBuildOnCVSS.
231      *
232      * @param failBuildOnCVSS new value of failBuildOnCVSS
233      */
234     public void setFailBuildOnCVSS(float failBuildOnCVSS) {
235         this.failBuildOnCVSS = failBuildOnCVSS;
236     }
237     /**
238      * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to
239      * false. Default is true.
240      */
241     private boolean autoUpdate = true;
242 
243     /**
244      * Get the value of autoUpdate.
245      *
246      * @return the value of autoUpdate
247      */
248     public boolean isAutoUpdate() {
249         return autoUpdate;
250     }
251 
252     /**
253      * Set the value of autoUpdate.
254      *
255      * @param autoUpdate new value of autoUpdate
256      */
257     public void setAutoUpdate(boolean autoUpdate) {
258         this.autoUpdate = autoUpdate;
259     }
260     /**
261      * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this
262      * within the Site plugin unless the externalReport is set to true. Default is HTML.
263      */
264     private String reportFormat = "HTML";
265 
266     /**
267      * Get the value of reportFormat.
268      *
269      * @return the value of reportFormat
270      */
271     public String getReportFormat() {
272         return reportFormat;
273     }
274 
275     /**
276      * Set the value of reportFormat.
277      *
278      * @param reportFormat new value of reportFormat
279      */
280     public void setReportFormat(ReportFormats reportFormat) {
281         this.reportFormat = reportFormat.getValue();
282     }
283     /**
284      * The Proxy URL.
285      */
286     private String proxyUrl;
287 
288     /**
289      * Get the value of proxyUrl.
290      *
291      * @return the value of proxyUrl
292      */
293     public String getProxyUrl() {
294         return proxyUrl;
295     }
296 
297     /**
298      * Set the value of proxyUrl.
299      *
300      * @param proxyUrl new value of proxyUrl
301      */
302     public void setProxyUrl(String proxyUrl) {
303         this.proxyUrl = proxyUrl;
304     }
305     /**
306      * The Proxy Port.
307      */
308     private String proxyPort;
309 
310     /**
311      * Get the value of proxyPort.
312      *
313      * @return the value of proxyPort
314      */
315     public String getProxyPort() {
316         return proxyPort;
317     }
318 
319     /**
320      * Set the value of proxyPort.
321      *
322      * @param proxyPort new value of proxyPort
323      */
324     public void setProxyPort(String proxyPort) {
325         this.proxyPort = proxyPort;
326     }
327     /**
328      * The Proxy username.
329      */
330     private String proxyUsername;
331 
332     /**
333      * Get the value of proxyUsername.
334      *
335      * @return the value of proxyUsername
336      */
337     public String getProxyUsername() {
338         return proxyUsername;
339     }
340 
341     /**
342      * Set the value of proxyUsername.
343      *
344      * @param proxyUsername new value of proxyUsername
345      */
346     public void setProxyUsername(String proxyUsername) {
347         this.proxyUsername = proxyUsername;
348     }
349     /**
350      * The Proxy password.
351      */
352     private String proxyPassword;
353 
354     /**
355      * Get the value of proxyPassword.
356      *
357      * @return the value of proxyPassword
358      */
359     public String getProxyPassword() {
360         return proxyPassword;
361     }
362 
363     /**
364      * Set the value of proxyPassword.
365      *
366      * @param proxyPassword new value of proxyPassword
367      */
368     public void setProxyPassword(String proxyPassword) {
369         this.proxyPassword = proxyPassword;
370     }
371     /**
372      * The Connection Timeout.
373      */
374     private String connectionTimeout;
375 
376     /**
377      * Get the value of connectionTimeout.
378      *
379      * @return the value of connectionTimeout
380      */
381     public String getConnectionTimeout() {
382         return connectionTimeout;
383     }
384 
385     /**
386      * Set the value of connectionTimeout.
387      *
388      * @param connectionTimeout new value of connectionTimeout
389      */
390     public void setConnectionTimeout(String connectionTimeout) {
391         this.connectionTimeout = connectionTimeout;
392     }
393     /**
394      * The file path used for verbose logging.
395      */
396     private String logFile = null;
397 
398     /**
399      * Get the value of logFile.
400      *
401      * @return the value of logFile
402      */
403     public String getLogFile() {
404         return logFile;
405     }
406 
407     /**
408      * Set the value of logFile.
409      *
410      * @param logFile new value of logFile
411      */
412     public void setLogFile(String logFile) {
413         this.logFile = logFile;
414     }
415     /**
416      * The path to the suppression file.
417      */
418     private String suppressionFile;
419 
420     /**
421      * Get the value of suppressionFile.
422      *
423      * @return the value of suppressionFile
424      */
425     public String getSuppressionFile() {
426         return suppressionFile;
427     }
428 
429     /**
430      * Set the value of suppressionFile.
431      *
432      * @param suppressionFile new value of suppressionFile
433      */
434     public void setSuppressionFile(String suppressionFile) {
435         this.suppressionFile = suppressionFile;
436     }
437     /**
438      * flag indicating whether or not to show a summary of findings.
439      */
440     private boolean showSummary = true;
441 
442     /**
443      * Get the value of showSummary.
444      *
445      * @return the value of showSummary
446      */
447     public boolean isShowSummary() {
448         return showSummary;
449     }
450 
451     /**
452      * Set the value of showSummary.
453      *
454      * @param showSummary new value of showSummary
455      */
456     public void setShowSummary(boolean showSummary) {
457         this.showSummary = showSummary;
458     }
459 
460     /**
461      * Whether or not the nexus analyzer is enabled.
462      */
463     private boolean nexusAnalyzerEnabled = true;
464 
465     /**
466      * Get the value of nexusAnalyzerEnabled.
467      *
468      * @return the value of nexusAnalyzerEnabled
469      */
470     public boolean isNexusAnalyzerEnabled() {
471         return nexusAnalyzerEnabled;
472     }
473 
474     /**
475      * Set the value of nexusAnalyzerEnabled.
476      *
477      * @param nexusAnalyzerEnabled new value of nexusAnalyzerEnabled
478      */
479     public void setNexusAnalyzerEnabled(boolean nexusAnalyzerEnabled) {
480         this.nexusAnalyzerEnabled = nexusAnalyzerEnabled;
481     }
482 
483     /**
484      * The URL of the Nexus server.
485      */
486     private String nexusUrl;
487 
488     /**
489      * Get the value of nexusUrl.
490      *
491      * @return the value of nexusUrl
492      */
493     public String getNexusUrl() {
494         return nexusUrl;
495     }
496 
497     /**
498      * Set the value of nexusUrl.
499      *
500      * @param nexusUrl new value of nexusUrl
501      */
502     public void setNexusUrl(String nexusUrl) {
503         this.nexusUrl = nexusUrl;
504     }
505 
506     /**
507      * The database driver name; such as org.h2.Driver.
508      */
509     private String databaseDriverName;
510 
511     /**
512      * Get the value of databaseDriverName.
513      *
514      * @return the value of databaseDriverName
515      */
516     public String getDatabaseDriverName() {
517         return databaseDriverName;
518     }
519 
520     /**
521      * Set the value of databaseDriverName.
522      *
523      * @param databaseDriverName new value of databaseDriverName
524      */
525     public void setDatabaseDriverName(String databaseDriverName) {
526         this.databaseDriverName = databaseDriverName;
527     }
528 
529     /**
530      * The path to the database driver JAR file if it is not on the class path.
531      */
532     private String databaseDriverPath;
533 
534     /**
535      * Get the value of databaseDriverPath.
536      *
537      * @return the value of databaseDriverPath
538      */
539     public String getDatabaseDriverPath() {
540         return databaseDriverPath;
541     }
542 
543     /**
544      * Set the value of databaseDriverPath.
545      *
546      * @param databaseDriverPath new value of databaseDriverPath
547      */
548     public void setDatabaseDriverPath(String databaseDriverPath) {
549         this.databaseDriverPath = databaseDriverPath;
550     }
551     /**
552      * The database connection string.
553      */
554     private String connectionString;
555 
556     /**
557      * Get the value of connectionString.
558      *
559      * @return the value of connectionString
560      */
561     public String getConnectionString() {
562         return connectionString;
563     }
564 
565     /**
566      * Set the value of connectionString.
567      *
568      * @param connectionString new value of connectionString
569      */
570     public void setConnectionString(String connectionString) {
571         this.connectionString = connectionString;
572     }
573     /**
574      * The user name for connecting to the database.
575      */
576     private String databaseUser;
577 
578     /**
579      * Get the value of databaseUser.
580      *
581      * @return the value of databaseUser
582      */
583     public String getDatabaseUser() {
584         return databaseUser;
585     }
586 
587     /**
588      * Set the value of databaseUser.
589      *
590      * @param databaseUser new value of databaseUser
591      */
592     public void setDatabaseUser(String databaseUser) {
593         this.databaseUser = databaseUser;
594     }
595 
596     /**
597      * The password to use when connecting to the database.
598      */
599     private String databasePassword;
600 
601     /**
602      * Get the value of databasePassword.
603      *
604      * @return the value of databasePassword
605      */
606     public String getDatabasePassword() {
607         return databasePassword;
608     }
609 
610     /**
611      * Set the value of databasePassword.
612      *
613      * @param databasePassword new value of databasePassword
614      */
615     public void setDatabasePassword(String databasePassword) {
616         this.databasePassword = databasePassword;
617     }
618 
619     @Override
620     public void execute() throws BuildException {
621         final InputStream in = DependencyCheckTask.class.getClassLoader().getResourceAsStream(LOG_PROPERTIES_FILE);
622         LogUtils.prepareLogger(in, logFile);
623 
624         dealWithReferences();
625         validateConfiguration();
626         populateSettings();
627 
628         final Engine engine = new Engine();
629         for (Resource resource : path) {
630             final FileProvider provider = resource.as(FileProvider.class);
631             if (provider != null) {
632                 final File file = provider.getFile();
633                 if (file != null && file.exists()) {
634                     engine.scan(file);
635                 }
636             }
637         }
638         try {
639             engine.analyzeDependencies();
640             DatabaseProperties prop = null;
641             CveDB cve = null;
642             try {
643                 cve = new CveDB();
644                 cve.open();
645                 prop = cve.getDatabaseProperties();
646             } catch (DatabaseException ex) {
647                 Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, "Unable to retrieve DB Properties", ex);
648             } finally {
649                 if (cve != null) {
650                     cve.close();
651                 }
652             }
653             final ReportGenerator reporter = new ReportGenerator(applicationName, engine.getDependencies(), engine.getAnalyzers(), prop);
654             reporter.generateReports(reportOutputDirectory, reportFormat);
655 
656             if (this.failBuildOnCVSS <= 10) {
657                 checkForFailure(engine.getDependencies());
658             }
659             if (this.showSummary) {
660                 showSummary(engine.getDependencies());
661             }
662         } catch (IOException ex) {
663             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, "Unable to generate dependency-check report", ex);
664             throw new BuildException("Unable to generate dependency-check report", ex);
665         } catch (Exception ex) {
666             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, "An exception occurred; unable to continue task", ex);
667             throw new BuildException("An exception occurred; unable to continue task", ex);
668         }
669     }
670 
671     /**
672      * Validate the configuration to ensure the parameters have been properly configured/initialized.
673      *
674      * @throws BuildException if the task was not configured correctly.
675      */
676     private void validateConfiguration() throws BuildException {
677         if (path == null) {
678             throw new BuildException("No project dependencies have been defined to analyze.");
679         }
680         if (failBuildOnCVSS < 0 || failBuildOnCVSS > 11) {
681             throw new BuildException("Invalid configuration, failBuildOnCVSS must be between 0 and 11.");
682         }
683     }
684 
685     /**
686      * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system
687      * properties required to change the proxy url, port, and connection timeout.
688      */
689     private void populateSettings() {
690         InputStream taskProperties = null;
691         try {
692             taskProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
693             Settings.mergeProperties(taskProperties);
694         } catch (IOException ex) {
695             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.WARNING, "Unable to load the dependency-check ant task.properties file.");
696             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINE, null, ex);
697         } finally {
698             if (taskProperties != null) {
699                 try {
700                     taskProperties.close();
701                 } catch (IOException ex) {
702                     Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.FINEST, null, ex);
703                 }
704             }
705         }
706         if (dataDirectory != null) {
707             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
708         } else {
709             final File jarPath = new File(DependencyCheckTask.class.getProtectionDomain().getCodeSource().getLocation().getPath());
710             final File base = jarPath.getParentFile();
711             final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
712             final File dataDir = new File(base, sub);
713             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
714         }
715 
716         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
717 
718         if (proxyUrl != null && !proxyUrl.isEmpty()) {
719             Settings.setString(Settings.KEYS.PROXY_URL, proxyUrl);
720         }
721         if (proxyPort != null && !proxyPort.isEmpty()) {
722             Settings.setString(Settings.KEYS.PROXY_PORT, proxyPort);
723         }
724         if (proxyUsername != null && !proxyUsername.isEmpty()) {
725             Settings.setString(Settings.KEYS.PROXY_USERNAME, proxyUsername);
726         }
727         if (proxyPassword != null && !proxyPassword.isEmpty()) {
728             Settings.setString(Settings.KEYS.PROXY_PASSWORD, proxyPassword);
729         }
730         if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
731             Settings.setString(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
732         }
733         if (suppressionFile != null && !suppressionFile.isEmpty()) {
734             Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
735         }
736         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled);
737         if (nexusUrl != null && !nexusUrl.isEmpty()) {
738             Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
739         }
740         if (databaseDriverName != null && !databaseDriverName.isEmpty()) {
741             Settings.setString(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
742         }
743         if (databaseDriverPath != null && !databaseDriverPath.isEmpty()) {
744             Settings.setString(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
745         }
746         if (connectionString != null && !connectionString.isEmpty()) {
747             Settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
748         }
749         if (databaseUser != null && !databaseUser.isEmpty()) {
750             Settings.setString(Settings.KEYS.DB_USER, databaseUser);
751         }
752         if (databasePassword != null && !databasePassword.isEmpty()) {
753             Settings.setString(Settings.KEYS.DB_PASSWORD, databasePassword);
754         }
755     }
756 
757     /**
758      * Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the
759      * configuration.
760      *
761      * @param dependencies the list of dependency objects
762      * @throws BuildException thrown if a CVSS score is found that is higher then the threshold set
763      */
764     private void checkForFailure(List<Dependency> dependencies) throws BuildException {
765         final StringBuilder ids = new StringBuilder();
766         for (Dependency d : dependencies) {
767             for (Vulnerability v : d.getVulnerabilities()) {
768                 if (v.getCvssScore() >= failBuildOnCVSS) {
769                     if (ids.length() == 0) {
770                         ids.append(v.getName());
771                     } else {
772                         ids.append(", ").append(v.getName());
773                     }
774                 }
775             }
776         }
777         if (ids.length() > 0) {
778             final String msg = String.format("%n%nDependency-Check Failure:%n"
779                     + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '%.1f': %s%n"
780                     + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids.toString());
781             throw new BuildException(msg);
782         }
783     }
784 
785     /**
786      * Generates a warning message listing a summary of dependencies and their associated CPE and CVE entries.
787      *
788      * @param dependencies a list of dependency objects
789      */
790     private void showSummary(List<Dependency> dependencies) {
791         final StringBuilder summary = new StringBuilder();
792         for (Dependency d : dependencies) {
793             boolean firstEntry = true;
794             final StringBuilder ids = new StringBuilder();
795             for (Vulnerability v : d.getVulnerabilities()) {
796                 if (firstEntry) {
797                     firstEntry = false;
798                 } else {
799                     ids.append(", ");
800                 }
801                 ids.append(v.getName());
802             }
803             if (ids.length() > 0) {
804                 summary.append(d.getFileName()).append(" (");
805                 firstEntry = true;
806                 for (Identifier id : d.getIdentifiers()) {
807                     if (firstEntry) {
808                         firstEntry = false;
809                     } else {
810                         summary.append(", ");
811                     }
812                     summary.append(id.getValue());
813                 }
814                 summary.append(") : ").append(ids).append(NEW_LINE);
815             }
816         }
817         if (summary.length() > 0) {
818             final String msg = String.format("%n%n"
819                     + "One or more dependencies were identified with known vulnerabilities:%n%n%s"
820                     + "%n%nSee the dependency-check report for more details.%n%n", summary.toString());
821             Logger.getLogger(DependencyCheckTask.class.getName()).log(Level.WARNING, msg);
822         }
823     }
824 
825     /**
826      * An enumeration of supported report formats: "ALL", "HTML", "XML", "VULN", etc..
827      */
828     public static class ReportFormats extends EnumeratedAttribute {
829 
830         /**
831          * Returns the list of values for the report format.
832          *
833          * @return the list of values for the report format
834          */
835         @Override
836         public String[] getValues() {
837             int i = 0;
838             final Format[] formats = Format.values();
839             final String[] values = new String[formats.length];
840             for (Format format : formats) {
841                 values[i++] = format.name();
842             }
843             return values;
844         }
845     }
846 }