Skip to content

Commit e2f7d99

Browse files
committed
Add support for configuring multiple Analyze tasks distinctly.
Certain key configurations have been moved to task inputs w/ the existing extension providing default values. This allows users to register multiple tasks with distinct configurations so they can produce multiple reports as needed.
1 parent 2b37d1b commit e2f7d99

File tree

5 files changed

+219
-16
lines changed

5 files changed

+219
-16
lines changed

src/main/groovy/org/owasp/dependencycheck/gradle/tasks/AbstractAnalyze.groovy

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ import org.gradle.api.artifacts.result.ResolvedDependencyResult
3535
import org.gradle.api.attributes.Attribute
3636
import org.gradle.api.file.DirectoryProperty
3737
import org.gradle.api.model.ObjectFactory
38+
import org.gradle.api.provider.ListProperty
39+
import org.gradle.api.provider.Property
40+
import org.gradle.api.tasks.Input
3841
import org.gradle.api.tasks.Internal
42+
import org.gradle.api.tasks.Optional
3943
import org.gradle.api.tasks.OutputDirectory
4044
import org.gradle.api.tasks.TaskAction
4145
import org.gradle.maven.MavenModule
@@ -71,10 +75,112 @@ import static org.owasp.dependencycheck.utils.Checksum.*
7175
//@groovy.transform.CompileStatic
7276
abstract class AbstractAnalyze extends ConfiguredTask {
7377

78+
/**
79+
* A list of which Gradle configurations will be scanned.
80+
*
81+
* <p>If empty, all resolvable configurations will be scanned. This is mutually
82+
* exclusive with {@link #skipConfigurations}</p>
83+
*
84+
* @see #skipConfigurations
85+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#scanConfigurations
86+
*/
87+
@Input
88+
@Optional
89+
final ListProperty<String> scanConfigurations
90+
91+
/**
92+
* A list of which Gradle configurations to skip when scanning all configurations.
93+
*
94+
* <p>This is mutually exclusive with {@link #scanConfigurations}</p>
95+
*
96+
* @see #scanConfigurations
97+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#skipConfigurations
98+
*/
99+
@Input
100+
@Optional
101+
final ListProperty<String> skipConfigurations
102+
103+
/**
104+
* Whether to skip the test configurations when scanning.
105+
*
106+
* <p>When trying to scan test configurations, this also needs to be explicitly disabled.
107+
* Defaults to <code>true</code>.</p>
108+
*
109+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#skipTestGroups
110+
*/
111+
@Input
112+
@Optional
113+
final Property<Boolean> skipTestGroups
114+
115+
/**
116+
* Whether to scan the dependencies of the various Gradle configurations.
117+
*
118+
* <p>Defaults to <code>true</code>.</p>
119+
*
120+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#scanDependencies
121+
*/
122+
@Input
123+
@Optional
124+
final Property<Boolean> scanDependencies
125+
126+
/**
127+
* Whether to scan the dependencies of the Gradle build environment.
128+
*
129+
* <p>Defaults to <code>false</code>.</p>
130+
*
131+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#scanDependencies
132+
*/
133+
@Input
134+
@Optional
135+
final Property<Boolean> scanBuildEnv
136+
137+
/**
138+
* Specifies if the build should be failed if a CVSS score equal to or above a specified level is identified.
139+
*
140+
* <p>Defaults to <code>11</code>, i.e. builds will never fail. More information on CVSS scores can be found at the
141+
* <a href="https://nvd.nist.gov/vuln-metrics/cvss">NVD</a></p>
142+
*
143+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#failBuildOnCVSS
144+
*/
145+
@Input
146+
@Optional
147+
final Property<Float> failBuildOnCVSS
148+
149+
/**
150+
* <p>The file path to the XML suppression file - used to suppress
151+
* <a href="https://dependency-check.github.io/DependencyCheck/general/suppression.html">false positives</a>.</p>
152+
*
153+
* <p>This can be a local file path, a URL to a
154+
* suppression file, or even a reference to a file on the class path.</p>
155+
*
156+
* <p>Multiple suppression files can also be specified with {@link #suppressionFiles}.</p>
157+
*
158+
* @see #suppressionFiles
159+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#suppressionFile
160+
*/
161+
@Input
162+
@Optional
163+
final Property<String> suppressionFile
164+
165+
/**
166+
* A list of file paths to XML suppression files.
167+
*
168+
* <p>These can be local file paths, URLs to hosted
169+
* suppression files, or even references to files on the class path.</p>
170+
*
171+
* @see #suppressionFile
172+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#suppressionFiles
173+
*/
174+
@Input
175+
@Optional
176+
final ListProperty<String> suppressionFiles
177+
74178
@Internal
75179
String currentProjectName = project.getName()
180+
76181
@Internal
77182
Attribute artifactType = Attribute.of('artifactType', String)
183+
78184
// @Internal
79185
private static final GradleVersion CUTOVER_GRADLE_VERSION = GradleVersion.version("4.0")
80186
private static final GradleVersion IGNORE_NON_RESOLVABLE_SCOPES_GRADLE_VERSION = GradleVersion.version("7.0")
@@ -83,13 +189,26 @@ abstract class AbstractAnalyze extends ConfiguredTask {
83189

84190
/**
85191
* The output directory for the dependency-check reports.
192+
*
193+
* @see org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension#outputDirectory
86194
*/
87195
@OutputDirectory
88196
final DirectoryProperty outputDir
89197

90198
@Inject
91199
AbstractAnalyze(ObjectFactory objects) {
200+
// task inputs are defaulted using the 'dependencyCheck' extension
201+
// this creates a system whereby you can either globally set these inputs via the extension
202+
// or you can override them on a per-task basis
203+
failBuildOnCVSS = objects.property(Float).convention(config.failBuildOnCVSS)
92204
outputDir = objects.directoryProperty().convention(config.outputDirectory)
205+
scanBuildEnv = objects.property(Boolean).convention(config.scanBuildEnv)
206+
scanConfigurations = objects.listProperty(String).convention(config.scanConfigurations)
207+
scanDependencies = objects.property(Boolean).convention(config.scanDependencies)
208+
skipConfigurations = objects.listProperty(String).convention(config.skipConfigurations)
209+
skipTestGroups = objects.property(Boolean).convention(config.skipTestGroups)
210+
suppressionFile = objects.property(String).convention(config.suppressionFile)
211+
suppressionFiles = objects.listProperty(String).convention(config.suppressionFiles)
93212
}
94213

95214
/**
@@ -176,15 +295,16 @@ abstract class AbstractAnalyze extends ConfiguredTask {
176295
String determineDisplayName() {
177296
return project.metaClass.respondsTo(project, "getDisplayName") ? project.getDisplayName() : project.getName()
178297
}
298+
179299
/**
180300
* Verifies aspects of the configuration to ensure dependency-check can run correctly.
181301
*/
182302
@groovy.transform.CompileStatic
183303
def verifySettings() {
184-
if (!config.scanDependencies.get() && !config.scanBuildEnv.get()) {
304+
if (!scanDependencies.get() && !scanBuildEnv.get()) {
185305
throw new IllegalArgumentException("At least one of scanDependencies or scanBuildEnv must be set to true")
186306
}
187-
if (!config.scanConfigurations.get().isEmpty() && !config.skipConfigurations.get().isEmpty()) {
307+
if (!scanConfigurations.get().isEmpty() && !skipConfigurations.get().isEmpty()) {
188308
throw new IllegalArgumentException("you can only specify one of scanConfigurations or skipConfigurations")
189309
}
190310
if (!config.scanProjects.get().isEmpty() && !config.skipProjects.get().isEmpty()) {
@@ -263,7 +383,7 @@ abstract class AbstractAnalyze extends ConfiguredTask {
263383
*/
264384
@groovy.transform.CompileStatic
265385
CheckForFailureResult checkForFailure(Engine engine) {
266-
if (config.failBuildOnCVSS.get() > 10) {
386+
if (failBuildOnCVSS.get() > 10) {
267387
return CheckForFailureResult.createSuccess()
268388
}
269389

@@ -278,12 +398,12 @@ abstract class AbstractAnalyze extends ConfiguredTask {
278398
&& v.getCvssV4().getCvssData().getBaseScore() != null ? v.getCvssV4().getCvssData().getBaseScore() : -1;
279399
final boolean useUnscored = cvssV2 == -1 && cvssV3 == -1 && cvssV4 == -1;
280400
final double unscoredCvss = (useUnscored && v.getUnscoredSeverity() != null) ? SeverityUtil.estimateCvssV2(v.getUnscoredSeverity()) : -1;
281-
if (cvssV2 >= config.failBuildOnCVSS.get()
282-
|| cvssV3 >= config.failBuildOnCVSS.get()
283-
|| cvssV4 >= config.failBuildOnCVSS.get()
284-
|| useUnscored && unscoredCvss >= config.failBuildOnCVSS.get()
401+
if (cvssV2 >= failBuildOnCVSS.get()
402+
|| cvssV3 >= failBuildOnCVSS.get()
403+
|| cvssV4 >= failBuildOnCVSS.get()
404+
|| useUnscored && unscoredCvss >= failBuildOnCVSS.get()
285405
//safety net to fail on any if for some reason the above misses on 0
286-
|| (config.failBuildOnCVSS.get() <= 0.0f)) {
406+
|| (failBuildOnCVSS.get() <= 0.0f)) {
287407
vulnerabilities.add(v.getName());
288408
}
289409
}
@@ -292,7 +412,7 @@ abstract class AbstractAnalyze extends ConfiguredTask {
292412
if (vulnerabilities.size() > 0) {
293413
final String msg = String.format("%n%nDependency-Analyze Failure:%n"
294414
+ "One or more dependencies were identified with vulnerabilities that have a CVSS score greater than '%.1f': %s%n"
295-
+ "See the dependency-check report for more details.%n%n", config.failBuildOnCVSS.get(), vulnerabilities.join(", "))
415+
+ "See the dependency-check report for more details.%n%n", failBuildOnCVSS.get(), vulnerabilities.join(", "))
296416
return CheckForFailureResult.createFailed(msg)
297417
} else {
298418
return CheckForFailureResult.createSuccess()
@@ -351,7 +471,7 @@ abstract class AbstractAnalyze extends ConfiguredTask {
351471
*/
352472
@groovy.transform.CompileStatic
353473
boolean shouldBeScanned(Configuration configuration) {
354-
config.scanConfigurations.get().isEmpty() || config.scanConfigurations.get().contains(configuration.name)
474+
return scanConfigurations.get().isEmpty() || scanConfigurations.get().contains(configuration.name)
355475
}
356476

357477
/**
@@ -366,7 +486,7 @@ abstract class AbstractAnalyze extends ConfiguredTask {
366486
"runtime".equals(configuration.name) ||
367487
"compile".equals(configuration.name) ||
368488
"compileOnly".equals(configuration.name)))
369-
|| config.skipConfigurations.get().contains(configuration.name))
489+
|| skipConfigurations.get().contains(configuration.name))
370490
}
371491

372492
/**
@@ -385,7 +505,7 @@ abstract class AbstractAnalyze extends ConfiguredTask {
385505
*/
386506
@groovy.transform.CompileStatic
387507
boolean shouldBeSkippedAsTest(Configuration configuration) {
388-
config.skipTestGroups.get() && isTestConfiguration(configuration)
508+
skipTestGroups.get() && isTestConfiguration(configuration)
389509
}
390510

391511
/**

src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Aggregate.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ class Aggregate extends AbstractAnalyze {
5757
private def scanProject(Set<Project> projects, Engine engine) {
5858
projects.each { Project project ->
5959
if (shouldBeScanned(project) && !shouldBeSkipped(project)) {
60-
if (this.config.scanDependencies.get()) {
60+
if (scanDependencies.get()) {
6161
processConfigurations(project, engine)
6262
}
63-
if (this.config.scanBuildEnv.get()) {
63+
if (scanBuildEnv.get()) {
6464
processBuildEnvironment(project, engine)
6565
}
6666
}

src/main/groovy/org/owasp/dependencycheck/gradle/tasks/Analyze.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ class Analyze extends AbstractAnalyze {
4646
def scanDependencies(Engine engine) {
4747
if (shouldBeScanned(project) && !shouldBeSkipped(project)) {
4848
logger.lifecycle("Verifying dependencies for project ${currentProjectName}")
49-
if (this.config.scanDependencies.get()) {
49+
if (scanDependencies.get()) {
5050
processConfigurations(project, engine)
5151
}
52-
if (this.config.scanBuildEnv.get()) {
52+
if (scanBuildEnv.get()) {
5353
processBuildEnvironment(project, engine)
5454
}
5555
}

src/test/groovy/org/owasp/dependencycheck/gradle/DependencyCheckConfigurationSelectionIntegSpec.groovy

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,33 @@ class DependencyCheckConfigurationSelectionIntegSpec extends Specification {
146146
result.output.contains('commons-collections')
147147
}
148148

149+
def "analysis generates distinct reports when multiple tasks are used"() {
150+
given:
151+
copyBuildFileIntoProjectDir('multipleTasks.gradle')
152+
153+
when:
154+
def result = executeTaskAndGetResult(ANALYZE_TASK, true)
155+
println("Created copy of this @ $testProjectDir")
156+
157+
then:
158+
result.task(":$ANALYZE_TASK").outcome == SUCCESS
159+
def mainReport = new File(testProjectDir, 'build/reports/dependency-check/main/dependency-check-report.csv')
160+
def testReport = new File(testProjectDir, 'build/reports/dependency-check/test/dependency-check-report.csv')
161+
def buildReport = new File(testProjectDir, 'build/reports/dependency-check/build/dependency-check-report.csv')
162+
// the various reports exist
163+
mainReport.exists()
164+
testReport.exists()
165+
buildReport.exists()
166+
// and they contain only the CVEs expected for their respective configurations
167+
mainReport.text.contains('CVE-2015-6420') // CVE from commons-collections
168+
testReport.text.contains('CVE-2016-7051') // CVE from jackson
169+
buildReport.text.contains('CVE-2016-3092') // CVE from commons-fileupload
170+
!mainReport.text.contains('CVE-2016-7051') // CVE from jackson shouldn't be in main report
171+
!mainReport.text.contains('CVE-2016-3092') // CVE from commons-fileupload shouldn't be in main report
172+
!testReport.text.contains('CVE-2016-3092') // CVE from commons-fileupload shouldn't be in test report
173+
!buildReport.text.contains('CVE-2015-6420') // CVE from commons-collections shouldn't be in build report
174+
!buildReport.text.contains('CVE-2016-7051') // CVE from jackson shouldn't be in build report
175+
}
149176

150177
private void copyBuildFileIntoProjectDir(String buildFileName) {
151178
copyResourceFileIntoProjectDir(buildFileName, 'build.gradle')
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
buildscript {
2+
repositories {
3+
mavenLocal()
4+
mavenCentral()
5+
}
6+
dependencies {
7+
classpath group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.1'
8+
}
9+
}
10+
11+
plugins {
12+
id 'org.owasp.dependencycheck'
13+
id 'java'
14+
}
15+
16+
sourceCompatibility = 1.5
17+
version = '1.0'
18+
19+
repositories {
20+
mavenLocal()
21+
mavenCentral()
22+
}
23+
24+
dependencies {
25+
implementation group: 'commons-collections', name: 'commons-collections', version: '3.2'
26+
testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.7.0'
27+
}
28+
29+
dependencyCheck {
30+
format = 'csv'
31+
analyzers.ossIndex.enabled = false
32+
nvd.datafeedUrl = 'https://dependency-check.github.io/DependencyCheck/hb_nvd/'
33+
}
34+
35+
def dependencyCheckAnalyzeTest = tasks.register('dependencyCheckAnalyzeTest', org.owasp.dependencycheck.gradle.tasks.Analyze) {
36+
description = 'Runs a dependency-check analysis on just the test configuration.'
37+
outputDir = project.layout.buildDirectory.dir("reports/dependency-check/test")
38+
scanConfigurations = ['testRuntimeClasspath']
39+
skipTestGroups = false
40+
}
41+
42+
def dependencyCheckAnalyzeBuild = tasks.register('dependencyCheckAnalyzeBuild', org.owasp.dependencycheck.gradle.tasks.Analyze) {
43+
description = 'Runs a dependency-check analysis on just the test configuration.'
44+
outputDir = project.layout.buildDirectory.dir("reports/dependency-check/build")
45+
scanConfigurations = []
46+
scanBuildEnv = true
47+
scanDependencies = false
48+
}
49+
50+
tasks.dependencyCheckAnalyze {
51+
description = 'Runs a dependency-check analysis on just the runtime configuration.'
52+
outputDir = project.layout.buildDirectory.dir("reports/dependency-check/main")
53+
scanConfigurations = ['runtimeClasspath']
54+
55+
dependsOn dependencyCheckAnalyzeTest, dependencyCheckAnalyzeBuild
56+
}

0 commit comments

Comments
 (0)