View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2014 Steve Springett. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.agent;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.util.List;
23  import java.util.stream.Collectors;
24  import java.util.stream.Stream;
25  import javax.annotation.concurrent.NotThreadSafe;
26  import org.owasp.dependencycheck.Engine;
27  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
28  import org.owasp.dependencycheck.data.update.exception.UpdateException;
29  import org.owasp.dependencycheck.dependency.Dependency;
30  import org.owasp.dependencycheck.dependency.Vulnerability;
31  import org.owasp.dependencycheck.dependency.naming.Identifier;
32  import org.owasp.dependencycheck.exception.ExceptionCollection;
33  import org.owasp.dependencycheck.exception.ReportException;
34  import org.owasp.dependencycheck.exception.ScanAgentException;
35  import org.owasp.dependencycheck.reporting.ReportGenerator;
36  import org.owasp.dependencycheck.utils.Settings;
37  import org.owasp.dependencycheck.utils.SeverityUtil;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * This class provides a way to easily conduct a scan solely based on existing
43   * evidence metadata rather than collecting evidence from the files themselves.
44   * This class is based on the Ant task and Maven plugin with the exception that
45   * it takes a list of dependencies that can be programmatically added from data
46   * in a spreadsheet, database or some other datasource and conduct a scan based
47   * on this pre-defined evidence.
48   *
49   * <h2>Example:</h2>
50   * <pre>
51   * List&lt;Dependency&gt; dependencies = new ArrayList&lt;Dependency&gt;();
52   * Dependency dependency = new Dependency(new File(FileUtils.getBitBucket()));
53   * dependency.addEvidence(EvidenceType.PRODUCT, "my-datasource", "name", "Jetty", Confidence.HIGH);
54   * dependency.addEvidence(EvidenceType.VERSION, "my-datasource", "version", "5.1.10", Confidence.HIGH);
55   * dependency.addEvidence(EvidenceType.VENDOR, "my-datasource", "vendor", "mortbay", Confidence.HIGH);
56   * dependencies.add(dependency);
57   *
58   * DependencyCheckScanAgent scan = new DependencyCheckScanAgent();
59   * scan.setDependencies(dependencies);
60   * scan.setReportFormat(ReportGenerator.Format.ALL);
61   * scan.setReportOutputDirectory(System.getProperty("user.home"));
62   * scan.execute();
63   * </pre>
64   *
65   * @author Steve Springett
66   */
67  @SuppressWarnings("unused")
68  @NotThreadSafe
69  public class DependencyCheckScanAgent {
70  
71      //<editor-fold defaultstate="collapsed" desc="private fields">
72      /**
73       * System specific new line character.
74       */
75      private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern();
76      /**
77       * Logger for use throughout the class.
78       */
79      private static final Logger LOGGER = LoggerFactory.getLogger(DependencyCheckScanAgent.class);
80      /**
81       * The application name for the report.
82       */
83      private String applicationName = "Dependency-Check";
84      /**
85       * The pre-determined dependencies to scan
86       */
87      private List<Dependency> dependencies;
88      /**
89       * The location of the data directory that contains
90       */
91      private String dataDirectory = null;
92      /**
93       * Specifies the destination directory for the generated Dependency-Check
94       * report.
95       */
96      private String reportOutputDirectory;
97      /**
98       * Specifies if the build should be failed if a CVSS score above a specified
99       * level is identified. The default is 11 which means since the CVSS scores
100      * are 0-10, by default the build will never fail and the CVSS score is set
101      * to 11. The valid range for the fail build on CVSS is 0 to 11, where
102      * anything above 10 will not cause the build to fail.
103      */
104     private Double failBuildOnCVSS = 11.0;
105     /**
106      * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not
107      * recommended that this be turned to false. Default is true.
108      */
109     private boolean autoUpdate = true;
110     /**
111      * The NVD API key.
112      */
113     private String nvdApiKey;
114 
115     /**
116      * Sets whether the data directory should be updated without performing a
117      * scan. Default is false.
118      */
119     private boolean updateOnly = false;
120     /**
121      * flag indicating whether to generate a report of findings.
122      */
123     private boolean generateReport = true;
124     /**
125      * The report format to be generated (HTML, XML, CSV, JSON, JUNIT, SARIF,
126      * JENKINS, GITLAB, ALL). This configuration option has no affect if using
127      * this within the Site plugin unless the externalReport is set to true.
128      * Default is HTML.
129      */
130     private ReportGenerator.Format reportFormat = ReportGenerator.Format.HTML;
131     /**
132      * The Proxy Server.
133      */
134     private String proxyServer;
135     /**
136      * The Proxy Port.
137      */
138     private String proxyPort;
139     /**
140      * The Proxy username.
141      */
142     private String proxyUsername;
143     /**
144      * The Proxy password.
145      */
146     private String proxyPassword;
147     /**
148      * The Connection Timeout.
149      */
150     private String connectionTimeout;
151     /**
152      * The Connection Read Timeout.
153      */
154     private String readTimeout;
155     /**
156      * The file path used for verbose logging.
157      */
158     private String logFile = null;
159     /**
160      * flag indicating whether to show a summary of findings.
161      */
162     private boolean showSummary = true;
163     /**
164      * The path to the suppression file.
165      */
166     private String suppressionFile;
167     /**
168      * The password to use when connecting to the database.
169      */
170     private String databasePassword;
171     /**
172      * The starting string that identifies CPEs that are qualified to be
173      * imported.
174      */
175     private String cpeStartsWithFilter;
176     /**
177      * Whether the Maven Central analyzer is enabled.
178      */
179     private boolean centralAnalyzerEnabled = true;
180     /**
181      * Whether the build should fail if there are unused suppression rules.
182      */
183     private boolean failOnUnusedSuppressionRule = false;
184     /**
185      * The URL of Maven Central.
186      */
187     private String centralUrl;
188     /**
189      * Whether the nexus analyzer is enabled.
190      */
191     private boolean nexusAnalyzerEnabled = true;
192     /**
193      * The URL of the Nexus server.
194      */
195     private String nexusUrl;
196     /**
197      * Whether the defined proxy should be used when connecting to Nexus.
198      */
199     private boolean nexusUsesProxy = true;
200     /**
201      * The database driver name; such as org.h2.Driver.
202      */
203     private String databaseDriverName;
204     /**
205      * The path to the database driver JAR file if it is not on the class path.
206      */
207     private String databaseDriverPath;
208     /**
209      * The database connection string.
210      */
211     private String connectionString;
212     /**
213      * The username for connecting to the database.
214      */
215     private String databaseUser;
216     /**
217      * Additional ZIP File extensions to add analyze. This should be a
218      * comma-separated list of file extensions to treat like ZIP files.
219      */
220     private String zipExtensions;
221     /**
222      * The path to dotnet core for .NET assembly analysis.
223      */
224     private String pathToCore;
225     /**
226      * The configured settings.
227      */
228     private Settings settings;
229     /**
230      * The path to optional dependency-check properties file. This will be used
231      * to side-load additional user-defined properties.
232      * {@link Settings#mergeProperties(String)}
233      */
234     private String propertiesFilePath;
235     //</editor-fold>
236     //<editor-fold defaultstate="collapsed" desc="getters/setters">
237 
238     /**
239      * Get the value of applicationName.
240      *
241      * @return the value of applicationName
242      */
243     public String getApplicationName() {
244         return applicationName;
245     }
246 
247     /**
248      * Set the value of applicationName.
249      *
250      * @param applicationName new value of applicationName
251      */
252     public void setApplicationName(String applicationName) {
253         this.applicationName = applicationName;
254     }
255 
256     /**
257      * Get the value of nvdApiKey.
258      *
259      * @return the value of nvdApiKey
260      */
261     public String getNvdApiKey() {
262         return nvdApiKey;
263     }
264 
265     /**
266      * Set the value of nvdApiKey.
267      *
268      * @param nvdApiKey new value of nvdApiKey
269      */
270     public void setNvdApiKey(String nvdApiKey) {
271         this.nvdApiKey = nvdApiKey;
272     }
273 
274     /**
275      * Returns a list of pre-determined dependencies.
276      *
277      * @return returns a list of dependencies
278      */
279     public List<Dependency> getDependencies() {
280         return dependencies;
281     }
282 
283     /**
284      * Sets the list of dependencies to scan.
285      *
286      * @param dependencies new value of dependencies
287      */
288     public void setDependencies(List<Dependency> dependencies) {
289         this.dependencies = dependencies;
290     }
291 
292     /**
293      * Get the value of dataDirectory.
294      *
295      * @return the value of dataDirectory
296      */
297     public String getDataDirectory() {
298         return dataDirectory;
299     }
300 
301     /**
302      * Set the value of dataDirectory.
303      *
304      * @param dataDirectory new value of dataDirectory
305      */
306     public void setDataDirectory(String dataDirectory) {
307         this.dataDirectory = dataDirectory;
308     }
309 
310     /**
311      * Get the value of reportOutputDirectory.
312      *
313      * @return the value of reportOutputDirectory
314      */
315     public String getReportOutputDirectory() {
316         return reportOutputDirectory;
317     }
318 
319     /**
320      * Set the value of reportOutputDirectory.
321      *
322      * @param reportOutputDirectory new value of reportOutputDirectory
323      */
324     public void setReportOutputDirectory(String reportOutputDirectory) {
325         this.reportOutputDirectory = reportOutputDirectory;
326     }
327 
328     /**
329      * Get the value of failBuildOnCVSS.
330      *
331      * @return the value of failBuildOnCVSS
332      */
333     public Double getFailBuildOnCVSS() {
334         return failBuildOnCVSS;
335     }
336 
337     /**
338      * Set the value of failBuildOnCVSS.
339      *
340      * @param failBuildOnCVSS new value of failBuildOnCVSS
341      */
342     public void setFailBuildOnCVSS(Double failBuildOnCVSS) {
343         this.failBuildOnCVSS = failBuildOnCVSS;
344     }
345 
346     /**
347      * Get the value of autoUpdate.
348      *
349      * @return the value of autoUpdate
350      */
351     public boolean isAutoUpdate() {
352         return autoUpdate;
353     }
354 
355     /**
356      * Set the value of autoUpdate.
357      *
358      * @param autoUpdate new value of autoUpdate
359      */
360     public void setAutoUpdate(boolean autoUpdate) {
361         this.autoUpdate = autoUpdate;
362     }
363 
364     /**
365      * Get the value of updateOnly.
366      *
367      * @return the value of updateOnly
368      */
369     public boolean isUpdateOnly() {
370         return updateOnly;
371     }
372 
373     /**
374      * Set the value of updateOnly.
375      *
376      * @param updateOnly new value of updateOnly
377      */
378     public void setUpdateOnly(boolean updateOnly) {
379         this.updateOnly = updateOnly;
380     }
381 
382     /**
383      * Get the value of generateReport.
384      *
385      * @return the value of generateReport
386      */
387     public boolean isGenerateReport() {
388         return generateReport;
389     }
390 
391     /**
392      * Set the value of generateReport.
393      *
394      * @param generateReport new value of generateReport
395      */
396     public void setGenerateReport(boolean generateReport) {
397         this.generateReport = generateReport;
398     }
399 
400     /**
401      * Get the value of reportFormat.
402      *
403      * @return the value of reportFormat
404      */
405     public ReportGenerator.Format getReportFormat() {
406         return reportFormat;
407     }
408 
409     /**
410      * Set the value of reportFormat.
411      *
412      * @param reportFormat new value of reportFormat
413      */
414     public void setReportFormat(ReportGenerator.Format reportFormat) {
415         this.reportFormat = reportFormat;
416     }
417 
418     /**
419      * Get the value of proxyServer.
420      *
421      * @return the value of proxyServer
422      */
423     public String getProxyServer() {
424         return proxyServer;
425     }
426 
427     /**
428      * Set the value of proxyServer.
429      *
430      * @param proxyServer new value of proxyServer
431      */
432     public void setProxyServer(String proxyServer) {
433         this.proxyServer = proxyServer;
434     }
435 
436     /**
437      * Get the value of proxyServer.
438      *
439      * @return the value of proxyServer
440      * @deprecated use
441      * {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#getProxyServer()}
442      * instead
443      */
444     @Deprecated
445     public String getProxyUrl() {
446         return proxyServer;
447     }
448 
449     /**
450      * Set the value of proxyServer.
451      *
452      * @param proxyUrl new value of proxyServer
453      * @deprecated use {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#setProxyServer(java.lang.String)
454      * } instead
455      */
456     @Deprecated
457     public void setProxyUrl(String proxyUrl) {
458         this.proxyServer = proxyUrl;
459     }
460 
461     /**
462      * Get the value of proxyPort.
463      *
464      * @return the value of proxyPort
465      */
466     public String getProxyPort() {
467         return proxyPort;
468     }
469 
470     /**
471      * Set the value of proxyPort.
472      *
473      * @param proxyPort new value of proxyPort
474      */
475     public void setProxyPort(String proxyPort) {
476         this.proxyPort = proxyPort;
477     }
478 
479     /**
480      * Get the value of proxyUsername.
481      *
482      * @return the value of proxyUsername
483      */
484     public String getProxyUsername() {
485         return proxyUsername;
486     }
487 
488     /**
489      * Set the value of proxyUsername.
490      *
491      * @param proxyUsername new value of proxyUsername
492      */
493     public void setProxyUsername(String proxyUsername) {
494         this.proxyUsername = proxyUsername;
495     }
496 
497     /**
498      * Get the value of proxyPassword.
499      *
500      * @return the value of proxyPassword
501      */
502     public String getProxyPassword() {
503         return proxyPassword;
504     }
505 
506     /**
507      * Set the value of proxyPassword.
508      *
509      * @param proxyPassword new value of proxyPassword
510      */
511     public void setProxyPassword(String proxyPassword) {
512         this.proxyPassword = proxyPassword;
513     }
514 
515     /**
516      * Get the value of connectionTimeout.
517      *
518      * @return the value of connectionTimeout
519      */
520     public String getConnectionTimeout() {
521         return connectionTimeout;
522     }
523 
524     /**
525      * Set the value of connectionTimeout.
526      *
527      * @param connectionTimeout new value of connectionTimeout
528      */
529     public void setConnectionTimeout(String connectionTimeout) {
530         this.connectionTimeout = connectionTimeout;
531     }
532 
533     /**
534      * Get the value of readTimeout.
535      *
536      * @return the value of readTimeout
537      */
538     public String getReadTimeout() {
539         return readTimeout;
540     }
541 
542     /**
543      * Set the value of readTimeout.
544      *
545      * @param readTimeout new value of readTimeout
546      */
547     public void setReadTimeout(String readTimeout) {
548         this.readTimeout = readTimeout;
549     }
550 
551     /**
552      * Get the value of logFile.
553      *
554      * @return the value of logFile
555      */
556     public String getLogFile() {
557         return logFile;
558     }
559 
560     /**
561      * Set the value of logFile.
562      *
563      * @param logFile new value of logFile
564      */
565     public void setLogFile(String logFile) {
566         this.logFile = logFile;
567     }
568 
569     /**
570      * Get the value of suppressionFile.
571      *
572      * @return the value of suppressionFile
573      */
574     public String getSuppressionFile() {
575         return suppressionFile;
576     }
577 
578     /**
579      * Set the value of suppressionFile.
580      *
581      * @param suppressionFile new value of suppressionFile
582      */
583     public void setSuppressionFile(String suppressionFile) {
584         this.suppressionFile = suppressionFile;
585     }
586 
587     /**
588      * Get the value of showSummary.
589      *
590      * @return the value of showSummary
591      */
592     public boolean isShowSummary() {
593         return showSummary;
594     }
595 
596     /**
597      * Set the value of showSummary.
598      *
599      * @param showSummary new value of showSummary
600      */
601     public void setShowSummary(boolean showSummary) {
602         this.showSummary = showSummary;
603     }
604 
605     /**
606      * Sets starting string that identifies CPEs that are qualified to be
607      * imported.
608      *
609      * @param cpeStartsWithFilter filters CPEs based on this starting string
610      * (i.e. cpe:/a: )
611      */
612     public void setCpeStartsWithFilter(String cpeStartsWithFilter) {
613         this.cpeStartsWithFilter = cpeStartsWithFilter;
614     }
615 
616     /**
617      * Returns the starting string that identifies CPEs that are qualified to be
618      * imported.
619      *
620      * @return the CPE starting filter (i.e. cpe:/a: )
621      */
622     public String getCpeStartsWithFilter() {
623         return cpeStartsWithFilter;
624     }
625 
626     /**
627      * Get the value of failOnUnusedSuppressionRule.
628      *
629      * @return the value of failOnUnusedSuppressionRule
630      */
631     public boolean isFailOnUnusedSuppressionRule() {
632         return failOnUnusedSuppressionRule;
633     }
634 
635     /**
636      * Set the value of failOnUnusedSuppressionRule.
637      *
638      * @param failOnUnusedSuppressionRule new value of failOnUnusedSuppressionRule
639      */
640     public void setFailOnUnusedSuppressionRule(boolean failOnUnusedSuppressionRule) {
641         this.failOnUnusedSuppressionRule = failOnUnusedSuppressionRule;
642     }
643 
644     /**
645      * Get the value of centralAnalyzerEnabled.
646      *
647      * @return the value of centralAnalyzerEnabled
648      */
649     public boolean isCentralAnalyzerEnabled() {
650         return centralAnalyzerEnabled;
651     }
652 
653     /**
654      * Set the value of centralAnalyzerEnabled.
655      *
656      * @param centralAnalyzerEnabled new value of centralAnalyzerEnabled
657      */
658     public void setCentralAnalyzerEnabled(boolean centralAnalyzerEnabled) {
659         this.centralAnalyzerEnabled = centralAnalyzerEnabled;
660     }
661 
662     /**
663      * Get the value of centralUrl.
664      *
665      * @return the value of centralUrl
666      */
667     public String getCentralUrl() {
668         return centralUrl;
669     }
670 
671     /**
672      * Set the value of centralUrl.
673      *
674      * @param centralUrl new value of centralUrl
675      */
676     public void setCentralUrl(String centralUrl) {
677         this.centralUrl = centralUrl;
678     }
679 
680     /**
681      * Get the value of nexusAnalyzerEnabled.
682      *
683      * @return the value of nexusAnalyzerEnabled
684      */
685     public boolean isNexusAnalyzerEnabled() {
686         return nexusAnalyzerEnabled;
687     }
688 
689     /**
690      * Set the value of nexusAnalyzerEnabled.
691      *
692      * @param nexusAnalyzerEnabled new value of nexusAnalyzerEnabled
693      */
694     public void setNexusAnalyzerEnabled(boolean nexusAnalyzerEnabled) {
695         this.nexusAnalyzerEnabled = nexusAnalyzerEnabled;
696     }
697 
698     /**
699      * Get the value of nexusUrl.
700      *
701      * @return the value of nexusUrl
702      */
703     public String getNexusUrl() {
704         return nexusUrl;
705     }
706 
707     /**
708      * Set the value of nexusUrl.
709      *
710      * @param nexusUrl new value of nexusUrl
711      */
712     public void setNexusUrl(String nexusUrl) {
713         this.nexusUrl = nexusUrl;
714     }
715 
716     /**
717      * Get the value of nexusUsesProxy.
718      *
719      * @return the value of nexusUsesProxy
720      */
721     public boolean isNexusUsesProxy() {
722         return nexusUsesProxy;
723     }
724 
725     /**
726      * Set the value of nexusUsesProxy.
727      *
728      * @param nexusUsesProxy new value of nexusUsesProxy
729      */
730     public void setNexusUsesProxy(boolean nexusUsesProxy) {
731         this.nexusUsesProxy = nexusUsesProxy;
732     }
733 
734     /**
735      * Get the value of databaseDriverName.
736      *
737      * @return the value of databaseDriverName
738      */
739     public String getDatabaseDriverName() {
740         return databaseDriverName;
741     }
742 
743     /**
744      * Set the value of databaseDriverName.
745      *
746      * @param databaseDriverName new value of databaseDriverName
747      */
748     public void setDatabaseDriverName(String databaseDriverName) {
749         this.databaseDriverName = databaseDriverName;
750     }
751 
752     /**
753      * Get the value of databaseDriverPath.
754      *
755      * @return the value of databaseDriverPath
756      */
757     public String getDatabaseDriverPath() {
758         return databaseDriverPath;
759     }
760 
761     /**
762      * Set the value of databaseDriverPath.
763      *
764      * @param databaseDriverPath new value of databaseDriverPath
765      */
766     public void setDatabaseDriverPath(String databaseDriverPath) {
767         this.databaseDriverPath = databaseDriverPath;
768     }
769 
770     /**
771      * Get the value of connectionString.
772      *
773      * @return the value of connectionString
774      */
775     public String getConnectionString() {
776         return connectionString;
777     }
778 
779     /**
780      * Set the value of connectionString.
781      *
782      * @param connectionString new value of connectionString
783      */
784     public void setConnectionString(String connectionString) {
785         this.connectionString = connectionString;
786     }
787 
788     /**
789      * Get the value of databaseUser.
790      *
791      * @return the value of databaseUser
792      */
793     public String getDatabaseUser() {
794         return databaseUser;
795     }
796 
797     /**
798      * Set the value of databaseUser.
799      *
800      * @param databaseUser new value of databaseUser
801      */
802     public void setDatabaseUser(String databaseUser) {
803         this.databaseUser = databaseUser;
804     }
805 
806     /**
807      * Get the value of databasePassword.
808      *
809      * @return the value of databasePassword
810      */
811     public String getDatabasePassword() {
812         return databasePassword;
813     }
814 
815     /**
816      * Set the value of databasePassword.
817      *
818      * @param databasePassword new value of databasePassword
819      */
820     public void setDatabasePassword(String databasePassword) {
821         this.databasePassword = databasePassword;
822     }
823 
824     /**
825      * Get the value of zipExtensions.
826      *
827      * @return the value of zipExtensions
828      */
829     public String getZipExtensions() {
830         return zipExtensions;
831     }
832 
833     /**
834      * Set the value of zipExtensions.
835      *
836      * @param zipExtensions new value of zipExtensions
837      */
838     public void setZipExtensions(String zipExtensions) {
839         this.zipExtensions = zipExtensions;
840     }
841 
842     /**
843      * Get the value of pathToCore.
844      *
845      * @return the value of pathToCore
846      */
847     public String getPathToDotnetCore() {
848         return pathToCore;
849     }
850 
851     /**
852      * Set the value of pathToCore.
853      *
854      * @param pathToCore new value of pathToCore
855      */
856     public void setPathToDotnetCore(String pathToCore) {
857         this.pathToCore = pathToCore;
858     }
859 
860     /**
861      * Get the value of propertiesFilePath.
862      *
863      * @return the value of propertiesFilePath
864      */
865     public String getPropertiesFilePath() {
866         return propertiesFilePath;
867     }
868 
869     /**
870      * Set the value of propertiesFilePath.
871      *
872      * @param propertiesFilePath new value of propertiesFilePath
873      */
874     public void setPropertiesFilePath(String propertiesFilePath) {
875         this.propertiesFilePath = propertiesFilePath;
876     }
877     //</editor-fold>
878 
879     /**
880      * Executes the Dependency-Check on the dependent libraries. <b>Note</b>,
881      * the engine object returned from this method must be closed by calling
882      * `close()`
883      *
884      * @return the Engine used to scan the dependencies.
885      * @throws ExceptionCollection a collection of one or more exceptions that
886      * occurred during analysis.
887      */
888     @SuppressWarnings("squid:S2095")
889     private Engine executeDependencyCheck() throws ExceptionCollection {
890         populateSettings();
891         final Engine engine;
892         try {
893             engine = new Engine(settings);
894         } catch (DatabaseException ex) {
895             throw new ExceptionCollection(ex, true);
896         }
897         if (this.updateOnly) {
898             try {
899                 engine.doUpdates();
900             } catch (UpdateException ex) {
901                 throw new ExceptionCollection(ex);
902             } finally {
903                 engine.close();
904             }
905         } else {
906             engine.setDependencies(this.dependencies);
907             engine.analyzeDependencies();
908         }
909         return engine;
910     }
911 
912     /**
913      * Generates the reports for a given dependency-check engine.
914      *
915      * @param engine a dependency-check engine
916      * @param outDirectory the directory to write the reports to
917      * @throws ScanAgentException thrown if there is an error generating the
918      * report
919      */
920     private void generateExternalReports(Engine engine, File outDirectory) throws ScanAgentException {
921         try {
922             engine.writeReports(applicationName, outDirectory, this.reportFormat.name(), null);
923         } catch (ReportException ex) {
924             LOGGER.debug("Unexpected exception occurred during analysis; please see the verbose error log for more details.", ex);
925             throw new ScanAgentException("Error generating the report", ex);
926         }
927     }
928 
929     /**
930      * Takes the properties supplied and updates the dependency-check settings.
931      * Additionally, this sets the system properties required to change the
932      * proxy server, port, and connection timeout.
933      */
934     private void populateSettings() {
935         settings = new Settings();
936         if (dataDirectory != null) {
937             settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
938         } else {
939             final File jarPath = new File(DependencyCheckScanAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath());
940             final File base = jarPath.getParentFile();
941             final String sub = settings.getString(Settings.KEYS.DATA_DIRECTORY);
942             final File dataDir = new File(base, sub);
943             settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
944         }
945         if (propertiesFilePath != null) {
946             try {
947                 settings.mergeProperties(propertiesFilePath);
948                 LOGGER.info("Successfully loaded user-defined properties");
949             } catch (IOException e) {
950                 LOGGER.error("Unable to merge user-defined properties", e);
951                 LOGGER.error("Continuing execution");
952             }
953         }
954 
955         settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
956         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_SERVER, proxyServer);
957         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PORT, proxyPort);
958         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME, proxyUsername);
959         settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPassword);
960         settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
961         settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_READ_TIMEOUT, readTimeout);
962         settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
963         settings.setStringIfNotEmpty(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER, cpeStartsWithFilter);
964         settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, centralAnalyzerEnabled);
965         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_CENTRAL_URL, centralUrl);
966         settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, nexusAnalyzerEnabled);
967         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
968         settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
969         settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
970         settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
971         settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
972         settings.setStringIfNotEmpty(Settings.KEYS.DB_USER, databaseUser);
973         settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD, databasePassword);
974         settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, zipExtensions);
975         settings.setStringIfNotEmpty(Settings.KEYS.NVD_API_KEY, nvdApiKey);
976         settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_DOTNET_PATH, pathToCore);
977         settings.setBoolean(Settings.KEYS.FAIL_ON_UNUSED_SUPPRESSION_RULE, failOnUnusedSuppressionRule);
978     }
979 
980     /**
981      * Executes the dependency-check and generates the report.
982      *
983      * @return a reference to the engine used to perform the scan.
984      * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
985      * there is an exception executing the scan.
986      */
987     public Engine execute() throws ScanAgentException {
988         Engine engine = null;
989         try {
990             engine = executeDependencyCheck();
991             if (!this.updateOnly) {
992                 if (this.generateReport) {
993                     generateExternalReports(engine, new File(this.reportOutputDirectory));
994                 }
995                 if (this.showSummary) {
996                     showSummary(engine.getDependencies());
997                 }
998                 if (this.failBuildOnCVSS <= 10.0) {
999                     checkForFailure(engine.getDependencies());
1000                 }
1001             }
1002         } catch (ExceptionCollection ex) {
1003             if (ex.isFatal()) {
1004                 LOGGER.error("A fatal exception occurred during analysis; analysis has stopped. Please see the debug log for more details.");
1005                 LOGGER.debug("", ex);
1006             }
1007             throw new ScanAgentException("One or more exceptions occurred during analysis; please see the debug log for more details.", ex);
1008         } finally {
1009             if (engine != null) {
1010                 engine.close();
1011             }
1012             settings.cleanup(true);
1013         }
1014         return engine;
1015     }
1016 
1017     /**
1018      * Checks to see if a vulnerability has been identified with a CVSS score
1019      * that is above the threshold set in the configuration.
1020      *
1021      * @param dependencies the list of dependency objects
1022      * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
1023      * there is an exception executing the scan.
1024      */
1025     private void checkForFailure(Dependency[] dependencies) throws ScanAgentException {
1026         final StringBuilder ids = new StringBuilder();
1027         for (Dependency d : dependencies) {
1028             boolean addName = true;
1029             for (Vulnerability v : d.getVulnerabilities()) {
1030                 final double cvssV2 = v.getCvssV2() != null && v.getCvssV2().getCvssData() != null
1031                         && v.getCvssV2().getCvssData().getBaseScore() != null ? v.getCvssV2().getCvssData().getBaseScore() : -1;
1032                 final double cvssV3 = v.getCvssV3() != null && v.getCvssV3().getCvssData() != null
1033                         && v.getCvssV3().getCvssData().getBaseScore() != null ? v.getCvssV3().getCvssData().getBaseScore() : -1;
1034                 final double cvssV4 = v.getCvssV4() != null && v.getCvssV4().getCvssData() != null
1035                         && v.getCvssV4().getCvssData().getBaseScore() != null ? v.getCvssV4().getCvssData().getBaseScore() : -1;
1036                 final boolean useUnscored = cvssV2 == -1 && cvssV3 == -1 && cvssV4 == -1;
1037                 final double unscoredCvss = (useUnscored && v.getUnscoredSeverity() != null) ? SeverityUtil.estimateCvssV2(v.getUnscoredSeverity()) : -1;
1038                 if (cvssV2 >= failBuildOnCVSS
1039                         || cvssV3 >= failBuildOnCVSS
1040                         || cvssV4 >= failBuildOnCVSS
1041                         || unscoredCvss >= failBuildOnCVSS
1042                         //safety net to fail on any if for some reason the above misses on 0
1043                         || failBuildOnCVSS <= 0.0f
1044                 ) {
1045                     if (addName) {
1046                         addName = false;
1047                         ids.append(NEW_LINE).append(d.getFileName()).append(" (")
1048                            .append(Stream.concat(d.getSoftwareIdentifiers().stream(), d.getVulnerableSoftwareIdentifiers().stream())
1049                                          .map(Identifier::getValue)
1050                                          .collect(Collectors.joining(", ")))
1051                            .append("): ")
1052                            .append(v.getName());
1053                     } else {
1054                         ids.append(", ").append(v.getName());
1055                     }
1056                 }
1057             }
1058         }
1059         if (ids.length() > 0) {
1060             final String msg;
1061             if (showSummary) {
1062                 msg = String.format("%n%nDependency-Check Failure:%n"
1063                         + "One or more dependencies were identified with vulnerabilities that have a CVSS score greater than or equal to '%.1f': %s%n"
1064                         + "See the dependency-check report for more details.%n%n", failBuildOnCVSS, ids);
1065             } else {
1066                 msg = String.format("%n%nDependency-Check Failure:%n"
1067                         + "One or more dependencies were identified with vulnerabilities.%n%n"
1068                         + "See the dependency-check report for more details.%n%n");
1069             }
1070             throw new ScanAgentException(msg);
1071         }
1072     }
1073 
1074     /**
1075      * Generates a warning message listing a summary of dependencies and their
1076      * associated CPE and CVE entries.
1077      *
1078      * @param dependencies a list of dependency objects
1079      */
1080     public static void showSummary(Dependency[] dependencies) {
1081         showSummary(null, dependencies);
1082     }
1083 
1084     /**
1085      * Generates a warning message listing a summary of dependencies and their
1086      * associated CPE and CVE entries.
1087      *
1088      * @param projectName the name of the project
1089      * @param dependencies a list of dependency objects
1090      */
1091     public static void showSummary(String projectName, Dependency[] dependencies) {
1092         final StringBuilder summary = new StringBuilder();
1093         for (Dependency d : dependencies) {
1094             final String ids = d.getVulnerabilities(true).stream()
1095                     .map(Vulnerability::getName)
1096                     .collect(Collectors.joining(", "));
1097             if (ids.length() > 0) {
1098                 summary.append(d.getFileName()).append(" (");
1099                 summary.append(Stream.concat(d.getSoftwareIdentifiers().stream(), d.getVulnerableSoftwareIdentifiers().stream())
1100                         .map(Identifier::getValue)
1101                         .collect(Collectors.joining(", ")));
1102                 summary.append(") : ").append(ids).append(NEW_LINE);
1103             }
1104         }
1105         if (summary.length() > 0) {
1106             if (projectName == null || projectName.isEmpty()) {
1107                 LOGGER.warn("\n\nOne or more dependencies were identified with known vulnerabilities:\n\n{}\n\n"
1108                         + "See the dependency-check report for more details.\n\n",
1109                         summary);
1110             } else {
1111                 LOGGER.warn("\n\nOne or more dependencies were identified with known vulnerabilities in {}:\n\n{}\n\n"
1112                         + "See the dependency-check report for more details.\n\n",
1113                         projectName,
1114                         summary);
1115             }
1116         }
1117     }
1118 }