From b345359eca7d11b19d6de6e7d209139dc4afd4f1 Mon Sep 17 00:00:00 2001
From: Jeremy Long
Date: Thu, 27 Oct 2022 20:46:44 -0400
Subject: [PATCH] feat: add mojo to scan plugins, resolves #4035
---
.../dependency/Dependency.java | 29 +-
.../main/resources/templates/htmlReport.vsl | 59 +-
.../dependencycheck/maven/AggregateMojo.java | 18 +
.../maven/BaseDependencyCheckMojo.java | 570 ++++++++++++------
.../dependencycheck/maven/CheckMojo.java | 15 +
.../CollectingRootDependencyGraphVisitor.java | 70 +++
.../dependencycheck/maven/PurgeMojo.java | 14 +
.../dependencycheck/maven/UpdateMojo.java | 14 +
.../maven/BaseDependencyCheckMojoTest.java | 4 +
9 files changed, 608 insertions(+), 185 deletions(-)
create mode 100644 maven/src/main/java/org/owasp/dependencycheck/maven/CollectingRootDependencyGraphVisitor.java
diff --git a/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java b/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
index fd31f392cd4..fff8c6b6f6e 100644
--- a/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
+++ b/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
@@ -102,6 +102,11 @@ public class Dependency extends EvidenceCollection implements Serializable {
* A collection of related dependencies.
*/
private final SortedSet relatedDependencies = new TreeSet<>(Dependency.NAME_COMPARATOR);
+ /**
+ * The set of dependencies that included this dependency (i.e., this is a
+ * transitive dependency because it was included by X).
+ */
+ private final Set includedBy = new HashSet<>();
/**
* A list of projects that reference this dependency.
*/
@@ -433,6 +438,7 @@ public synchronized Set getSoftwareIdentifiers() {
public synchronized Set getVulnerableSoftwareIdentifiers() {
return Collections.unmodifiableSet(this.vulnerableSoftwareIdentifiers);
}
+
/**
* Returns the count of vulnerability identifiers.
*
@@ -441,6 +447,7 @@ public synchronized Set getVulnerableSoftwareIdentifiers() {
public synchronized int getVulnerableSoftwareIdentifiersCount() {
return this.vulnerableSoftwareIdentifiers.size();
}
+
/**
* Adds a set of Identifiers to the current list of software identifiers.
* Only used for testing.
@@ -767,6 +774,26 @@ public synchronized void clearRelatedDependencies() {
relatedDependencies.clear();
}
+ /**
+ * Get the unmodifiable set of includedBy (the list of parents of this
+ * transitive dependency).
+ *
+ * @return the unmodifiable set of includedBy
+ */
+ public synchronized Set getIncludedBy() {
+ return Collections.unmodifiableSet(new HashSet<>(includedBy));
+ }
+
+ /**
+ * Adds the parent or root of the transitive dependency chain (i.e., this
+ * was included by the parent dependency X).
+ *
+ * @param includedBy a project reference
+ */
+ public synchronized void addIncludedBy(String includedBy) {
+ this.includedBy.add(includedBy);
+ }
+
/**
* Get the unmodifiable set of projectReferences.
*
@@ -808,7 +835,7 @@ public synchronized void addRelatedDependency(Dependency dependency) {
LOGGER.debug("dependency: {}", dependency);
} else if (NAME_COMPARATOR.compare(this, dependency) == 0) {
LOGGER.debug("Attempted to add the same dependency as this, likely due to merging identical dependencies "
- + "obtained from different modules");
+ + "obtained from different modules");
LOGGER.debug("this: {}", this);
LOGGER.debug("dependency: {}", dependency);
} else if (!relatedDependencies.add(dependency)) {
diff --git a/core/src/main/resources/templates/htmlReport.vsl b/core/src/main/resources/templates/htmlReport.vsl
index 1dad4c983b4..29239b7bbdd 100644
--- a/core/src/main/resources/templates/htmlReport.vsl
+++ b/core/src/main/resources/templates/htmlReport.vsl
@@ -602,6 +602,28 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
.underline {
text-decoration: underline;
}
+ .tooltip {
+ position: relative;
+ display: inline-block;
+ border-bottom: 1px dotted black;
+ }
+
+ .tooltip .tooltiptext {
+ visibility: hidden;
+ width: 220px;
+ background-color: #cccccc;
+ text-align: center;
+ border-radius: 6px;
+ padding: 5px 0;
+
+ /* Position the tooltip */
+ position: absolute;
+ z-index: 1;
+ }
+
+ .tooltip:hover .tooltiptext {
+ visibility: visible;
+ }
@@ -804,10 +826,7 @@ Getting Help: SHA256:$enc.html($dependency.Sha256sum)
#end
#if ($dependency.projectReferences.size()==1)
-
Referenced In Project/Scope:
- #foreach($ref in $dependency.projectReferences)
- $enc.html($ref)
- #end
+
Referenced In Project/Scope: $enc.html($dependency.projectReferences.iterator().next())
#end
#if ($dependency.projectReferences.size()>1)
Referenced In Projects/Scopes:
#set($cnt=$cnt+1)
Evidence
@@ -1010,11 +1039,31 @@ Getting Help: File Path: $enc.html($dependency.FilePath)
- #if(!$dependency.isVirtual())
+ #if(!$dependency.isVirtual())
MD5: $enc.html($dependency.Md5sum)
SHA1: $enc.html($dependency.Sha1sum)
SHA256: $enc.html($dependency.Sha256sum)
+ #end
+ #if ($dependency.projectReferences.size()==1)
+
Referenced In Project/Scope: $enc.html($dependency.projectReferences.iterator().next())
+ #end
+ #if ($dependency.projectReferences.size()>1)
+
Referenced In Projects/Scopes:
+ #foreach($ref in $dependency.projectReferences)
+ - $enc.html($ref)
+ #end
+
+ #end
+ #if ($dependency.includedBy.size()==1)
+
Included by: $enc.html($dependency.includedBy.iterator().next())
+ #end
+ #if ($dependency.includedBy.size()>1)
+
Included by:
+ #foreach($parent in $dependency.includedBy)
+ - $enc.html($parent)
#end
+
+ #end
#set($cnt=$cnt+1)
Evidence
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java
index d7877a6484e..bc8bd135fcd 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java
@@ -96,6 +96,24 @@ protected ExceptionCollection scanDependencies(final Engine engine) throws MojoE
return exCol;
}
+ /**
+ * Scans the plugins of the project.
+ *
+ * @param engine the engine used to perform the scanning
+ * @param exCollection the collection of exceptions that might have occurred
+ * previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if a fatal exception occurs
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ ExceptionCollection exCol = scanPlugins(getProject(), engine, null);
+ for (MavenProject childProject : getDescendants(this.getProject())) {
+ exCol = scanPlugins(childProject, engine, exCol);
+ }
+ return exCol;
+ }
+
/**
* Returns a set containing all the descendant projects of the given
* project.
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java
index 1379d6c2853..75b37d85f2e 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java
@@ -83,15 +83,16 @@
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.Restriction;
import org.apache.maven.artifact.versioning.VersionRange;
-import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
import org.owasp.dependencycheck.agent.DependencyCheckScanAgent;
import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
@@ -99,6 +100,8 @@
import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
+import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate;
+import org.apache.maven.shared.transfer.dependencies.DependableCoordinate;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.reporting.ReportGenerator;
import org.owasp.dependencycheck.utils.SeverityUtil;
@@ -969,6 +972,13 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
@Parameter(property = "scanDirectory")
private List scanDirectory;
+ /**
+ * The name of the report in the site.
+ */
+ @SuppressWarnings("CanBeFinal")
+ @Parameter(property = "odc.plugins.scan", defaultValue = "false", required = false)
+ private boolean scanPlugins = false;
+
//
//
/**
@@ -1142,12 +1152,14 @@ protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine)
protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine, boolean aggregate) {
try {
final List filterItems = Collections.singletonList(String.format("%s:%s", project.getGroupId(), project.getArtifactId()));
- final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project);
+ final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project, project.getRemoteArtifactRepositories());
//For some reason the filter does not filter out the project being analyzed
//if we pass in the filter below instead of null to the dependencyGraphBuilder
final DependencyNode dn = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);
- final CollectingDependencyNodeVisitor collectorVisitor = new CollectingDependencyNodeVisitor();
+ //final CollectingDependencyNodeVisitor collectorVisitor = new CollectingDependencyNodeVisitor();
+ final CollectingRootDependencyGraphVisitor collectorVisitor = new CollectingRootDependencyGraphVisitor();
+
// exclude artifact by pattern and its dependencies
final DependencyNodeVisitor transitiveFilterVisitor = new FilteringDependencyTransitiveNodeVisitor(collectorVisitor,
new ArtifactDependencyNodeFilter(new PatternExcludesArtifactFilter(getExcludes())));
@@ -1158,7 +1170,7 @@ protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine,
dn.accept(artifactFilter);
//collect dependencies with the filter - see comment above.
- final List nodes = new ArrayList<>(collectorVisitor.getNodes());
+ final Map> nodes = collectorVisitor.getNodes();
return collectDependencies(engine, project, nodes, buildingRequest, aggregate);
} catch (DependencyGraphBuilderException ex) {
@@ -1168,6 +1180,130 @@ protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine,
}
}
+ /**
+ * Scans the project's artifacts and adds them to the engine's dependency
+ * list.
+ *
+ * @param project the project to scan the dependencies of
+ * @param engine the engine to use to scan the dependencies
+ * @param exCollection the collection of exceptions that have previously occurred
+ * @return a collection of exceptions that may have occurred while resolving
+ * and scanning the dependencies
+ */
+ protected ExceptionCollection scanPlugins(MavenProject project, Engine engine, ExceptionCollection exCollection) {
+ ExceptionCollection exCol = exCollection;
+ final Set plugins = getProject().getPluginArtifacts();
+ final Set reports = getProject().getReportArtifacts();
+ final Set extensions = getProject().getExtensionArtifacts();
+
+ plugins.addAll(reports);
+ plugins.addAll(extensions);
+
+ final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project, project.getPluginArtifactRepositories());
+ for (Artifact plugin : plugins) {
+ try {
+ final Artifact resolved = artifactResolver.resolveArtifact(buildingRequest, plugin).getArtifact();
+
+ exCol = addPluginToDependencies(project, engine, resolved, "pom.xml (plugins)", exCol);
+
+ final DefaultDependableCoordinate pluginCoordinate = new DefaultDependableCoordinate();
+ pluginCoordinate.setGroupId(resolved.getGroupId());
+ pluginCoordinate.setArtifactId(resolved.getArtifactId());
+ pluginCoordinate.setVersion(resolved.getVersion());
+
+ //TOOD - convert this to a packageURl instead of GAV
+ final String parent = resolved.getGroupId() + ":" + resolved.getArtifactId() + ":" + resolved.getVersion();
+ for (Artifact artifact : resolveArtifactDependencies(pluginCoordinate, project)) {
+ exCol = addPluginToDependencies(project, engine, artifact, parent, exCol);
+ }
+ } catch (ArtifactResolverException ex) {
+ throw new RuntimeException(ex);
+ } catch (IllegalArgumentException ex) {
+ throw new RuntimeException(ex);
+ } catch (DependencyResolverException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ return null;
+
+ }
+
+ private ExceptionCollection addPluginToDependencies(MavenProject project, Engine engine, Artifact artifact, String parent, ExceptionCollection exCollection) {
+ ExceptionCollection exCol = exCollection;
+ final String groupId = artifact.getGroupId();
+ final String artifactId = artifact.getArtifactId();
+ final String version = artifact.getVersion();
+ final File artifactFile = artifact.getFile();
+ if (artifactFile.isFile()) {
+ final List availableVersions = artifact.getAvailableVersions();
+
+ final List deps = engine.scan(artifactFile.getAbsoluteFile(),
+ project.getName() + " (plugins)");
+ if (deps != null) {
+ Dependency d = null;
+ if (deps.size() == 1) {
+ d = deps.get(0);
+ } else {
+ for (Dependency possible : deps) {
+ if (artifactFile.getAbsoluteFile().equals(possible.getActualFile())) {
+ d = possible;
+ break;
+ }
+ }
+ for (Dependency dep : deps) {
+ if (d != null && d != dep) {
+ //TODO convert to package URL
+ dep.addIncludedBy(groupId + ":" + artifactId + ":" + version + " (plugins)");
+ }
+ }
+ }
+ if (d != null) {
+ final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
+ d.addAsEvidence("pom", ma, Confidence.HIGHEST);
+ if (parent != null) {
+ d.addIncludedBy(parent + " (plugins)");
+ } else {
+ String includedby = String.format("%s:%s:%s",
+ project.getGroupId(),
+ project.getArtifactId(),
+ project.getVersion());
+ d.addIncludedBy(includedby);
+ }
+ if (availableVersions != null) {
+ for (ArtifactVersion av : availableVersions) {
+ d.addAvailableVersion(av.toString());
+ }
+ }
+ }
+ }
+ } else {
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(new DependencyNotFoundException("Unable to resolve plugin: "
+ + groupId + ":" + artifactId + ":" + version));
+ }
+
+ return exCol;
+ }
+
+ protected Set resolveArtifactDependencies(final DependableCoordinate artifact, MavenProject project)
+ throws DependencyResolverException {
+ final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project, project.getRemoteArtifactRepositories());
+
+ final Iterable artifactResults = dependencyResolver.resolveDependencies(buildingRequest, artifact, null);
+
+ final Set artifacts = new HashSet<>();
+
+ for (ArtifactResult artifactResult : artifactResults) {
+ artifacts.add(artifactResult.getArtifact());
+ }
+
+ return artifacts;
+
+ }
+
/**
* Converts the dependency to a dependency node object.
*
@@ -1311,180 +1447,23 @@ private ExceptionCollection collectDependencyManagementDependencies(Engine engin
*/
//CSOFF: OperatorWrap
private ExceptionCollection collectMavenDependencies(Engine engine, MavenProject project,
- List nodes, ProjectBuildingRequest buildingRequest, boolean aggregate) {
+ Map> nodeMap, ProjectBuildingRequest buildingRequest, boolean aggregate) {
- ExceptionCollection exCol = collectDependencyManagementDependencies(engine, buildingRequest, project, nodes, aggregate);
final List allResolvedDeps = new ArrayList<>();
- for (DependencyNode dependencyNode : nodes) {
- if (artifactScopeExcluded.passes(dependencyNode.getArtifact().getScope())
- || artifactTypeExcluded.passes(dependencyNode.getArtifact().getType())) {
- continue;
- }
-
- boolean isResolved = false;
- File artifactFile = null;
- String artifactId = null;
- String groupId = null;
- String version = null;
- List availableVersions = null;
- if (org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dependencyNode.getArtifact().getScope())) {
- final Artifact a = dependencyNode.getArtifact();
- if (a.isResolved() && a.getFile().isFile()) {
- artifactFile = a.getFile();
- isResolved = artifactFile.isFile();
- groupId = a.getGroupId();
- artifactId = a.getArtifactId();
- version = a.getVersion();
- availableVersions = a.getAvailableVersions();
- } else {
- for (org.apache.maven.model.Dependency d : project.getDependencies()) {
- if (d.getSystemPath() != null && artifactsMatch(d, a)) {
- artifactFile = new File(d.getSystemPath());
- isResolved = artifactFile.isFile();
- groupId = a.getGroupId();
- artifactId = a.getArtifactId();
- version = a.getVersion();
- availableVersions = a.getAvailableVersions();
- break;
- }
- }
- }
- if (!isResolved) {
- getLog().error("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString());
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
- exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: "
- + dependencyNode.toNodeString()));
- }
- } else {
- final Artifact dependencyArtifact = dependencyNode.getArtifact();
- final Artifact result;
- if (dependencyArtifact.isResolved()) {
- //All transitive dependencies, excluding reactor and dependencyManagement artifacts should
- //have been resolved by Maven prior to invoking the plugin - resolving the dependencies
- //manually is unnecessary, and does not work in some cases (issue-1751)
- getLog().debug(String.format("Skipping artifact %s, already resolved", dependencyArtifact.getArtifactId()));
- result = dependencyArtifact;
- } else {
- try {
- if (allResolvedDeps.isEmpty()) { // no (partially successful) resolution attempt done
- try {
- final List dependencies = project.getDependencies();
- final List managedDependencies
- = project.getDependencyManagement() == null ? null : project.getDependencyManagement()
- .getDependencies();
- final Iterable allDeps
- = dependencyResolver.resolveDependencies(buildingRequest, dependencies, managedDependencies,
- null);
- allDeps.forEach(allResolvedDeps::add);
- } catch (DependencyResolverException dre) {
- if (dre.getCause() instanceof org.eclipse.aether.resolution.DependencyResolutionException) {
- final List successResults
- = Mshared998Util.getResolutionResults(
- (org.eclipse.aether.resolution.DependencyResolutionException) dre.getCause());
- allResolvedDeps.addAll(successResults);
- } else {
- throw dre;
- }
- }
- }
- result = findInAllDeps(allResolvedDeps, dependencyNode.getArtifact(), project);
- } catch (DependencyNotFoundException | DependencyResolverException ex) {
- getLog().debug(String.format("Aggregate : %s", aggregate));
- boolean addException = true;
- //CSOFF: EmptyBlock
- if (!aggregate) {
- // do nothing - the exception is to be reported
- } else if (addReactorDependency(engine, dependencyNode.getArtifact(), project)) {
- // successfully resolved as a reactor dependency - swallow the exception
- addException = false;
- }
- if (addException) {
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
- exCol.addException(ex);
- }
- continue;
- }
- }
- if (aggregate && virtualSnapshotsFromReactor
- && dependencyNode.getArtifact().isSnapshot()
- && addSnapshotReactorDependency(engine, dependencyNode.getArtifact(), project)) {
- continue;
- }
- isResolved = result.isResolved();
- artifactFile = result.getFile();
- groupId = result.getGroupId();
- artifactId = result.getArtifactId();
- version = result.getVersion();
- availableVersions = result.getAvailableVersions();
- }
- if (isResolved && artifactFile != null) {
- final List deps = engine.scan(artifactFile.getAbsoluteFile(),
- createProjectReferenceName(project, dependencyNode));
- if (deps != null) {
- scannedFiles.add(artifactFile);
- Dependency d = null;
- if (deps.size() == 1) {
- d = deps.get(0);
- } else {
- for (Dependency possible : deps) {
- if (artifactFile.getAbsoluteFile().equals(possible.getActualFile())) {
- d = possible;
- break;
- }
- }
- }
- if (d != null) {
- final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
- d.addAsEvidence("pom", ma, Confidence.HIGHEST);
- if (availableVersions != null) {
- for (ArtifactVersion av : availableVersions) {
- d.addAvailableVersion(av.toString());
- }
- }
- getLog().debug(String.format("Adding project reference %s on dependency %s",
- project.getName(), d.getDisplayFileName()));
- } else if (getLog().isDebugEnabled()) {
- final String msg = String.format("More than 1 dependency was identified in first pass scan of '%s' in project %s",
- dependencyNode.getArtifact().getId(), project.getName());
- getLog().debug(msg);
- }
- } else if ("import".equals(dependencyNode.getArtifact().getScope())) {
- final String msg = String.format("Skipping '%s:%s' in project %s as it uses an `import` scope",
- dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
- getLog().debug(msg);
- } else if ("pom".equals(dependencyNode.getArtifact().getType())) {
+ //dependency management
+ final List dmNodes = new ArrayList<>();
+ ExceptionCollection exCol = collectDependencyManagementDependencies(engine, buildingRequest, project, dmNodes, aggregate);
+ for (DependencyNode dependencyNode : dmNodes) {
+ exCol = scanDependencyNode(dependencyNode, null, engine, project, allResolvedDeps, buildingRequest, aggregate, exCol);
+ }
- try {
- final Dependency d = new Dependency(artifactFile.getAbsoluteFile());
- final Model pom = PomUtils.readPom(artifactFile.getAbsoluteFile());
- JarAnalyzer.setPomEvidence(d, pom, null, true);
- engine.addDependency(d);
- } catch (AnalysisException ex) {
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
- exCol.addException(ex);
- getLog().debug("Error reading pom " + artifactFile.getAbsoluteFile(), ex);
- }
- } else {
- if (!scannedFiles.contains(artifactFile)) {
- final String msg = String.format("No analyzer could be found or the artifact has been scanned twice for '%s:%s' in project %s",
- dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
- getLog().warn(msg);
- }
- }
- } else {
- final String msg = String.format("Unable to resolve '%s' in project %s",
- dependencyNode.getArtifact().getId(), project.getName());
- getLog().debug(msg);
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
+ //dependencies
+ for (DependencyNode root : nodeMap.keySet()) {
+ List nodes = nodeMap.get(root);
+ exCol = scanDependencyNode(root, null, engine, project, allResolvedDeps, buildingRequest, aggregate, exCol);
+ for (DependencyNode dependencyNode : nodes) {
+ exCol = scanDependencyNode(dependencyNode, root, engine, project, allResolvedDeps, buildingRequest, aggregate, exCol);
}
}
return exCol;
@@ -1567,7 +1546,7 @@ protected String createProjectReferenceName(MavenProject project, DependencyNode
* and scanning the dependencies
*/
private ExceptionCollection collectDependencies(Engine engine, MavenProject project,
- List nodes, ProjectBuildingRequest buildingRequest, boolean aggregate) {
+ Map> nodes, ProjectBuildingRequest buildingRequest, boolean aggregate) {
ExceptionCollection exCol;
exCol = collectMavenDependencies(engine, project, nodes, buildingRequest, aggregate);
@@ -1729,7 +1708,12 @@ private boolean addVirtualDependencyFromReactor(Engine engine, Artifact artifact
d.setEcosystem(JarAnalyzer.DEPENDENCY_ECOSYSTEM);
d.setDisplayFileName(displayName);
d.addProjectReference(depender.getName());
-
+ //TODO - consider packageUrL
+ String includedby = String.format("%s:%s:%s",
+ depender.getGroupId(),
+ depender.getArtifactId(),
+ depender.getVersion());
+ d.addIncludedBy(includedby);
d.addEvidence(EvidenceType.PRODUCT, "project", "artifactid", prj.getArtifactId(), Confidence.HIGHEST);
d.addEvidence(EvidenceType.VENDOR, "project", "artifactid", prj.getArtifactId(), Confidence.LOW);
@@ -1808,13 +1792,14 @@ private boolean addSnapshotReactorDependency(Engine engine, Artifact artifact, f
/**
* @param project The target project to create a building request for.
+ * @param repos the artifact repositories to use.
* @return Returns a new ProjectBuildingRequest populated from the current
* session and the target project remote repositories, used to resolve
* artifacts.
*/
- public ProjectBuildingRequest newResolveArtifactProjectBuildingRequest(MavenProject project) {
+ public ProjectBuildingRequest newResolveArtifactProjectBuildingRequest(MavenProject project, List repos) {
final ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
- buildingRequest.setRemoteRepositories(new ArrayList<>(project.getRemoteArtifactRepositories()));
+ buildingRequest.setRemoteRepositories(repos);
buildingRequest.setProject(project);
return buildingRequest;
}
@@ -1831,6 +1816,9 @@ protected void runCheck() throws MojoExecutionException, MojoFailureException {
muteJCS();
try (Engine engine = initializeEngine()) {
ExceptionCollection exCol = scanDependencies(engine);
+ if (scanPlugins) {
+ exCol = scanPlugins(engine, exCol);
+ }
try {
engine.analyzeDependencies();
} catch (ExceptionCollection ex) {
@@ -1920,7 +1908,7 @@ private ExceptionCollection handleAnalysisExceptions(ExceptionCollection current
}
/**
- * Scans the dependencies of the projects in aggregate.
+ * Scans the dependencies of the projects.
*
* @param engine the engine used to perform the scanning
* @return a collection of exceptions
@@ -1928,6 +1916,17 @@ private ExceptionCollection handleAnalysisExceptions(ExceptionCollection current
*/
protected abstract ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException;
+ /**
+ * Scans the plugins of the projects.
+ *
+ * @param engine the engine used to perform the scanning
+ * @param exCol the collection of any exceptions that have previously been
+ * captured.
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if a fatal exception occurs
+ */
+ protected abstract ExceptionCollection scanPlugins(Engine engine, ExceptionCollection exCol) throws MojoExecutionException;
+
/**
* Returns the report output directory.
*
@@ -2515,5 +2514,218 @@ private String getDefaultCveUrlModified() {
}
//
+ private ExceptionCollection scanDependencyNode(DependencyNode dependencyNode, DependencyNode root,
+ Engine engine, MavenProject project, List allResolvedDeps,
+ ProjectBuildingRequest buildingRequest, boolean aggregate, ExceptionCollection exceptionCollection) {
+ ExceptionCollection exCol = exceptionCollection;
+ if (artifactScopeExcluded.passes(dependencyNode.getArtifact().getScope())
+ || artifactTypeExcluded.passes(dependencyNode.getArtifact().getType())) {
+ return exCol;
+ }
+
+ boolean isResolved = false;
+ File artifactFile = null;
+ String artifactId = null;
+ String groupId = null;
+ String version = null;
+ List availableVersions = null;
+ if (org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dependencyNode.getArtifact().getScope())) {
+ final Artifact a = dependencyNode.getArtifact();
+ if (a.isResolved() && a.getFile().isFile()) {
+ artifactFile = a.getFile();
+ isResolved = artifactFile.isFile();
+ groupId = a.getGroupId();
+ artifactId = a.getArtifactId();
+ version = a.getVersion();
+ availableVersions = a.getAvailableVersions();
+ } else {
+ for (org.apache.maven.model.Dependency d : project.getDependencies()) {
+ if (d.getSystemPath() != null && artifactsMatch(d, a)) {
+ artifactFile = new File(d.getSystemPath());
+ isResolved = artifactFile.isFile();
+ groupId = a.getGroupId();
+ artifactId = a.getArtifactId();
+ version = a.getVersion();
+ availableVersions = a.getAvailableVersions();
+ break;
+ }
+ }
+ }
+ if (!isResolved) {
+ getLog().error("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString());
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: "
+ + dependencyNode.toNodeString()));
+ }
+ } else {
+ final Artifact dependencyArtifact = dependencyNode.getArtifact();
+ final Artifact result;
+ if (dependencyArtifact.isResolved()) {
+ //All transitive dependencies, excluding reactor and dependencyManagement artifacts should
+ //have been resolved by Maven prior to invoking the plugin - resolving the dependencies
+ //manually is unnecessary, and does not work in some cases (issue-1751)
+ getLog().debug(String.format("Skipping artifact %s, already resolved", dependencyArtifact.getArtifactId()));
+ result = dependencyArtifact;
+ } else {
+ try {
+ if (allResolvedDeps.isEmpty()) { // no (partially successful) resolution attempt done
+ try {
+ final List dependencies = project.getDependencies();
+ final List managedDependencies
+ = project.getDependencyManagement() == null ? null : project.getDependencyManagement()
+ .getDependencies();
+ final Iterable allDeps
+ = dependencyResolver.resolveDependencies(buildingRequest, dependencies, managedDependencies,
+ null);
+ allDeps.forEach(allResolvedDeps::add);
+ } catch (DependencyResolverException dre) {
+ if (dre.getCause() instanceof org.eclipse.aether.resolution.DependencyResolutionException) {
+ final List successResults
+ = Mshared998Util.getResolutionResults(
+ (org.eclipse.aether.resolution.DependencyResolutionException) dre.getCause());
+ allResolvedDeps.addAll(successResults);
+ } else {
+ throw dre;
+ }
+ }
+ }
+ result = findInAllDeps(allResolvedDeps, dependencyNode.getArtifact(), project);
+ } catch (DependencyNotFoundException | DependencyResolverException ex) {
+ getLog().debug(String.format("Aggregate : %s", aggregate));
+ boolean addException = true;
+ //CSOFF: EmptyBlock
+ if (!aggregate) {
+ // do nothing - the exception is to be reported
+ } else if (addReactorDependency(engine, dependencyNode.getArtifact(), project)) {
+ // successfully resolved as a reactor dependency - swallow the exception
+ addException = false;
+ }
+ if (addException) {
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(ex);
+ }
+ return exCol;
+ }
+ }
+ if (aggregate && virtualSnapshotsFromReactor
+ && dependencyNode.getArtifact().isSnapshot()
+ //TODO ensure root is passed in so we can assign parent
+ && addSnapshotReactorDependency(engine, dependencyNode.getArtifact(), project)) {
+ return exCol;
+ }
+ isResolved = result.isResolved();
+ artifactFile = result.getFile();
+ groupId = result.getGroupId();
+ artifactId = result.getArtifactId();
+ version = result.getVersion();
+ availableVersions = result.getAvailableVersions();
+ }
+ if (isResolved && artifactFile != null) {
+ final List deps = engine.scan(artifactFile.getAbsoluteFile(),
+ createProjectReferenceName(project, dependencyNode));
+ if (deps != null) {
+ scannedFiles.add(artifactFile);
+ Dependency d = null;
+ if (deps.size() == 1) {
+ d = deps.get(0);
+
+ } else {
+ for (Dependency possible : deps) {
+ if (artifactFile.getAbsoluteFile().equals(possible.getActualFile())) {
+ d = possible;
+ break;
+ }
+ }
+ for (Dependency dep : deps) {
+ if (d != null && d != dep) {
+ //TODO convert to package URL
+ dep.addIncludedBy(groupId + ":" + artifactId + ":" + version);
+ }
+ }
+ }
+ if (d != null) {
+ final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
+ d.addAsEvidence("pom", ma, Confidence.HIGHEST);
+ if (root != null) {
+ //TODO convert to packageURL
+ String includedby = String.format("%s:%s:%s",
+ root.getArtifact().getGroupId(),
+ root.getArtifact().getArtifactId(),
+ root.getArtifact().getVersion());
+ d.addIncludedBy(includedby);
+ } else {
+ //TODO convert to packageURL
+ String includedby = String.format("%s:%s:%s",
+ project.getGroupId(),
+ project.getArtifactId(),
+ project.getVersion());
+ d.addIncludedBy(includedby);
+ }
+ if (availableVersions != null) {
+ for (ArtifactVersion av : availableVersions) {
+ d.addAvailableVersion(av.toString());
+ }
+ }
+ getLog().debug(String.format("Adding project reference %s on dependency %s",
+ project.getName(), d.getDisplayFileName()));
+ } else if (getLog().isDebugEnabled()) {
+ final String msg = String.format("More than 1 dependency was identified in first pass scan of '%s' in project %s",
+ dependencyNode.getArtifact().getId(), project.getName());
+ getLog().debug(msg);
+ }
+ } else if ("import".equals(dependencyNode.getArtifact().getScope())) {
+ final String msg = String.format("Skipping '%s:%s' in project %s as it uses an `import` scope",
+ dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
+ getLog().debug(msg);
+ } else if ("pom".equals(dependencyNode.getArtifact().getType())) {
+
+ try {
+ final Dependency d = new Dependency(artifactFile.getAbsoluteFile());
+ final Model pom = PomUtils.readPom(artifactFile.getAbsoluteFile());
+ JarAnalyzer.setPomEvidence(d, pom, null, true);
+ if (root != null) {
+ //TODO convert to packageURL
+ String includedby = String.format("%s:%s:%s",
+ root.getArtifact().getGroupId(),
+ root.getArtifact().getArtifactId(),
+ root.getArtifact().getVersion());
+ d.addIncludedBy(includedby);
+ } else {
+ //TODO convert to packageURL
+ String includedby = String.format("%s:%s:%s",
+ project.getGroupId(),
+ project.getArtifactId(),
+ project.getVersion());
+ d.addIncludedBy(includedby);
+ }
+ engine.addDependency(d);
+ } catch (AnalysisException ex) {
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(ex);
+ getLog().debug("Error reading pom " + artifactFile.getAbsoluteFile(), ex);
+ }
+ } else {
+ if (!scannedFiles.contains(artifactFile)) {
+ final String msg = String.format("No analyzer could be found or the artifact has been scanned twice for '%s:%s' in project %s",
+ dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
+ getLog().warn(msg);
+ }
+ }
+ } else {
+ final String msg = String.format("Unable to resolve '%s' in project %s",
+ dependencyNode.getArtifact().getId(), project.getName());
+ getLog().debug(msg);
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ }
+ return exCol;
+ }
}
//CSON: FileLength
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java
index a7da4da4b12..073962a26e8 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java
@@ -24,6 +24,7 @@
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.exception.ExceptionCollection;
@@ -103,5 +104,19 @@ public String getDescription(Locale locale) {
protected ExceptionCollection scanDependencies(final Engine engine) throws MojoExecutionException {
return scanArtifacts(getProject(), engine);
}
+
+ /**
+ * Scans the plugins of the project.
+ *
+ * @param engine the engine used to perform the scanning
+ * @param exCollection the collection of exceptions that might have occurred previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if a fatal exception occurs
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ ExceptionCollection exCol = scanPlugins(getProject(), engine, exCollection);
+ return exCol;
+ }
}
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/CollectingRootDependencyGraphVisitor.java b/maven/src/main/java/org/owasp/dependencycheck/maven/CollectingRootDependencyGraphVisitor.java
new file mode 100644
index 00000000000..ca6ce191a37
--- /dev/null
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/CollectingRootDependencyGraphVisitor.java
@@ -0,0 +1,70 @@
+/*
+ * This file is part of dependency-check-maven.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (c) 2022 Jeremy Long. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.maven;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
+
+/**
+ *
+ * @author Jeremy Long
+ */
+public class CollectingRootDependencyGraphVisitor implements DependencyNodeVisitor {
+
+ /**
+ * The map of nodes collected by root nodes.
+ */
+ private final Map> nodes = new HashMap<>();
+ private DependencyNode root;
+ private int depth = 0;
+
+ public CollectingRootDependencyGraphVisitor() {
+
+ }
+
+ @Override
+ public boolean visit(DependencyNode node) {
+ if (depth == 0) {
+ root = node;
+ if (!nodes.containsKey(root)) {
+ nodes.put(root, new ArrayList<>());
+ }
+ } else {
+ // collect node
+ nodes.get(root).add(node);
+ }
+ depth += 1;
+ return true;
+ }
+
+ @Override
+ public boolean endVisit(DependencyNode node) {
+ depth -= 1;
+ return true;
+ }
+
+ public Map> getNodes() {
+ return Collections.unmodifiableMap(nodes);
+ }
+
+}
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java
index ec42d6c372c..c160379eed7 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java
@@ -104,4 +104,18 @@ public String getDescription(Locale locale) {
protected ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException {
throw new UnsupportedOperationException("Operation not supported");
}
+
+
+ /**
+ * Throws an exception if called. The purge mojo does not scan dependencies.
+ *
+ * @param engine the engine used to scan
+ * @param exCollection the collection of exceptions that might have occurred previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if there is an exception
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ throw new UnsupportedOperationException("Operation not supported");
+ }
}
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java
index b0d444ba644..0f2159bd5dc 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java
@@ -131,4 +131,18 @@ public String getDescription(Locale locale) {
protected ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException {
throw new UnsupportedOperationException("Operation not supported");
}
+
+ /**
+ * Throws an exception if called. The purge mojo does not scan dependencies.
+ *
+ * @param engine the engine used to scan
+ * @param exCollection the collection of exceptions that might have occurred
+ * previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if there is an exception
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ throw new UnsupportedOperationException("Operation not supported");
+ }
}
diff --git a/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java b/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java
index 89d7193eee8..ca13bcb0126 100644
--- a/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java
+++ b/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java
@@ -231,6 +231,10 @@ public boolean canGenerateReport() {
protected ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException {
throw new UnsupportedOperationException("Operation not supported");
}
+ @Override
+ protected ExceptionCollection scanPlugins(Engine engine, ExceptionCollection exCollection) throws MojoExecutionException {
+ throw new UnsupportedOperationException("Operation not supported");
+ }
}
@Test