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