diff --git a/src/integTest/groovy/org/owasp/dependencycheck/gradle/DependencyCheckConfigurationSelectionIntegSpec.groovy b/src/integTest/groovy/org/owasp/dependencycheck/gradle/DependencyCheckConfigurationSelectionIntegSpec.groovy index 6df4cfb..27e86e2 100644 --- a/src/integTest/groovy/org/owasp/dependencycheck/gradle/DependencyCheckConfigurationSelectionIntegSpec.groovy +++ b/src/integTest/groovy/org/owasp/dependencycheck/gradle/DependencyCheckConfigurationSelectionIntegSpec.groovy @@ -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'() { @@ -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"() { diff --git a/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy b/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy index b9cac95..1747f39 100644 --- a/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy +++ b/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy @@ -123,6 +123,10 @@ class DependencyCheckExtension { * This is mutually exclusive with the scanConfigurations property. */ List skipConfigurations = [] + /** + * The artifact types that will be analyzed in the gradle build. + */ + List analyzedTypes = ['jar', 'aar', 'js', 'war', 'ear', 'zip'] /** * Whether or not to skip the execution of dependency-check. */ diff --git a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/AbstractAnalyze.groovy b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/AbstractAnalyze.groovy index 94d8b9f..e4d8a47 100644 --- a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/AbstractAnalyze.groovy +++ b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/AbstractAnalyze.groovy @@ -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 @@ -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 @@ -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) { @@ -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") @@ -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) @@ -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) @@ -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 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 deps, ResolvedArtifact artifact, String configurationName) { + protected void addInfoToDependencies(List 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 { @@ -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) } } } diff --git a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Aggregate.groovy b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Aggregate.groovy index 2c7862b..f5308a3 100644 --- a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Aggregate.groovy +++ b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Aggregate.groovy @@ -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.' @@ -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) } } } diff --git a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Analyze.groovy b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Analyze.groovy index 21608cb..d6268b2 100644 --- a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Analyze.groovy +++ b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Analyze.groovy @@ -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. */ @@ -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) } }