Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Multi-Project Support for Android #90

Merged
merged 2 commits into from
Jul 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import static org.owasp.dependencycheck.gradle.DependencyCheckPlugin.*

class DependencyCheckConfigurationSelectionIntegSpec extends Specification {

@Rule final TemporaryFolder testProjectDir = new TemporaryFolder()
@Rule
final TemporaryFolder testProjectDir = new TemporaryFolder()


def 'test dependencies are ignored by default'() {
Expand All @@ -30,13 +31,19 @@ class DependencyCheckConfigurationSelectionIntegSpec extends Specification {

when:
def result = executeTaskAndGetResult(ANALYZE_TASK, false)
//println "-----------------"
//println result.output
//println "-----------------"
//String fileContents = new File(new File(testProjectDir.root, 'build/reports'), 'dependency-check-report.html').text
//println fileContents

then:
result.task(":$ANALYZE_TASK").outcome == FAILED
result.output.contains('CVE-2015-6420')
result.output.contains('CVE-2014-0114')
result.output.contains('CVE-2016-3092')
result.output.contains('CVE-2015-5262')
//the nvd CVE was updated and the version used is no longer considered vulnerable
//result.output.contains('CVE-2015-5262')
}

def "custom configurations are scanned by default"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ class DependencyCheckExtension {
* This is mutually exclusive with the scanConfigurations property.
*/
List<String> skipConfigurations = []
/**
* The artifact types that will be analyzed in the gradle build.
*/
List<String> analyzedTypes = ['jar', 'aar', 'js', 'war', 'ear', 'zip']
/**
* Whether or not to skip the execution of dependency-check.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ package org.owasp.dependencycheck.gradle.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.InvalidUserDataException
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleVersionIdentifier
import org.gradle.api.attributes.Attribute
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.owasp.dependencycheck.Engine
Expand Down Expand Up @@ -52,6 +55,8 @@ abstract class AbstractAnalyze extends DefaultTask {
def settings
@Internal
def PROPERTIES_FILE = "task.properties"
@Internal
def artifactType = Attribute.of('artifactType', String)

/**
* Calls dependency-check-core's analysis engine to scan
Expand Down Expand Up @@ -97,7 +102,8 @@ abstract class AbstractAnalyze extends DefaultTask {
def displayName = determineDisplayName()
def groupId = project.getGroup()
File output = new File(config.outputDirectory)
engine.writeReports(displayName, groupId, name.toString(), project.getVersion().toString(), output, config.format.toString())
engine.writeReports(displayName, groupId, name.toString(), project.getVersion().toString(), output,
config.format.toString())
showSummary(engine)
checkForFailure(engine)
} catch (ReportException ex) {
Expand Down Expand Up @@ -130,7 +136,9 @@ abstract class AbstractAnalyze extends DefaultTask {
project.metaClass.respondsTo(project, "getDisplayName") ?
project.getDisplayName() : project.getName()
}

/**
* Verifies aspects of the configuration to ensure dependency-check can run correctly.
*/
def verifySettings() {
if (config.scanConfigurations && config.skipConfigurations) {
throw new IllegalArgumentException("you can only specify one of scanConfigurations or skipConfigurations")
Expand All @@ -144,7 +152,6 @@ abstract class AbstractAnalyze extends DefaultTask {
def initializeSettings() {
settings = new Settings()


InputStream taskProperties = null
try {
taskProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE)
Expand Down Expand Up @@ -362,6 +369,11 @@ abstract class AbstractAnalyze extends DefaultTask {
config.skipTestGroups && isTestConfiguration(configuration)
}

/**
* Determines if the configuration should be considered a test configuration.
* @param configuration the configuration to insepct
* @return true if the configuration is considered a tet configuration; otherwise false
*/
def isTestConfiguration(configuration) {
def isTestConfiguration = isTestConfigurationCheck(configuration)

Expand Down Expand Up @@ -389,29 +401,83 @@ abstract class AbstractAnalyze extends DefaultTask {
isTestConfiguration
}

/**
* Determines if the onfiguration can be resolved
* @param configuration the configuration to inspect
* @return true if the configuration can be resolved; otherwise false
*/
def canBeResolved(configuration) {
// Configuration.isCanBeResolved() has been introduced with Gradle 3.3,
// thus we need to check for the method's existence first
configuration.metaClass.respondsTo(configuration, "isCanBeResolved") ?
configuration.isCanBeResolved() : true
}

/**
* Process the incoming artifacts for the given project's configurations.
* @param project the project to analyze
* @param engine the dependency-check engine
*/
protected void processConfigurations(Project project, engine) {
project.configurations.findAll {
shouldBeScanned(it) && !(shouldBeSkipped(it) || shouldBeSkippedAsTest(it)) && canBeResolved(it)
}.each { Configuration configuration ->

String projectName = project.name
String scope = "$projectName:$configuration.name"

Map<String, ModuleVersionIdentifier> componentVersions = [:]
configuration.incoming.resolutionResult.allDependencies.each {
if (it.hasProperty('selected')) {
componentVersions.put(it.selected.id, it.selected.moduleVersion)
} else if (it.hasProperty('attempted')) {
logger.warn("Unable to resolve artifact: ${it.attempted.displayName}")
} else {
logger.warn("Unable to resolve: ${it}")
}
}

def types = config.analyzedTypes

types.each { type ->
configuration.incoming.artifactView {
attributes {
it.attribute(artifactType, type)
}
}.artifacts.each {
ModuleVersionIdentifier id = componentVersions[it.id.componentIdentifier]
def deps = engine.scan(it.file, scope)
if (deps == null) {
if (it.file.isFile()) {
addDependency(engine, projectName, configuration.name,
id.group, id.name, id.version, it.id.displayName, it.file)
} else {
addDependency(engine, projectName, configuration.name,
id.group, id.name, id.version, it.id.displayName)
}
} else {
addInfoToDependencies(deps, scope, id.group, id.name, id.version)
}
}
}
}
}

/**
* Adds additional information and evidence to the dependencies.
* @param deps the list of dependencies that will be updated
* @param artifact the artifact that was scanned to obtain the dependencies
* @param configurationName the configuration name that the artifact was identified in
* @param group the group id for the artifact coordinates
* @param artifact the artifact id for the artifact coordinates
* @param version the version number for the artifact coordinates
*/
protected void addInfoToDependencies(List<Dependency> deps, ResolvedArtifact artifact, String configurationName) {
protected void addInfoToDependencies(List<Dependency> deps, String configurationName,
String group, String artifact, String version) {
if (deps != null) {
if (deps.size() == 1) {
def d = deps.get(0)
MavenArtifact mavenArtifact = createMavenArtifact(artifact)
MavenArtifact mavenArtifact = new MavenArtifact(group, artifact, version)
d.addAsEvidence("gradle", mavenArtifact, Confidence.HIGHEST)
if (artifact.moduleVersion.id.group != null && artifact.moduleVersion.id.name != null && artifact.moduleVersion.id.version != null) {
d.addIdentifier("maven", String.format("%s:%s:%s",
artifact.moduleVersion.id.group, artifact.moduleVersion.id.name, artifact.moduleVersion.id.version),
null, Confidence.HIGHEST)
if (group != null && artifact != null && version != null) {
d.addIdentifier("maven", String.format("%s:%s:%s", group, artifact, version), null, Confidence.HIGHEST)
}
d.addProjectReference(configurationName)
} else {
Expand All @@ -421,62 +487,64 @@ abstract class AbstractAnalyze extends DefaultTask {
}

/**
* Creates a MavenArtifact from the Gradle artifact.
* @param artifact the Gradle artifact
* @return the MavenArtifact
*/
private MavenArtifact createMavenArtifact(ResolvedArtifact artifact) {
def id = artifact.moduleVersion.id
new MavenArtifact(id.group, id.name, id.version)
}

/**
* Adds a virtual dependency to the engine. This is used when an artifact is scanned that is not
* Adds a dependency to the engine. This is used when an artifact is scanned that is not
* supported by dependency-check (different dependency type for possibly new language).
* @param engine a reference to the engine
* @param projectName the project name
* @param configurationName the configuration name
* @param groupid the group id
* @param group the group id
* @param name the name or artifact id
* @param version the version number
* @param displayName the display name
*/
protected void addVirtualDependency(Engine engine, String projectName, String configurationName,
String groupid, String name, String version, String displayName) {

def display = displayName ?: "${groupid}:${name}:${version}"
logger.info("Adding virtual dependency for ${display}")

Dependency virtualDependency = new Dependency(new File(project.buildDir.getParentFile(), "build.gradle"), true)
protected void addDependency(Engine engine, String projectName, String configurationName,
String group, String name, String version, String displayName,
File file = null) {

def display = displayName ?: "${group}:${name}:${version}"
Dependency dependency
String sha256
if (file == null) {
logger.info("Adding virtual dependency for ${display}")
dependency = new Dependency(new File(project.buildDir.getParentFile(), "build.gradle"), true)
sha256 = getSHA256Checksum("${group}:${name}:${version}")
} else {
logger.info("Adding dependency for ${display}")
dependency = new Dependency(file)
sha256 = dependency.getSha256sum()
}

String sha256 = getSHA256Checksum("${groupid}:${name}:${version}")
def existing = engine.dependencies.find {
sha256.equals(it.getSha256sum())
}
if (existing != null) {
existing.addProjectReference("${projectName}:${configurationName}")
} else {
if (dependency.virtual) {
dependency.sha1sum = getSHA1Checksum("${group}:${name}:${version}")
dependency.sha256sum = sha256
dependency.md5sum = getMD5Checksum("${group}:${name}:${version}")
dependency.displayFileName = display
}
dependency.addEvidence(VENDOR, "build.gradle", "group", group, Confidence.HIGHEST)
dependency.addEvidence(VENDOR, "build.gradle", "name", name, Confidence.MEDIUM)
dependency.addEvidence(VENDOR, "build.gradle", "displayName", display, Confidence.MEDIUM)
dependency.addEvidence(PRODUCT, "build.gradle", "group", group, Confidence.MEDIUM)
dependency.addEvidence(PRODUCT, "build.gradle", "name", name, Confidence.HIGHEST)
dependency.addEvidence(PRODUCT, "build.gradle", "displayName", display, Confidence.HIGH)
dependency.addEvidence(VERSION, "build.gradle", "version", version, Confidence.HIGHEST)
dependency.name = name
dependency.version = version
dependency.packagePath = "${group}:${name}:${version}"
dependency.addProjectReference("${projectName}:${configurationName}")
if (file!=null && file.getName().endsWith(".aar")) {
dependency.ecosystem = "android"
} else {
dependency.ecosystem = "gradle"
}
dependency.addIdentifier("maven", "${group}:${name}:${version}", null, Confidence.HIGHEST)

virtualDependency.setSha1sum(getSHA1Checksum("${groupid}:${name}:${version}"))
virtualDependency.setSha256sum(sha256)
virtualDependency.setMd5sum(getMD5Checksum("${groupid}:${name}:${version}"))
virtualDependency.addEvidence(VENDOR, "build.gradle", "group", groupid, Confidence.HIGHEST)
virtualDependency.addEvidence(VENDOR, "build.gradle", "name", name, Confidence.MEDIUM)
virtualDependency.addEvidence(VENDOR, "build.gradle", "displayName", display, Confidence.MEDIUM)
virtualDependency.addEvidence(PRODUCT, "build.gradle", "group", groupid, Confidence.MEDIUM)
virtualDependency.addEvidence(PRODUCT, "build.gradle", "name", name, Confidence.HIGHEST)
virtualDependency.addEvidence(PRODUCT, "build.gradle", "displayName", display, Confidence.HIGH)
virtualDependency.addEvidence(VERSION, "build.gradle", "version", version, Confidence.HIGHEST)
virtualDependency.setName(name)
virtualDependency.setVersion(version)
virtualDependency.setDisplayFileName(display)
virtualDependency.setPackagePath("${groupid}:${name}:${version}")
virtualDependency.addProjectReference("${projectName}:${configurationName}")
virtualDependency.setEcosystem("gradle")
virtualDependency.addIdentifier("maven", "${groupid}:${name}:${version}",
null, Confidence.HIGHEST)

engine.addDependency(virtualDependency)
engine.addDependency(dependency)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@

package org.owasp.dependencycheck.gradle.tasks

import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.Project

/**
* Checks the projects dependencies for known vulnerabilities.
*/
class Aggregate extends AbstractAnalyze {


Aggregate() {
group = 'OWASP dependency-check'
description = 'Identifies and reports known vulnerabilities (CVEs) in multi-project dependencies.'
Expand All @@ -36,26 +36,8 @@ class Aggregate extends AbstractAnalyze {
*/
def scanDependencies(engine) {
logger.lifecycle("Verifying dependencies for project ${currentProjectName}")
project.rootProject.allprojects.collectMany {
it.configurations.findAll {
shouldBeScanned(it) && !(shouldBeSkipped(it) || shouldBeSkippedAsTest(it)) && canBeResolved(it)
}.each { Configuration configuration ->
String projectName = it.name
String scope = "$it.name:$configuration.name"
def resolved = configuration.getResolvedConfiguration().getResolvedArtifacts()
if (resolved.size() > 0) {
logger.lifecycle("Analyzing ${scope}")
}
resolved.each { ResolvedArtifact artifact ->
def deps = engine.scan(artifact.getFile(), scope)
if (deps == null) {
addVirtualDependency(engine, projectName, configuration.name, artifact.moduleVersion.id.group,
artifact.moduleVersion.id.name, artifact.moduleVersion.id.version, artifact.id.displayName)
} else {
addInfoToDependencies(deps, artifact, scope)
}
}
}
project.rootProject.allprojects.each { Project project ->
processConfigurations(project, engine)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
*/

package org.owasp.dependencycheck.gradle.tasks

import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact

/**
* Checks the projects dependencies for known vulnerabilities.
*/
Expand All @@ -36,26 +32,7 @@ class Analyze extends AbstractAnalyze {
*/
def scanDependencies(engine) {
logger.lifecycle("Verifying dependencies for project ${currentProjectName}")
project.getConfigurations().findAll {
shouldBeScanned(it) && !(shouldBeSkipped(it) || shouldBeSkippedAsTest(it)) && canBeResolved(it)
}.each { Configuration configuration ->

String projectName = project.name
String scope = "$projectName:$configuration.name"
def resolved = configuration.getResolvedConfiguration().getResolvedArtifacts()
if (resolved.size() > 0) {
logger.lifecycle("Analyzing ${scope}")
}
resolved.each { ResolvedArtifact artifact ->
def deps = engine.scan(artifact.getFile(), scope)
if (deps == null) {
addVirtualDependency(engine, projectName, configuration.name, artifact.moduleVersion.id.group,
artifact.moduleVersion.id.name, artifact.moduleVersion.id.version, artifact.id.displayName)
} else {
addInfoToDependencies(deps, artifact, scope)
}
}
}
processConfigurations(project, engine)
}

}