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) 2012 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.dependency;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.Serializable;
23  import java.security.NoSuchAlgorithmException;
24  import java.util.ArrayList;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  import java.util.SortedSet;
29  import java.util.TreeSet;
30  
31  import org.apache.commons.lang3.builder.EqualsBuilder;
32  import org.apache.commons.lang3.builder.HashCodeBuilder;
33  import org.owasp.dependencycheck.data.nexus.MavenArtifact;
34  import org.owasp.dependencycheck.utils.Checksum;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * A program dependency. This object is one of the core components within DependencyCheck. It is used to collect information about
40   * the dependency in the form of evidence. The Evidence is then used to determine if there are any known, published,
41   * vulnerabilities associated with the program dependency.
42   *
43   * @author Jeremy Long
44   */
45  public class Dependency implements Serializable, Comparable<Dependency> {
46  
47      /**
48       * The serial version UID for serialization.
49       */
50      private static final long serialVersionUID = 1L;
51      /**
52       * The logger.
53       */
54      private static final Logger LOGGER = LoggerFactory.getLogger(Dependency.class);
55      /**
56       * Used as starting point for generating the value in {@link #hashCode()}.
57       */
58      private static final int MAGIC_HASH_INIT_VALUE = 3;
59      /**
60       * Used as a multiplier for generating the value in {@link #hashCode()}.
61       */
62      private static final int MAGIC_HASH_MULTIPLIER = 47;
63      /**
64       * The actual file path of the dependency on disk.
65       */
66      private String actualFilePath;
67      /**
68       * The file path to display.
69       */
70      private String filePath;
71      /**
72       * The file name of the dependency.
73       */
74      private String fileName;
75      /**
76       * The md5 hash of the dependency.
77       */
78      private String md5sum;
79      /**
80       * The SHA1 hash of the dependency.
81       */
82      private String sha1sum;
83      /**
84       * A list of Identifiers.
85       */
86      private Set<Identifier> identifiers;
87      /**
88       * A collection of vendor evidence.
89       */
90      private final EvidenceCollection vendorEvidence;
91      /**
92       * A collection of product evidence.
93       */
94      private final EvidenceCollection productEvidence;
95      /**
96       * A collection of version evidence.
97       */
98      private final EvidenceCollection versionEvidence;
99  
100     /**
101      * Constructs a new Dependency object.
102      */
103     public Dependency() {
104         vendorEvidence = new EvidenceCollection();
105         productEvidence = new EvidenceCollection();
106         versionEvidence = new EvidenceCollection();
107         identifiers = new TreeSet<Identifier>();
108         vulnerabilities = new TreeSet<Vulnerability>(new VulnerabilityComparator());
109         suppressedIdentifiers = new TreeSet<Identifier>();
110         suppressedVulnerabilities = new TreeSet<Vulnerability>(new VulnerabilityComparator());
111     }
112 
113     /**
114      * Constructs a new Dependency object.
115      *
116      * @param file the File to create the dependency object from.
117      */
118     public Dependency(File file) {
119         this();
120         this.actualFilePath = file.getAbsolutePath();
121         this.filePath = this.actualFilePath;
122         this.fileName = file.getName();
123         determineHashes(file);
124     }
125 
126     /**
127      * Returns the file name of the dependency.
128      *
129      * @return the file name of the dependency
130      */
131     public String getFileName() {
132         return this.fileName;
133     }
134 
135     /**
136      * Returns the file name of the dependency with the backslash escaped for use in JavaScript. This is a complete hack as I
137      * could not get the replace to work in the template itself.
138      *
139      * @return the file name of the dependency with the backslash escaped for use in JavaScript
140      */
141     public String getFileNameForJavaScript() {
142         return this.fileName.replace("\\", "\\\\");
143     }
144 
145     /**
146      * Sets the file name of the dependency.
147      *
148      * @param fileName the file name of the dependency
149      */
150     public void setFileName(String fileName) {
151         this.fileName = fileName;
152     }
153 
154     /**
155      * Sets the actual file path of the dependency on disk.
156      *
157      * @param actualFilePath the file path of the dependency
158      */
159     public void setActualFilePath(String actualFilePath) {
160         this.actualFilePath = actualFilePath;
161         if (this.sha1sum == null) {
162             final File file = new File(this.actualFilePath);
163             determineHashes(file);
164         }
165     }
166 
167     /**
168      * Gets the file path of the dependency.
169      *
170      * @return the file path of the dependency
171      */
172     public String getActualFilePath() {
173         return this.actualFilePath;
174     }
175 
176     /**
177      * Gets a reference to the File object.
178      *
179      * @return the File object
180      */
181     public File getActualFile() {
182         return new File(this.actualFilePath);
183     }
184 
185     /**
186      * Sets the file path of the dependency.
187      *
188      * @param filePath the file path of the dependency
189      */
190     public void setFilePath(String filePath) {
191         this.filePath = filePath;
192     }
193 
194     /**
195      * The file name to display in reports.
196      */
197     private String displayName = null;
198 
199     /**
200      * Sets the file name to display in reports.
201      *
202      * @param displayName the name to display
203      */
204     public void setDisplayFileName(String displayName) {
205         this.displayName = displayName;
206     }
207 
208     /**
209      * Returns the file name to display in reports; if no display file name has been set it will default to the actual file name.
210      *
211      * @return the file name to display
212      */
213     public String getDisplayFileName() {
214         if (displayName == null) {
215             return this.fileName;
216         }
217         return this.displayName;
218     }
219 
220     /**
221      * <p>
222      * Gets the file path of the dependency.</p>
223      * <p>
224      * <b>NOTE:</b> This may not be the actual path of the file on disk. The actual path of the file on disk can be obtained via
225      * the getActualFilePath().</p>
226      *
227      * @return the file path of the dependency
228      */
229     public String getFilePath() {
230         return this.filePath;
231     }
232 
233     /**
234      * Returns the MD5 Checksum of the dependency file.
235      *
236      * @return the MD5 Checksum
237      */
238     public String getMd5sum() {
239         return this.md5sum;
240     }
241 
242     /**
243      * Sets the MD5 Checksum of the dependency.
244      *
245      * @param md5sum the MD5 Checksum
246      */
247     public void setMd5sum(String md5sum) {
248         this.md5sum = md5sum;
249     }
250 
251     /**
252      * Returns the SHA1 Checksum of the dependency.
253      *
254      * @return the SHA1 Checksum
255      */
256     public String getSha1sum() {
257         return this.sha1sum;
258     }
259 
260     /**
261      * Sets the SHA1 Checksum of the dependency.
262      *
263      * @param sha1sum the SHA1 Checksum
264      */
265     public void setSha1sum(String sha1sum) {
266         this.sha1sum = sha1sum;
267     }
268 
269     /**
270      * Returns a List of Identifiers.
271      *
272      * @return an ArrayList of Identifiers
273      */
274     public Set<Identifier> getIdentifiers() {
275         return this.identifiers;
276     }
277 
278     /**
279      * Sets a List of Identifiers.
280      *
281      * @param identifiers A list of Identifiers
282      */
283     public void setIdentifiers(Set<Identifier> identifiers) {
284         this.identifiers = identifiers;
285     }
286 
287     /**
288      * Adds an entry to the list of detected Identifiers for the dependency file.
289      *
290      * @param type the type of identifier (such as CPE)
291      * @param value the value of the identifier
292      * @param url the URL of the identifier
293      */
294     public void addIdentifier(String type, String value, String url) {
295         final Identifier i = new Identifier(type, value, url);
296         this.identifiers.add(i);
297     }
298 
299     /**
300      * Adds an entry to the list of detected Identifiers for the dependency file.
301      *
302      * @param type the type of identifier (such as CPE)
303      * @param value the value of the identifier
304      * @param url the URL of the identifier
305      * @param confidence the confidence in the Identifier being accurate
306      */
307     public void addIdentifier(String type, String value, String url, Confidence confidence) {
308         final Identifier i = new Identifier(type, value, url);
309         i.setConfidence(confidence);
310         this.identifiers.add(i);
311     }
312 
313     /**
314      * Adds the maven artifact as evidence.
315      *
316      * @param source The source of the evidence
317      * @param mavenArtifact The maven artifact
318      * @param confidence The confidence level of this evidence
319      */
320     public void addAsEvidence(String source, MavenArtifact mavenArtifact, Confidence confidence) {
321         if (mavenArtifact.getGroupId() != null && !mavenArtifact.getGroupId().isEmpty()) {
322             this.getVendorEvidence().addEvidence(source, "groupid", mavenArtifact.getGroupId(), confidence);
323         }
324         if (mavenArtifact.getArtifactId() != null && !mavenArtifact.getArtifactId().isEmpty()) {
325             this.getProductEvidence().addEvidence(source, "artifactid", mavenArtifact.getArtifactId(), confidence);
326         }
327         if (mavenArtifact.getVersion() != null && !mavenArtifact.getVersion().isEmpty()) {
328             this.getVersionEvidence().addEvidence(source, "version", mavenArtifact.getVersion(), confidence);
329         }
330         if (mavenArtifact.getArtifactUrl() != null && !mavenArtifact.getArtifactUrl().isEmpty()) {
331             boolean found = false;
332             for (Identifier i : this.getIdentifiers()) {
333                 if ("maven".equals(i.getType()) && i.getValue().equals(mavenArtifact.toString())) {
334                     found = true;
335                     i.setConfidence(Confidence.HIGHEST);
336                     final String url = "http://search.maven.org/#search|ga|1|1%3A%22" + this.getSha1sum() + "%22";
337                     i.setUrl(url);
338                     //i.setUrl(mavenArtifact.getArtifactUrl());
339                     LOGGER.debug("Already found identifier {}. Confidence set to highest", i.getValue());
340                     break;
341                 }
342             }
343             if (!found) {
344                 LOGGER.debug("Adding new maven identifier {}", mavenArtifact);
345                 this.addIdentifier("maven", mavenArtifact.toString(), mavenArtifact.getArtifactUrl(), Confidence.HIGHEST);
346             }
347         }
348     }
349 
350     /**
351      * Adds an entry to the list of detected Identifiers for the dependency file.
352      *
353      * @param identifier the identifier to add
354      */
355     public void addIdentifier(Identifier identifier) {
356         this.identifiers.add(identifier);
357     }
358 
359     /**
360      * A set of identifiers that have been suppressed.
361      */
362     private Set<Identifier> suppressedIdentifiers;
363 
364     /**
365      * Get the value of suppressedIdentifiers.
366      *
367      * @return the value of suppressedIdentifiers
368      */
369     public Set<Identifier> getSuppressedIdentifiers() {
370         return suppressedIdentifiers;
371     }
372 
373     /**
374      * Set the value of suppressedIdentifiers.
375      *
376      * @param suppressedIdentifiers new value of suppressedIdentifiers
377      */
378     public void setSuppressedIdentifiers(Set<Identifier> suppressedIdentifiers) {
379         this.suppressedIdentifiers = suppressedIdentifiers;
380     }
381 
382     /**
383      * Adds an identifier to the list of suppressed identifiers.
384      *
385      * @param identifier an identifier that was suppressed.
386      */
387     public void addSuppressedIdentifier(Identifier identifier) {
388         this.suppressedIdentifiers.add(identifier);
389     }
390 
391     /**
392      * A set of vulnerabilities that have been suppressed.
393      */
394     private SortedSet<Vulnerability> suppressedVulnerabilities;
395 
396     /**
397      * Get the value of suppressedVulnerabilities.
398      *
399      * @return the value of suppressedVulnerabilities
400      */
401     public SortedSet<Vulnerability> getSuppressedVulnerabilities() {
402         return suppressedVulnerabilities;
403     }
404 
405     /**
406      * Set the value of suppressedVulnerabilities.
407      *
408      * @param suppressedVulnerabilities new value of suppressedVulnerabilities
409      */
410     public void setSuppressedVulnerabilities(SortedSet<Vulnerability> suppressedVulnerabilities) {
411         this.suppressedVulnerabilities = suppressedVulnerabilities;
412     }
413 
414     /**
415      * Adds a vulnerability to the set of suppressed vulnerabilities.
416      *
417      * @param vulnerability the vulnerability that was suppressed
418      */
419     public void addSuppressedVulnerability(Vulnerability vulnerability) {
420         this.suppressedVulnerabilities.add(vulnerability);
421     }
422 
423     /**
424      * Returns the evidence used to identify this dependency.
425      *
426      * @return an EvidenceCollection.
427      */
428     public EvidenceCollection getEvidence() {
429         return EvidenceCollection.merge(this.productEvidence, this.vendorEvidence, this.versionEvidence);
430     }
431 
432     /**
433      * Returns the evidence used to identify this dependency.
434      *
435      * @return an EvidenceCollection.
436      */
437     public Set<Evidence> getEvidenceForDisplay() {
438         return EvidenceCollection.mergeForDisplay(this.productEvidence, this.vendorEvidence, this.versionEvidence);
439     }
440 
441     /**
442      * Returns the evidence used to identify this dependency.
443      *
444      * @return an EvidenceCollection.
445      */
446     public EvidenceCollection getEvidenceUsed() {
447         return EvidenceCollection.mergeUsed(this.productEvidence, this.vendorEvidence, this.versionEvidence);
448     }
449 
450     /**
451      * Gets the Vendor Evidence.
452      *
453      * @return an EvidenceCollection.
454      */
455     public EvidenceCollection getVendorEvidence() {
456         return this.vendorEvidence;
457     }
458 
459     /**
460      * Gets the Product Evidence.
461      *
462      * @return an EvidenceCollection.
463      */
464     public EvidenceCollection getProductEvidence() {
465         return this.productEvidence;
466     }
467 
468     /**
469      * Gets the Version Evidence.
470      *
471      * @return an EvidenceCollection.
472      */
473     public EvidenceCollection getVersionEvidence() {
474         return this.versionEvidence;
475     }
476 
477     /**
478      * The description of the JAR file.
479      */
480     private String description;
481 
482     /**
483      * Get the value of description.
484      *
485      * @return the value of description
486      */
487     public String getDescription() {
488         return description;
489     }
490 
491     /**
492      * Set the value of description.
493      *
494      * @param description new value of description
495      */
496     public void setDescription(String description) {
497         this.description = description;
498     }
499 
500     /**
501      * The license that this dependency uses.
502      */
503     private String license;
504 
505     /**
506      * Get the value of license.
507      *
508      * @return the value of license
509      */
510     public String getLicense() {
511         return license;
512     }
513 
514     /**
515      * Set the value of license.
516      *
517      * @param license new value of license
518      */
519     public void setLicense(String license) {
520         this.license = license;
521     }
522 
523     /**
524      * A list of vulnerabilities for this dependency.
525      */
526     private SortedSet<Vulnerability> vulnerabilities;
527 
528     /**
529      * Get the list of vulnerabilities.
530      *
531      * @return the list of vulnerabilities
532      */
533     public SortedSet<Vulnerability> getVulnerabilities() {
534         return vulnerabilities;
535     }
536 
537     /**
538      * Set the value of vulnerabilities.
539      *
540      * @param vulnerabilities new value of vulnerabilities
541      */
542     public void setVulnerabilities(SortedSet<Vulnerability> vulnerabilities) {
543         this.vulnerabilities = vulnerabilities;
544     }
545 
546     /**
547      * Determines the sha1 and md5 sum for the given file.
548      *
549      * @param file the file to create checksums for
550      */
551     private void determineHashes(File file) {
552         String md5 = null;
553         String sha1 = null;
554         try {
555             md5 = Checksum.getMD5Checksum(file);
556             sha1 = Checksum.getSHA1Checksum(file);
557         } catch (IOException ex) {
558             LOGGER.warn("Unable to read '{}' to determine hashes.", file.getName());
559             LOGGER.debug("", ex);
560         } catch (NoSuchAlgorithmException ex) {
561             LOGGER.warn("Unable to use MD5 of SHA1 checksums.");
562             LOGGER.debug("", ex);
563         }
564         this.setMd5sum(md5);
565         this.setSha1sum(sha1);
566     }
567 
568     /**
569      * Adds a vulnerability to the dependency.
570      *
571      * @param vulnerability a vulnerability outlining a vulnerability.
572      */
573     public void addVulnerability(Vulnerability vulnerability) {
574         this.vulnerabilities.add(vulnerability);
575     }
576 
577     /**
578      * A collection of related dependencies.
579      */
580     private Set<Dependency> relatedDependencies = new TreeSet<Dependency>();
581 
582     /**
583      * Get the value of {@link #relatedDependencies}. This field is used to collect other dependencies which really represent the
584      * same dependency, and may be presented as one item in reports.
585      *
586      * @return the value of relatedDependencies
587      */
588     public Set<Dependency> getRelatedDependencies() {
589         return relatedDependencies;
590     }
591 
592     /**
593      * A list of projects that reference this dependency.
594      */
595     private Set<String> projectReferences = new HashSet<String>();
596 
597     /**
598      * Get the value of projectReferences.
599      *
600      * @return the value of projectReferences
601      */
602     public Set<String> getProjectReferences() {
603         return projectReferences;
604     }
605 
606     /**
607      * Set the value of projectReferences.
608      *
609      * @param projectReferences new value of projectReferences
610      */
611     public void setProjectReferences(Set<String> projectReferences) {
612         this.projectReferences = projectReferences;
613     }
614 
615     /**
616      * Adds a project reference.
617      *
618      * @param projectReference a project reference
619      */
620     public void addProjectReference(String projectReference) {
621         this.projectReferences.add(projectReference);
622     }
623 
624     /**
625      * Add a collection of project reference.
626      *
627      * @param projectReferences a set of project references
628      */
629     public void addAllProjectReferences(Set<String> projectReferences) {
630         this.projectReferences.addAll(projectReferences);
631     }
632 
633     /**
634      * Set the value of relatedDependencies.
635      *
636      * @param relatedDependencies new value of relatedDependencies
637      */
638     public void setRelatedDependencies(Set<Dependency> relatedDependencies) {
639         this.relatedDependencies = relatedDependencies;
640     }
641 
642     /**
643      * Adds a related dependency. The internal collection is normally a {@link java.util.TreeSet}, which relies on
644      * {@link #compareTo(Dependency)}. A consequence of this is that if you attempt to add a dependency with the same file path
645      * (modulo character case) as one that is already in the collection, it won't get added.
646      *
647      * @param dependency a reference to the related dependency
648      */
649     public void addRelatedDependency(Dependency dependency) {
650         if (this == dependency) {
651             LOGGER.warn("Attempted to add a circular reference - please post the log file to issue #172 here "
652                     + "https://github.com/jeremylong/DependencyCheck/issues/172");
653             LOGGER.debug("this: {}", this);
654             LOGGER.debug("dependency: {}", dependency);
655         } else if (!relatedDependencies.add(dependency)) {
656             LOGGER.debug("Failed to add dependency, likely due to referencing the same file as another dependency in the set.");
657             LOGGER.debug("this: {}", this);
658             LOGGER.debug("dependency: {}", dependency);
659         }
660     }
661 
662     /**
663      * A list of available versions.
664      */
665     private List<String> availableVersions = new ArrayList<String>();
666 
667     /**
668      * Get the value of availableVersions.
669      *
670      * @return the value of availableVersions
671      */
672     public List<String> getAvailableVersions() {
673         return availableVersions;
674     }
675 
676     /**
677      * Set the value of availableVersions.
678      *
679      * @param availableVersions new value of availableVersions
680      */
681     public void setAvailableVersions(List<String> availableVersions) {
682         this.availableVersions = availableVersions;
683     }
684 
685     /**
686      * Adds a version to the available version list.
687      *
688      * @param version the version to add to the list
689      */
690     public void addAvailableVersion(String version) {
691         this.availableVersions.add(version);
692     }
693 
694     /**
695      * Implementation of the Comparable&lt;Dependency&gt; interface. The comparison is solely based on the file path.
696      *
697      * @param o a dependency to compare
698      * @return an integer representing the natural ordering
699      */
700     @Override
701     public int compareTo(Dependency o) {
702         return this.getFilePath().compareToIgnoreCase(o.getFilePath());
703     }
704 
705     /**
706      * Implementation of the equals method.
707      *
708      * @param obj the object to compare
709      * @return true if the objects are equal, otherwise false
710      */
711     @Override
712     public boolean equals(Object obj) {
713         if (obj == null || getClass() != obj.getClass()) {
714             return false;
715         }
716         final Dependency other = (Dependency) obj;
717         return new EqualsBuilder()
718                 .appendSuper(super.equals(obj))
719                 .append(this.actualFilePath, other.actualFilePath)
720                 .append(this.filePath, other.filePath)
721                 .append(this.fileName, other.fileName)
722                 .append(this.md5sum, other.md5sum)
723                 .append(this.sha1sum, other.sha1sum)
724                 .append(this.identifiers, other.identifiers)
725                 .append(this.vendorEvidence, other.vendorEvidence)
726                 .append(this.productEvidence, other.productEvidence)
727                 .append(this.versionEvidence, other.versionEvidence)
728                 .append(this.description, other.description)
729                 .append(this.license, other.license)
730                 .append(this.vulnerabilities, other.vulnerabilities)
731                 //.append(this.relatedDependencies, other.relatedDependencies)
732                 .append(this.projectReferences, other.projectReferences)
733                 .append(this.availableVersions, other.availableVersions)
734                 .isEquals();
735     }
736 
737     /**
738      * Generates the HashCode.
739      *
740      * @return the HashCode
741      */
742     @Override
743     public int hashCode() {
744         return new HashCodeBuilder(MAGIC_HASH_INIT_VALUE, MAGIC_HASH_MULTIPLIER)
745                 .append(actualFilePath)
746                 .append(filePath)
747                 .append(fileName)
748                 .append(md5sum)
749                 .append(sha1sum)
750                 .append(identifiers)
751                 .append(vendorEvidence)
752                 .append(productEvidence)
753                 .append(versionEvidence)
754                 .append(description)
755                 .append(license)
756                 .append(vulnerabilities)
757                 //.append(relatedDependencies)
758                 .append(projectReferences)
759                 .append(availableVersions)
760                 .toHashCode();
761     }
762 
763     /**
764      * Standard toString() implementation showing the filename, actualFilePath, and filePath.
765      *
766      * @return the string representation of the file
767      */
768     @Override
769     public String toString() {
770         return "Dependency{ fileName='" + fileName + "', actualFilePath='" + actualFilePath + "', filePath='" + filePath + "'}";
771     }
772 }