Skip to content

Commit

Permalink
feat: add mojo to scan plugins, resolves #4035
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremylong committed Oct 24, 2022
1 parent 4aaaa03 commit 18b9b22
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ public class Dependency extends EvidenceCollection implements Serializable {
* A collection of related dependencies.
*/
private final SortedSet<Dependency> 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<String> includedBy = new HashSet<>();
/**
* A list of projects that reference this dependency.
*/
Expand Down Expand Up @@ -433,6 +438,7 @@ public synchronized Set<Identifier> getSoftwareIdentifiers() {
public synchronized Set<Identifier> getVulnerableSoftwareIdentifiers() {
return Collections.unmodifiableSet(this.vulnerableSoftwareIdentifiers);
}

/**
* Returns the count of vulnerability identifiers.
*
Expand All @@ -441,6 +447,7 @@ public synchronized Set<Identifier> 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.
Expand Down Expand Up @@ -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<String> 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.
*
Expand Down Expand Up @@ -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)) {
Expand Down
37 changes: 32 additions & 5 deletions core/src/main/resources/templates/htmlReport.vsl
Original file line number Diff line number Diff line change
Expand Up @@ -804,10 +804,7 @@ Getting Help: <a href="https://github.com/jeremylong/DependencyCheck/issues" tar
<b>SHA256:</b>$enc.html($dependency.Sha256sum)
#end
#if ($dependency.projectReferences.size()==1)
<br/><b>Referenced In Project/Scope:</b>
#foreach($ref in $dependency.projectReferences)
$enc.html($ref)
#end
<br/><b>Referenced In Project/Scope:</b> $enc.html($dependency.projectReferences.iterator().next())
#end
#if ($dependency.projectReferences.size()>1)
<br/><b>Referenced In Projects/Scopes:</b><ul>
Expand All @@ -816,6 +813,16 @@ Getting Help: <a href="https://github.com/jeremylong/DependencyCheck/issues" tar
#end
</ul>
#end
#if ($dependency.includedBy.size()==1)
<br/><b>Child of:</b> $enc.html($dependency.includedBy.iterator().next())
#end
#if ($dependency.includedBy.size()>1)
<br/><b>Child of:</b><ul>
#foreach($parent in $dependency.includedBy)
<li>$enc.html($parent)</li>
#end
</ul>
#end
</p>
#set($cnt=$cnt+1)
<h4 id="header$cnt" class="subsectionheader expandable expandablesubsection white">Evidence</h4>
Expand Down Expand Up @@ -1010,11 +1017,31 @@ Getting Help: <a href="https://github.com/jeremylong/DependencyCheck/issues" tar
#end
#end
<b>File&nbsp;Path:</b>&nbsp;$enc.html($dependency.FilePath)<br/>
#if(!$dependency.isVirtual())
#if(!$dependency.isVirtual())
<b>MD5:</b>&nbsp;$enc.html($dependency.Md5sum)<br/>
<b>SHA1:</b>&nbsp;$enc.html($dependency.Sha1sum)<br/>
<b>SHA256:</b>&nbsp;$enc.html($dependency.Sha256sum)
#end
#if ($dependency.projectReferences.size()==1)
<br/><b>Referenced In Project/Scope:</b> $enc.html($dependency.projectReferences.iterator().next())
#end
#if ($dependency.projectReferences.size()>1)
<br/><b>Referenced In Projects/Scopes:</b><ul>
#foreach($ref in $dependency.projectReferences)
<li>$enc.html($ref)</li>
#end
</ul>
#end
#if ($dependency.includedBy.size()==1)
<br/><b>Child of:</b> $enc.html($dependency.includedBy.iterator().next())
#end
#if ($dependency.includedBy.size()>1)
<br/><b>Child of:</b><ul>
#foreach($parent in $dependency.includedBy)
<li>$enc.html($parent)</li>
#end
</ul>
#end
</p>
#set($cnt=$cnt+1)
<h4 id="header$cnt" class="subsectionheader expandable expandablesubsection white">Evidence</h4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
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;
Expand All @@ -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;
Expand Down Expand Up @@ -653,12 +656,13 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
private String ossIndexServerId;

/**
* Whether we should only warn about Sonatype OSS Index remote errors instead of failing the goal completely.
* Whether we should only warn about Sonatype OSS Index remote errors
* instead of failing the goal completely.
*/
@SuppressWarnings("CanBeFinal")
@Parameter(property = "ossIndexWarnOnlyOnRemoteErrors")
private Boolean ossIndexWarnOnlyOnRemoteErrors;

/**
* Whether or not the Elixir Mix Audit Analyzer is enabled.
*/
Expand Down Expand Up @@ -1133,7 +1137,7 @@ protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine)
protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine, boolean aggregate) {
try {
final List<String> 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);
Expand All @@ -1159,6 +1163,123 @@ 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
* @return a collection of exceptions that may have occurred while resolving
* and scanning the dependencies
*/
protected ExceptionCollection scanPlugins(MavenProject project, Engine engine) {
ExceptionCollection exCol = null;
final Set<Artifact> plugins = getProject().getPluginArtifacts();
final Set<Artifact> reports = getProject().getReportArtifacts();
final Set<Artifact> 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(engine, resolved, null, 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(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(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<ArtifactVersion> availableVersions = artifact.getAvailableVersions();

final List<Dependency> 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) {
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)");
}
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<Artifact> resolveArtifactDependencies(final DependableCoordinate artifact, MavenProject project)
throws DependencyResolverException {
final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project, project.getRemoteArtifactRepositories());

final Iterable<ArtifactResult> artifactResults = dependencyResolver.resolveDependencies(buildingRequest, artifact, null);

final Set<Artifact> artifacts = new HashSet<>();

for (ArtifactResult artifactResult : artifactResults) {
artifacts.add(artifactResult.getArtifact());
}

return artifacts;

}

/**
* Converts the dependency to a dependency node object.
*
Expand Down Expand Up @@ -1365,15 +1486,15 @@ private ExceptionCollection collectMavenDependencies(Engine engine, MavenProject
final List<org.apache.maven.model.Dependency> dependencies = project.getDependencies();
final List<org.apache.maven.model.Dependency> managedDependencies
= project.getDependencyManagement() == null ? null : project.getDependencyManagement()
.getDependencies();
.getDependencies();
final Iterable<ArtifactResult> allDeps
= dependencyResolver.resolveDependencies(buildingRequest, dependencies, managedDependencies,
null);
null);
allDeps.forEach(allResolvedDeps::add);
} catch (DependencyResolverException dre) {
if (dre.getCause() instanceof org.eclipse.aether.resolution.DependencyResolutionException) {
final List<ArtifactResult> successResults =
Mshared998Util.getResolutionResults(
final List<ArtifactResult> successResults
= Mshared998Util.getResolutionResults(
(org.eclipse.aether.resolution.DependencyResolutionException) dre.getCause());
allResolvedDeps.addAll(successResults);
} else {
Expand Down Expand Up @@ -1486,13 +1607,15 @@ && addSnapshotReactorDependency(engine, dependencyNode.getArtifact(), project))
* Utility method for a work-around to MSHARED-998
*
* @param allDeps The List of ArtifactResults for all dependencies
* @param unresolvedArtifact The ArtifactCoordinate of the artifact we're looking for
* @param unresolvedArtifact The ArtifactCoordinate of the artifact we're
* looking for
* @param project The project in whose context resolution was attempted
* @return the resolved artifact matching with {@code unresolvedArtifact}
* @throws DependencyNotFoundException If {@code unresolvedArtifact} could not be found within {@code allDeps}
* @throws DependencyNotFoundException If {@code unresolvedArtifact} could
* not be found within {@code allDeps}
*/
private Artifact findInAllDeps(final List<ArtifactResult> allDeps, final Artifact unresolvedArtifact,
final MavenProject project)
final MavenProject project)
throws DependencyNotFoundException {
Artifact result = null;
for (final ArtifactResult res : allDeps) {
Expand All @@ -1512,8 +1635,10 @@ private Artifact findInAllDeps(final List<ArtifactResult> allDeps, final Artifac
* Utility method for a work-around to MSHARED-998
*
* @param res A single ArtifactResult obtained from the DependencyResolver
* @param unresolvedArtifact The unresolved Artifact from the dependencyGraph that we try to find
* @return {@code true} when unresolvedArtifact is non-null and matches with the artifact of res
* @param unresolvedArtifact The unresolved Artifact from the
* dependencyGraph that we try to find
* @return {@code true} when unresolvedArtifact is non-null and matches with
* the artifact of res
*/
private boolean sameArtifact(final ArtifactResult res, final Artifact unresolvedArtifact) {
if (res == null || res.getArtifact() == null || unresolvedArtifact == null) {
Expand Down Expand Up @@ -1687,7 +1812,7 @@ private boolean addReactorDependency(Engine engine, Artifact artifact, final Mav
* <code>false</code>
*/
private boolean addVirtualDependencyFromReactor(Engine engine, Artifact artifact,
final MavenProject depender, String infoLogTemplate) {
final MavenProject depender, String infoLogTemplate) {

getLog().debug(String.format("Checking the reactor projects (%d) for %s:%s:%s",
reactorProjects.size(),
Expand Down Expand Up @@ -1794,13 +1919,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<ArtifactRepository> repos) {
final ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
buildingRequest.setRemoteRepositories(new ArrayList<>(project.getRemoteArtifactRepositories()));
buildingRequest.setRemoteRepositories(repos);
buildingRequest.setProject(project);
return buildingRequest;
}
Expand Down
Loading

0 comments on commit 18b9b22

Please sign in to comment.