diff --git a/.gitignore b/.gitignore index 0f79d7db7..e7a69cf2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ */target/** # IntelliJ test run side-effects dependency-check-core/data/ +dependency-check-ant/data/ # Intellij project files *.iml *.ipr diff --git a/README.md b/README.md index 51f1488bc..e980972ec 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,69 @@ docker run --rm \ ``` +Upgrade Notes +------------- + +### Upgrading from **1.x.x** to **2.x.x** + +Note that when upgrading from version 1.x.x that the following changes will need to be made to your configuration. + +#### Suppression file + +In order to support multiple suppression files, the mechanism for configuring suppression files has changed. +As such, users that have defined a suppression file in their configuration will need to update. + +See the examples below: + +##### Ant + +Old: + +```xml + + +``` + +New: + +```xml + + suppression.xml + +``` + +##### Maven + +Old: + +```xml + + org.owasp + dependency-check-maven + + suppression.xml + + +``` + +New: + +```xml + + org.owasp + dependency-check-maven + + + suppression.xml + + + +``` + + Mailing List ------------ diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java index a796e0ebd..9761effd3 100644 --- a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java +++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java @@ -18,7 +18,9 @@ package org.owasp.dependencycheck.taskdefs; import java.io.File; +import java.util.ArrayList; import java.util.List; + import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.EnumeratedAttribute; @@ -62,7 +64,7 @@ public class Check extends Update { * Whether or not the NSP Analyzer is enabled. */ private Boolean nspAnalyzerEnabled; - + /** * Whether or not the Ruby Bundle Audit Analyzer is enabled. */ @@ -153,9 +155,14 @@ public class Check extends Update { */ private String reportFormat = "HTML"; /** - * The path to the suppression file. + * Suppression file path. */ - private String suppressionFile; + private String suppressionFile = null; + /** + * Suppression file paths. + */ + private List suppressionFiles = new ArrayList<>(); + /** * The path to the suppression file. */ @@ -230,6 +237,17 @@ public class Check extends Update { getPath().add(rc); } + /** + * Add a suppression file. + * + * This is called by Ant with the configured {@link SuppressionFile}. + * + * @param suppressionFile the suppression file to add. + */ + public void addConfiguredSuppressionFile(final SuppressionFile suppressionFile) { + suppressionFiles.add(suppressionFile.getPath()); + } + /** * Returns the path. If the path has not been initialized yet, this class is * synchronized, and will instantiate the path object. @@ -436,12 +454,12 @@ public class Check extends Update { } /** - * Get the value of suppressionFile. + * Gets suppression file paths. * - * @return the value of suppressionFile + * @return the suppression files. */ - public String getSuppressionFile() { - return suppressionFile; + public List getSuppressionFiles() { + return suppressionFiles; } /** @@ -451,6 +469,7 @@ public class Check extends Update { */ public void setSuppressionFile(String suppressionFile) { this.suppressionFile = suppressionFile; + suppressionFiles.add(suppressionFile); } /** @@ -742,6 +761,7 @@ public class Check extends Update { public void setNodeAnalyzerEnabled(Boolean nodeAnalyzerEnabled) { this.nodeAnalyzerEnabled = nodeAnalyzerEnabled; } + /** * Get the value of nspAnalyzerEnabled. * @@ -750,6 +770,7 @@ public class Check extends Update { public Boolean isNspAnalyzerEnabled() { return nspAnalyzerEnabled; } + /** * Set the value of nspAnalyzerEnabled. * @@ -1013,7 +1034,7 @@ public class Check extends Update { protected void populateSettings() throws BuildException { super.populateSettings(); Settings.setBooleanIfNotNull(Settings.KEYS.AUTO_UPDATE, autoUpdate); - Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile); + Settings.setArrayIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFiles.toArray(new String[suppressionFiles.size()])); Settings.setStringIfNotEmpty(Settings.KEYS.HINTS_FILE, hintsFile); Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_EXPERIMENTAL_ENABLED, enableExperimental); Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_JAR_ENABLED, jarAnalyzerEnabled); diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/SuppressionFile.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/SuppressionFile.java new file mode 100644 index 000000000..0cc3c3364 --- /dev/null +++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/SuppressionFile.java @@ -0,0 +1,51 @@ +/* + * This file is part of dependency-check-ant. + * + * 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) 2017 The OWASP Foundation. All Rights Reserved. + */ +package org.owasp.dependencycheck.taskdefs; + +/** + * Class : {@link SuppressionFile} Responsibility : Models a suppression file + * nested XML element where the simple content is its location. + * + * @author Phillip Whittlesea + */ +public class SuppressionFile { + + /** + * The path to the suppression file. + */ + private String path; + + /** + * Called by ant with the simple content of the suppressionFile xml element. + * + * @param text the simple content. + */ + public final void addText(String text) { + this.path = text; + } + + /** + * Gets the path to the suppression file. + * + * @return the path. + */ + public String getPath() { + return path; + } + +} diff --git a/dependency-check-ant/src/site/markdown/configuration.md b/dependency-check-ant/src/site/markdown/configuration.md index 9155b8124..22f9a2164 100644 --- a/dependency-check-ant/src/site/markdown/configuration.md +++ b/dependency-check-ant/src/site/markdown/configuration.md @@ -18,6 +18,7 @@ the project's dependencies. reportoutputdirectory="${basedir}" reportformat="ALL"> + path/to/suppression.xml @@ -38,14 +39,19 @@ failOnError | Whether the build should fail if there is an error execu projectName | The name of the project being scanned. | Dependency-Check reportFormat | The report format to be generated (HTML, XML, CSV, JSON, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML reportOutputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build | 'target' -suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html) |   hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html) |   proxyServer | The Proxy Server; see the [proxy configuration](../data/proxy.html) page for more information. |   proxyPort | The Proxy Port. |   proxyUsername | Defines the proxy user name. |   proxyPassword | Defines the proxy password. |   connectionTimeout | The URL Connection Timeout. |   -enableExperimental | Enable the [experimental analyzers](../analyzers/index.html). If not enabled the experimental analyzers (see below) will not be loaded or used. | false +enableExperimental | Enable the [experimental analyzers](../analyzers/index.html). If not enabled the experimental analyzers (see below) will not be loaded or used. | false + +The following nested elements can be set on the dependency-check task. + +Property | Description | Default Value +----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------- +suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html). Element can be specified multiple times. |   Analyzer Configuration ==================== diff --git a/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java b/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java index ffab315f3..1e86afed6 100644 --- a/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java +++ b/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java @@ -114,4 +114,54 @@ public class DependencyCheckTaskTest { expectedException.expect(BuildException.class); buildFileRule.executeTarget("failCVSS"); } + + /** + * Test the DependencyCheckTask where a CVE is suppressed. + */ + @Test + public void testSuppressingCVE() { + // GIVEN an ant task with a vulnerability + final String antTaskName = "suppression"; + + // WHEN executing the ant task + buildFileRule.executeTarget(antTaskName); + + // THEN the ant task executed without error + final File report = new File("target/suppression-report.html"); + assertTrue("Expected the DependencyCheck report to be generated", report.exists()); + } + + /** + * Test the DependencyCheckTask deprecated suppression property throws an + * exception with a warning. + */ + @Test + public void testSuppressingSingle() { + // GIVEN an ant task with a vulnerability using the legacy property + final String antTaskName = "suppression-single"; + + // WHEN executing the ant task + buildFileRule.executeTarget(antTaskName); + + // THEN the ant task executed without error + final File report = new File("target/suppression-single-report.html"); + assertTrue("Expected the DependencyCheck report to be generated", report.exists()); + } + + /** + * Test the DependencyCheckTask deprecated suppression property throws an + * exception with a warning. + */ + @Test + public void testSuppressingMultiple() { + // GIVEN an ant task with a vulnerability using multiple was to configure the suppression file + final String antTaskName = "suppression-multiple"; + + // WHEN executing the ant task + buildFileRule.executeTarget(antTaskName); + + // THEN the ant task executed without error + final File report = new File("target/suppression-multiple-report.html"); + assertTrue("Expected the DependencyCheck report to be generated", report.exists()); + } } diff --git a/dependency-check-ant/src/test/resources/build.xml b/dependency-check-ant/src/test/resources/build.xml index 11808a5b9..68a71e62d 100644 --- a/dependency-check-ant/src/test/resources/build.xml +++ b/dependency-check-ant/src/test/resources/build.xml @@ -71,4 +71,47 @@ + + + + ${project.build.directory}/test-classes/test-suppression1.xml + ${project.build.directory}/test-classes/test-suppression2.xml + + + + + + + + + + + + + + + + ${project.build.directory}/test-classes/test-suppression2.xml + + + + + + diff --git a/dependency-check-ant/src/test/resources/test-suppression1.xml b/dependency-check-ant/src/test/resources/test-suppression1.xml new file mode 100644 index 000000000..ff324590e --- /dev/null +++ b/dependency-check-ant/src/test/resources/test-suppression1.xml @@ -0,0 +1,27 @@ + + + + + + ^org\.apache\.axis:axis:.*$ + cpe:/a:apache:axis + + diff --git a/dependency-check-ant/src/test/resources/test-suppression2.xml b/dependency-check-ant/src/test/resources/test-suppression2.xml new file mode 100644 index 000000000..4fd833855 --- /dev/null +++ b/dependency-check-ant/src/test/resources/test-suppression2.xml @@ -0,0 +1,41 @@ + + + + + + ^jetty:org\.mortbay\.jetty:.*$ + cpe:/a:jetty:jetty + + + + ^jetty:org\.mortbay\.jetty:.*$ + cpe:/a:mortbay:jetty + + + + ^jetty:org\.mortbay\.jetty:.*$ + cpe:/a:mortbay_jetty:jetty + + diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java index 57c5cbf74..9d1746f14 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java @@ -387,7 +387,7 @@ public class App { final String proxyPass = cli.getProxyPassword(); final String dataDirectory = cli.getDataDirectory(); final File propertiesFile = cli.getPropertiesFile(); - final String suppressionFile = cli.getSuppressionFile(); + final String[] suppressionFiles = cli.getSuppressionFiles(); final String hintsFile = cli.getHintsFile(); final String nexusUrl = cli.getNexusUrl(); final String databaseDriverName = cli.getDatabaseDriverName(); @@ -436,10 +436,11 @@ public class App { Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME, proxyUser); Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPass); Settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout); - Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile); Settings.setStringIfNotEmpty(Settings.KEYS.HINTS_FILE, hintsFile); Settings.setIntIfNotNull(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, cveValidForHours); + Settings.setArrayIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFiles); + //File Type Analyzer Settings Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_EXPERIMENTAL_ENABLED, experimentalEnabled); diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java index aa0a89560..8df1725dd 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java @@ -273,8 +273,9 @@ public final class CliParser { .desc("Sets how deep nested symbolic links will be followed; 0 indicates symbolic links will not be followed.") .build(); - final Option suppressionFile = Option.builder().argName("file").hasArg().longOpt(ARGUMENT.SUPPRESSION_FILE) - .desc("The file path to the suppression XML file.") + final Option suppressionFile = Option.builder().argName("file").hasArgs().longOpt(ARGUMENT.SUPPRESSION_FILES) + .desc("The file path to the suppression XML file. This can be specified more then once to utilize multiple " + + "suppression files") .build(); final Option hintsFile = Option.builder().argName("file").hasArg().longOpt(ARGUMENT.HINTS_FILE) @@ -735,7 +736,8 @@ public final class CliParser { public boolean isNodeJsDisabled() { return hasDisableOption(ARGUMENT.DISABLE_NODE_JS, Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED); } -/** + + /** * Returns true if the disableNSP command line argument was specified. * * @return true if the disableNSP command line argument was specified; @@ -1031,12 +1033,12 @@ public final class CliParser { } /** - * Returns the path to the suppression file. + * Returns the paths to the suppression files. * - * @return the path to the suppression file + * @return the paths to the suppression files. */ - public String getSuppressionFile() { - return line.getOptionValue(ARGUMENT.SUPPRESSION_FILE); + public String[] getSuppressionFiles() { + return line.getOptionValues(ARGUMENT.SUPPRESSION_FILES); } /** @@ -1374,9 +1376,9 @@ public final class CliParser { public static final String SYM_LINK_DEPTH = "symLink"; /** * The CLI argument name for setting the location of the suppression - * file. + * file(s). */ - public static final String SUPPRESSION_FILE = "suppression"; + public static final String SUPPRESSION_FILES = "suppression"; /** * The CLI argument name for setting the location of the hint file. */ diff --git a/dependency-check-cli/src/site/markdown/arguments.md b/dependency-check-cli/src/site/markdown/arguments.md index 6a09f4694..f418e0595 100644 --- a/dependency-check-cli/src/site/markdown/arguments.md +++ b/dependency-check-cli/src/site/markdown/arguments.md @@ -14,7 +14,7 @@ Short | Argument Name   | Parameter | Description | Requir | \-\-failOnCvss | \ | If the score set between 0 and 10 the exit code from dependency-check will indicate if a vulnerability with a CVSS score equal to or higher was identified. | Optional \-l | \-\-log | \ | The file path to write verbose logging information. | Optional \-n | \-\-noupdate | | Disables the automatic updating of the CPE data. | Optional - | \-\-suppression | \ | The file path to the suppression XML file; used to suppress [false positives](../general/suppression.html). | Optional + | \-\-suppression | \ | The file paths to the suppression XML files; used to suppress [false positives](../general/suppression.html). This can be specified more then once to utilize multiple suppression files. | Optional \-h | \-\-help | | Print the help message. | Optional | \-\-advancedHelp | | Print the advanced help message. | Optional \-v | \-\-version | | Print the version information. | Optional diff --git a/dependency-check-cli/src/test/java/org/owasp/dependencycheck/AppTest.java b/dependency-check-cli/src/test/java/org/owasp/dependencycheck/AppTest.java index 9659c5241..4a2346a31 100644 --- a/dependency-check-cli/src/test/java/org/owasp/dependencycheck/AppTest.java +++ b/dependency-check-cli/src/test/java/org/owasp/dependencycheck/AppTest.java @@ -13,28 +13,59 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright (c) 2017 The OWASP Foundatio. All Rights Reserved. + * Copyright (c) 2017 The OWASP Foundation. All Rights Reserved. */ package org.owasp.dependencycheck; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + import java.io.File; import java.io.FileNotFoundException; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; + import org.apache.commons.cli.ParseException; import org.apache.commons.cli.UnrecognizedOptionException; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.rules.ExpectedException; import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; +import org.owasp.dependencycheck.utils.Settings.KEYS; /** - * - * @author jeremy + * Tests for the {@link AppTest} class. */ public class AppTest { + /** + * Test rule for asserting exceptions and their contents. + */ + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + /** + * Initialize the {@link Settings} singleton. + */ + @Before + public void setUp() { + Settings.initialize(); + } + + /** + * Clean the {@link Settings} singleton. + */ + @After + public void tearDown() { + Settings.cleanup(); + } + /** * Test of ensureCanonicalPath method, of class App. */ @@ -52,20 +83,20 @@ public class AppTest { assertTrue("result=" + result, result.endsWith(expResult)); } - @Test(expected = UnrecognizedOptionException.class) - public void testPopulateSettingsException() throws FileNotFoundException, ParseException, InvalidSettingException, URISyntaxException { - String[] args = {"-invalidPROPERTY"}; - assertTrue(testBooleanProperties(args, null)); - } - + /** + * Assert that boolean properties can be set on the CLI and parsed into the + * {@link Settings} singleton. + * + * @throws Exception the unexpected {@link Exception}. + */ @Test - public void testPopulateSettings() throws FileNotFoundException, ParseException, InvalidSettingException, URISyntaxException { + public void testPopulateSettings() throws Exception { File prop = new File(this.getClass().getClassLoader().getResource("sample.properties").toURI().getPath()); String[] args = {"-P", prop.getAbsolutePath()}; Map expected = new HashMap<>(); expected.put(Settings.KEYS.AUTO_UPDATE, Boolean.FALSE); expected.put(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, Boolean.TRUE); - + assertTrue(testBooleanProperties(args, expected)); String[] args2 = {"-n"}; @@ -103,8 +134,67 @@ public class AppTest { expected.put(Settings.KEYS.AUTO_UPDATE, Boolean.FALSE); expected.put(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, Boolean.FALSE); assertTrue(testBooleanProperties(args8, expected)); + } - + /** + * Assert that an {@link UnrecognizedOptionException} is thrown when a + * property that is not supported is specified on the CLI. + * + * @throws Exception the unexpected {@link Exception}. + */ + @Test + public void testPopulateSettingsException() throws Exception { + String[] args = {"-invalidPROPERTY"}; + + expectedException.expect(UnrecognizedOptionException.class); + expectedException.expectMessage("Unrecognized option: -invalidPROPERTY"); + testBooleanProperties(args, null); + } + + /** + * Assert that a single suppression file can be set using the CLI. + * + * @throws Exception the unexpected {@link Exception}. + */ + @Test + public void testPopulatingSuppressionSettingsWithASingleFile() throws Exception { + // GIVEN CLI properties with the mandatory arguments + File prop = new File(this.getClass().getClassLoader().getResource("sample.properties").toURI().getPath()); + + // AND a single suppression file + String[] args = {"-P", prop.getAbsolutePath(), "--suppression", "another-file.xml"}; + + // WHEN parsing the CLI arguments + final CliParser cli = new CliParser(); + cli.parse(args); + final App classUnderTest = new App(); + classUnderTest.populateSettings(cli); + + // THEN the suppression file is set in the settings singleton for use in the application core + assertThat("Expected the suppression file to be set in the Settings singleton", Settings.getString(KEYS.SUPPRESSION_FILE), is("another-file.xml")); + } + + /** + * Assert that multiple suppression files can be set using the CLI. + * + * @throws Exception the unexpected {@link Exception}. + */ + @Test + public void testPopulatingSuppressionSettingsWithMultipleFiles() throws Exception { + // GIVEN CLI properties with the mandatory arguments + File prop = new File(this.getClass().getClassLoader().getResource("sample.properties").toURI().getPath()); + + // AND a single suppression file + String[] args = {"-P", prop.getAbsolutePath(), "--suppression", "first-file.xml", "another-file.xml"}; + + // WHEN parsing the CLI arguments + final CliParser cli = new CliParser(); + cli.parse(args); + final App classUnderTest = new App(); + classUnderTest.populateSettings(cli); + + // THEN the suppression file is set in the settings singleton for use in the application core + assertThat("Expected the suppression files to be set in the Settings singleton with a separator", Settings.getString(KEYS.SUPPRESSION_FILE), is("first-file.xml,another-file.xml")); } private boolean testBooleanProperties(String[] args, Map expected) throws URISyntaxException, FileNotFoundException, ParseException, InvalidSettingException { @@ -124,4 +214,5 @@ public class AppTest { Settings.cleanup(); } } + } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java index bc48e6412..da6ac40bc 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java @@ -71,7 +71,7 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer { try { loadSuppressionData(); } catch (SuppressionParseException ex) { - throw new InitializationException("Error initializing the suppression analyzer", ex); + throw new InitializationException("Error initializing the suppression analyzer: " + ex.getLocalizedMessage(), ex); } } @@ -99,23 +99,41 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer { } /** - * Loads the suppression rules file. + * Loads all the suppression rules files configured in the {@link Settings} singleton. * * @throws SuppressionParseException thrown if the XML cannot be parsed. */ private void loadSuppressionData() throws SuppressionParseException { final SuppressionParser parser = new SuppressionParser(); - File file = null; try { final InputStream in = FileUtils.getResourceAsStream("dependencycheck-base-suppression.xml"); rules = parser.parseSuppressionRules(in); } catch (SAXException ex) { throw new SuppressionParseException("Unable to parse the base suppression data file", ex); } - final String suppressionFilePath = Settings.getString(Settings.KEYS.SUPPRESSION_FILE); - if (suppressionFilePath == null) { + final String[] suppressionFilePaths = Settings.getArray(Settings.KEYS.SUPPRESSION_FILE); + if (suppressionFilePaths == null || suppressionFilePaths.length == 0) { return; } + + // Load all the suppression file paths + for (final String suppressionFilePath : suppressionFilePaths) { + loadSuppressionFile(parser, suppressionFilePath); + } + LOGGER.debug("{} suppression rules were loaded.", rules.size()); + } + + /** + * Load a single suppression rules file from the path provided using the parser provided. + * + * @param parser the parser to use for loading the file. + * @param suppressionFilePath the path to load. + * @throws SuppressionParseException thrown if the suppression file cannot be loaded and parsed. + */ + private void loadSuppressionFile(final SuppressionParser parser, final String suppressionFilePath) throws SuppressionParseException { + LOGGER.debug("Loading suppression rules from '{}'", suppressionFilePath); + + File file = null; boolean deleteTempFile = false; try { final Pattern uriRx = Pattern.compile("^(https?|file)\\:.*", Pattern.CASE_INSENSITIVE); @@ -153,7 +171,6 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer { } try { rules.addAll(parser.parseSuppressionRules(file)); - LOGGER.debug("{} suppression rules were loaded.", rules.size()); } catch (SuppressionParseException ex) { LOGGER.warn("Unable to parse suppression xml file '{}'", file.getPath()); LOGGER.warn(ex.getMessage()); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java index 119d90bd0..704cf32dd 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java @@ -17,30 +17,34 @@ */ package org.owasp.dependencycheck.analyzer; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.Set; + import org.junit.Before; import org.junit.Test; import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Dependency; -import org.owasp.dependencycheck.xml.suppression.SuppressionRule; -import org.owasp.dependencycheck.utils.Settings; -import org.slf4j.LoggerFactory; - -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.util.List; -import java.util.Set; - -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import org.owasp.dependencycheck.exception.InitializationException; +import org.owasp.dependencycheck.utils.Settings; +import org.owasp.dependencycheck.utils.Settings.KEYS; /** * @author Jeremy Long */ public class AbstractSuppressionAnalyzerTest extends BaseTest { + /** A second suppression file to test with. */ + private static final String OTHER_SUPPRESSIONS_FILE = "other-suppressions.xml"; + + /** Suppression file to test with. */ + private static final String SUPPRESSIONS_FILE = "suppressions.xml"; + private AbstractSuppressionAnalyzer instance; @Before @@ -64,24 +68,42 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest { */ @Test public void testGetRulesFromSuppressionFileFromURL() throws Exception { - setSupressionFileFromURL(); - instance.initialize(); - int expCount = 5; - List result = instance.getRules(); - assertTrue(expCount <= result.size()); + final String fileUrl = getClass().getClassLoader().getResource(SUPPRESSIONS_FILE).toURI().toURL().toString(); + final int numberOfExtraLoadedRules = getNumberOfRulesLoadedFromPath(fileUrl) - getNumberOfRulesLoadedInCoreFile(); + assertEquals("Expected 5 extra rules in the given path", 5, numberOfExtraLoadedRules); } /** * Test of getRules method, of class AbstractSuppressionAnalyzer for - * suppression file declared as URL. + * suppression file on the classpath. */ @Test public void testGetRulesFromSuppressionFileInClasspath() throws Exception { - Settings.setString(Settings.KEYS.SUPPRESSION_FILE, "suppressions.xml"); + final int numberOfExtraLoadedRules = getNumberOfRulesLoadedFromPath(SUPPRESSIONS_FILE) - getNumberOfRulesLoadedInCoreFile(); + assertEquals("Expected 5 extra rules in the given file", 5, numberOfExtraLoadedRules); + } + + /** + * Assert that rules are loaded from multiple files if multiple files are denfined in the {@link Settings} singleton. + */ + @Test + public void testGetRulesFromMultipleSuppressionFiles() throws Exception { + final int rulesInCoreFile = getNumberOfRulesLoadedInCoreFile(); + + // GIVEN suppression rules from one file + final int rulesInFirstFile = getNumberOfRulesLoadedFromPath(SUPPRESSIONS_FILE) - rulesInCoreFile; + + // AND suppression rules from another file + final int rulesInSecondFile = getNumberOfRulesLoadedFromPath(OTHER_SUPPRESSIONS_FILE) - rulesInCoreFile; + + // WHEN initializing with both suppression files + final String[] suppressionFiles = { SUPPRESSIONS_FILE, OTHER_SUPPRESSIONS_FILE }; + Settings.setArrayIfNotEmpty(KEYS.SUPPRESSION_FILE, suppressionFiles); instance.initialize(); - int expCount = 5; - int currentSize = instance.getRules().size(); - assertTrue(expCount <= currentSize); + + // THEN rules from both files were loaded + final int expectedSize = rulesInFirstFile + rulesInSecondFile + rulesInCoreFile; + assertThat("Expected suppressions from both files", instance.getRules().size(), is(expectedSize)); } @Test(expected = InitializationException.class) @@ -90,15 +112,33 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest { instance.initialize(); } - private void setSupressionFileFromURL() throws Exception { - try { - final String uri = this.getClass().getClassLoader().getResource("suppressions.xml").toURI().toURL().toString(); - Settings.setString(Settings.KEYS.SUPPRESSION_FILE, uri); - } catch (URISyntaxException ex) { - LoggerFactory.getLogger(AbstractSuppressionAnalyzerTest.class).error("", ex); - } catch (MalformedURLException ex) { - LoggerFactory.getLogger(AbstractSuppressionAnalyzerTest.class).error("", ex); - } + /** + * Return the number of rules that are loaded from the core suppression file. + * + * @return the number of rules defined in the core suppresion file. + * @throws Exception if loading the rules fails. + */ + private int getNumberOfRulesLoadedInCoreFile() throws Exception { + Settings.removeProperty(KEYS.SUPPRESSION_FILE); + + final AbstractSuppressionAnalyzerImpl coreFileAnalyzer = new AbstractSuppressionAnalyzerImpl(); + coreFileAnalyzer.initialize(); + return coreFileAnalyzer.getRules().size(); + } + + /** + * Load a file into the {@link AbstractSuppressionAnalyzer} and return the number of rules loaded. + * + * @param path the path to load. + * @return the number of rules that were loaded (including the core rules). + * @throws Exception if loading the rules fails. + */ + private int getNumberOfRulesLoadedFromPath(final String path) throws Exception { + Settings.setString(KEYS.SUPPRESSION_FILE, path); + + final AbstractSuppressionAnalyzerImpl fileAnalyzer = new AbstractSuppressionAnalyzerImpl(); + fileAnalyzer.initialize(); + return fileAnalyzer.getRules().size(); } public class AbstractSuppressionAnalyzerImpl extends AbstractSuppressionAnalyzer { diff --git a/dependency-check-core/src/test/resources/other-suppressions.xml b/dependency-check-core/src/test/resources/other-suppressions.xml new file mode 100644 index 000000000..f0b4fad7b --- /dev/null +++ b/dependency-check-core/src/test/resources/other-suppressions.xml @@ -0,0 +1,27 @@ + + + + + + ^com\.fasterxml\.jackson.*:.*:.*$ + cpe:/a:fasterxml:jackson + + diff --git a/dependency-check-maven/src/it/629-jackson-dataformat/pom.xml b/dependency-check-maven/src/it/629-jackson-dataformat/pom.xml index f1e6cd154..0c7cce1f4 100644 --- a/dependency-check-maven/src/it/629-jackson-dataformat/pom.xml +++ b/dependency-check-maven/src/it/629-jackson-dataformat/pom.xml @@ -14,7 +14,7 @@ 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) 2013 Jeremy Long. All Rights Reserved. +Copyright (c) 2017 Jeremy Long. All Rights Reserved. --> 4.0.0 @@ -22,8 +22,8 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. test-dataformat-jackson 1.0.0-SNAPSHOT jar - - + + com.fasterxml.jackson.core jackson-databind 2.4.5 @@ -38,10 +38,10 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. jackson-dataformat-cbor 2.4.5 - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.4.5 - - - \ No newline at end of file + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.4.5 + + + diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files-configs/invoker.properties b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/invoker.properties new file mode 100644 index 000000000..9c2542d23 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/invoker.properties @@ -0,0 +1,18 @@ +# +# This file is part of dependency-check-core. +# +# 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) 2017 The OWASP Foundation. All Rights Reserved. +# +invoker.goals = install ${project.groupId}:${project.artifactId}:${project.version}:check diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files-configs/pom.xml b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/pom.xml new file mode 100644 index 000000000..cec15d819 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/pom.xml @@ -0,0 +1,54 @@ + + + + 4.0.0 + org.owasp.test + test-multiple-suppression-files + 1.0.0-SNAPSHOT + jar + + + + + com.vaadin.external.google + android-json + 0.0.20131108.vaadin1 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.4.5 + + + + + + + org.owasp + dependency-check-maven + + ${project.basedir}/test-suppression1.xml + + ${project.basedir}/test-suppression2.xml + + + + + + diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files-configs/postbuild.groovy b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/postbuild.groovy new file mode 100644 index 000000000..2a42192d9 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/postbuild.groovy @@ -0,0 +1,35 @@ +/* + * This file is part of dependency-check-core. + * + * 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) 2017 The OWASP Foundation. All Rights Reserved. + */ + +import org.apache.commons.io.FileUtils +import org.apache.commons.lang.StringUtils + +import java.nio.charset.Charset + +// Check that suppression worked. +String log = FileUtils.readFileToString(new File(basedir, "build.log"), Charset.defaultCharset().name()); +int count = StringUtils.countMatches(log, "CVE-2016-5696"); +if (count > 0) { + System.out.println(String.format("CVE-2016-5696 (android-json-0.0.20131108.vaadin1.jar) was identified and should be suppressed")); + return false; +} +count = StringUtils.countMatches(log, "CVE-2016-7051"); +if (count > 0) { + System.out.println(String.format("CVE-2016-7051 (jackson-module-jaxb-annotations-2.4.5.jar) was identified and should be suppressed")); + return false; +} diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files-configs/test-suppression1.xml b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/test-suppression1.xml new file mode 100644 index 000000000..e3bd8c0b0 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/test-suppression1.xml @@ -0,0 +1,27 @@ + + + + + + ^com\.vaadin\.external\.google:android-json:.*$ + cpe:/a:google:android + + diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files-configs/test-suppression2.xml b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/test-suppression2.xml new file mode 100644 index 000000000..f0b4fad7b --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files-configs/test-suppression2.xml @@ -0,0 +1,27 @@ + + + + + + ^com\.fasterxml\.jackson.*:.*:.*$ + cpe:/a:fasterxml:jackson + + diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files/invoker.properties b/dependency-check-maven/src/it/730-multiple-suppression-files/invoker.properties new file mode 100644 index 000000000..9c2542d23 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files/invoker.properties @@ -0,0 +1,18 @@ +# +# This file is part of dependency-check-core. +# +# 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) 2017 The OWASP Foundation. All Rights Reserved. +# +invoker.goals = install ${project.groupId}:${project.artifactId}:${project.version}:check diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files/pom.xml b/dependency-check-maven/src/it/730-multiple-suppression-files/pom.xml new file mode 100644 index 000000000..1b64e9518 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files/pom.xml @@ -0,0 +1,54 @@ + + + + 4.0.0 + org.owasp.test + test-multiple-suppression-files + 1.0.0-SNAPSHOT + jar + + + + + com.vaadin.external.google + android-json + 0.0.20131108.vaadin1 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.4.5 + + + + + + + org.owasp + dependency-check-maven + + + ${project.basedir}/test-suppression1.xml + ${project.basedir}/test-suppression2.xml + + + + + + diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files/postbuild.groovy b/dependency-check-maven/src/it/730-multiple-suppression-files/postbuild.groovy new file mode 100644 index 000000000..2a42192d9 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files/postbuild.groovy @@ -0,0 +1,35 @@ +/* + * This file is part of dependency-check-core. + * + * 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) 2017 The OWASP Foundation. All Rights Reserved. + */ + +import org.apache.commons.io.FileUtils +import org.apache.commons.lang.StringUtils + +import java.nio.charset.Charset + +// Check that suppression worked. +String log = FileUtils.readFileToString(new File(basedir, "build.log"), Charset.defaultCharset().name()); +int count = StringUtils.countMatches(log, "CVE-2016-5696"); +if (count > 0) { + System.out.println(String.format("CVE-2016-5696 (android-json-0.0.20131108.vaadin1.jar) was identified and should be suppressed")); + return false; +} +count = StringUtils.countMatches(log, "CVE-2016-7051"); +if (count > 0) { + System.out.println(String.format("CVE-2016-7051 (jackson-module-jaxb-annotations-2.4.5.jar) was identified and should be suppressed")); + return false; +} diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files/test-suppression1.xml b/dependency-check-maven/src/it/730-multiple-suppression-files/test-suppression1.xml new file mode 100644 index 000000000..e3bd8c0b0 --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files/test-suppression1.xml @@ -0,0 +1,27 @@ + + + + + + ^com\.vaadin\.external\.google:android-json:.*$ + cpe:/a:google:android + + diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files/test-suppression2.xml b/dependency-check-maven/src/it/730-multiple-suppression-files/test-suppression2.xml new file mode 100644 index 000000000..f0b4fad7b --- /dev/null +++ b/dependency-check-maven/src/it/730-multiple-suppression-files/test-suppression2.xml @@ -0,0 +1,27 @@ + + + + + + ^com\.fasterxml\.jackson.*:.*:.*$ + cpe:/a:fasterxml:jackson + + diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index f36c5251b..941864d3e 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import java.util.List; import java.util.Locale; import org.apache.maven.artifact.Artifact; @@ -199,11 +200,15 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma @Parameter(property = "connectionTimeout", defaultValue = "", required = false) private String connectionTimeout; /** - * The path to the suppression file. + * The paths to the suppression files. */ - @Parameter(property = "suppressionFile", defaultValue = "", required = false) + @Parameter(required = false) + private String[] suppressionFiles; + /** + * The paths to the suppression file. + */ + @Parameter(required = false) private String suppressionFile; - /** * The path to the hints file. */ @@ -415,7 +420,8 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma private boolean skipSystemScope = false; /** - * Skip analysis for dependencies which type matches this regular expression. + * Skip analysis for dependencies which type matches this regular + * expression. */ @SuppressWarnings("CanBeFinal") @Parameter(property = "skipArtifactType", required = false) @@ -488,7 +494,6 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma */ private Filter artifactTypeExcluded; - // // /** @@ -660,8 +665,8 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma List nodes, ProjectBuildingRequest buildingRequest) { ExceptionCollection exCol = null; for (DependencyNode dependencyNode : nodes) { - if (artifactScopeExcluded.passes(dependencyNode.getArtifact().getScope()) || - artifactTypeExcluded.passes(dependencyNode.getArtifact().getType())) { + if (artifactScopeExcluded.passes(dependencyNode.getArtifact().getScope()) + || artifactTypeExcluded.passes(dependencyNode.getArtifact().getType())) { continue; } exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); @@ -686,7 +691,8 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma } if (!isResolved) { getLog().error("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString()); - exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString())); + exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: " + + dependencyNode.toNodeString())); } } else { final ArtifactCoordinate coordinate = TransferUtils.toArtifactCoordinate(dependencyNode.getArtifact()); @@ -924,9 +930,10 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma Settings.setStringIfNotNull(Settings.KEYS.PROXY_PASSWORD, password); Settings.setStringIfNotNull(Settings.KEYS.PROXY_NON_PROXY_HOSTS, proxy.getNonProxyHosts()); } + final String[] suppressions = determineSuppressions(); + Settings.setArrayIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressions); Settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout); - Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile); Settings.setStringIfNotEmpty(Settings.KEYS.HINTS_FILE, hintsFile); //File Type Analyzer Settings @@ -1014,6 +1021,25 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma artifactTypeExcluded = new ArtifactTypeExcluded(skipArtifactType); } + /** + * Combines the configured suppressionFile and suppressionFiles into a + * single array. + * + * @return an array of suppression file paths + */ + private String[] determineSuppressions() { + String[] suppressions = suppressionFiles; + if (suppressionFile != null) { + if (suppressions == null) { + suppressions = new String[]{suppressionFile}; + } else { + suppressions = Arrays.copyOf(suppressions, suppressions.length + 1); + suppressions[suppressions.length - 1] = suppressionFile; + } + } + return suppressions; + } + /** * Returns the maven proxy. * diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md index 4d801965a..b2abb8686 100644 --- a/dependency-check-maven/src/site/markdown/configuration.md +++ b/dependency-check-maven/src/site/markdown/configuration.md @@ -28,7 +28,7 @@ skipRuntimeScope | Skip analysis for artifacts with Runtime Scope. skipSystemScope | Skip analysis for artifacts with System Scope. | false skipTestScope | Skip analysis for artifacts with Test Scope. | true skipArtifactType | A regular expression used to filter/skip artifact types. |   -suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html). |   +suppressionFiles | The file paths to the XML suppression files \- used to suppress [false positives](../general/suppression.html). |   hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html). |   enableExperimental | Enable the [experimental analyzers](../analyzers/index.html). If not enabled the experimental analyzers (see below) will not be loaded or used. | false diff --git a/dependency-check-maven/src/site/markdown/index.md.vm b/dependency-check-maven/src/site/markdown/index.md.vm index 6490307fe..43815f4ab 100644 --- a/dependency-check-maven/src/site/markdown/index.md.vm +++ b/dependency-check-maven/src/site/markdown/index.md.vm @@ -204,3 +204,39 @@ Update the local cache of the NVD data from NIST without analyzing the dependenc ... ``` + +$H$H$H Example 7: +Suppress false positives using multiple suppression files (E.g. a company-wide suppression file and a local project file). + +```xml + + ... + + ... + + ... + + org.owasp + dependency-check-maven + ${project.version} + + + http://example.org/suppression.xml + project-suppression.xml + + + + + + check + + + + + ... + + ... + + ... + +``` diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java index b4a898192..50df24f14 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java @@ -151,10 +151,10 @@ public final class FileUtils { } /** - * Gets the {@link InputStream} for this resource + * Gets the {@link InputStream} for this resource. * * @param resource path - * @return + * @return the input stream for the given resource */ public static InputStream getResourceAsStream(String resource) { return FileUtils.class.getClassLoader() != null diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java index a2f142479..cb4eece09 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java @@ -33,6 +33,8 @@ import java.security.ProtectionDomain; import java.util.Enumeration; import java.util.Properties; +import org.apache.commons.lang3.StringUtils; + /** * A simple settings container that wraps the dependencycheck.properties file. * @@ -48,6 +50,10 @@ public final class Settings { * The properties file location. */ private static final String PROPERTIES_FILE = "dependencycheck.properties"; + /** + * Array separator. + */ + private static final String ARRAY_SEP = ","; /** * Thread local settings. */ @@ -592,6 +598,18 @@ public final class Settings { } } + /** + * Sets a property value only if the array value is not null and not empty. + * + * @param key the key for the property + * @param value the value for the property + */ + public static void setArrayIfNotEmpty(String key, String[] value) { + if (null != value && value.length > 0) { + setString(key, StringUtils.join(value, ARRAY_SEP)); + } + } + /** * Sets a property value. * @@ -745,7 +763,7 @@ public final class Settings { private static File getJarPath() { String decodedPath = "."; String jarPath = ""; - ProtectionDomain domain = Settings.class.getProtectionDomain(); + final ProtectionDomain domain = Settings.class.getProtectionDomain(); if (domain != null && domain.getCodeSource() != null && domain.getCodeSource().getLocation() != null) { jarPath = Settings.class.getProtectionDomain().getCodeSource().getLocation().getPath(); } @@ -805,6 +823,22 @@ public final class Settings { return System.getProperty(key, LOCAL_SETTINGS.get().props.getProperty(key)); } + /** + * Returns a list with the given key. + * + * If the propery is not set then {@code null} will be returned. + * + * @param key the key to get from this {@link Settings} singleton. + * @return the list or {@code null} if the key wasn't present. + */ + public static String[] getArray(final String key) { + final String string = getString(key); + if (string != null) { + return string.split(ARRAY_SEP); + } + return null; + } + /** * Removes a property from the local properties collection. This is mainly * used in test cases. diff --git a/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/SettingsTest.java b/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/SettingsTest.java index 5d0c96f93..a9087f9e7 100644 --- a/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/SettingsTest.java +++ b/dependency-check-utils/src/test/java/org/owasp/dependencycheck/utils/SettingsTest.java @@ -17,10 +17,18 @@ */ package org.owasp.dependencycheck.utils; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; + import java.io.File; import java.io.IOException; import java.net.URISyntaxException; + +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; /** @@ -29,6 +37,22 @@ import org.junit.Test; */ public class SettingsTest extends BaseTest { + /** + * Initialize the {@link Settings} singleton. + */ + @Before + public void setUp() { + Settings.initialize(); + } + + /** + * Clean the {@link Settings} singleton. + */ + @After + public void tearDown() { + Settings.cleanup(); + } + /** * Test of getString method, of class Settings. */ @@ -220,4 +244,101 @@ public class SettingsTest extends BaseTest { File tmp = Settings.getTempDirectory(); Assert.assertTrue(tmp.exists()); } + + /** + * Assert {@link Settings#getArray(String)} from a delimited string returns + * multiple values in an array. + */ + @Test + public void testGetArrayFromADelimitedString() { + // GIVEN a delimited string + final String delimitedString = "value1,value2"; + Settings.setString("key", delimitedString); + + // WHEN getting the array + final String[] array = Settings.getArray("key"); + + // THEN the split array is returned + assertThat("Expected the array to be non-null", array, notNullValue()); + assertThat("Expected the array to have two values", array.length, is(2)); + assertThat("Expected the first array value to be value1", array[0], is("value1")); + assertThat("Expected the second array value to be value2", array[1], is("value2")); + } + + /** + * Assert {@link Settings#getArray(String)} returns {@code null} if the + * property is not set. + */ + @Test + public void testGetArrayWhereThePropertyIsNotSet() { + // WHEN getting the array + final String[] array = Settings.getArray("key"); + + // THEN null is returned + assertThat("Expected the array to be null", array, nullValue()); + } + + /** + * Assert {@link Settings#setArrayIfNotEmpty(String, String[])} with an + * empty array is ignored. + */ + @Test + public void testSetArrayNotEmptyIgnoresAnEmptyArray() { + // GIVEN an empty array + final String[] array = {}; + + // WHEN setting the array + Settings.setArrayIfNotEmpty("key", array); + + // THEN the property was not set + assertThat("Expected the property to not be set", Settings.getString("key"), nullValue()); + } + + /** + * Assert {@link Settings#setArrayIfNotEmpty(String, String[])} with a null + * array is ignored. + */ + @Test + public void testSetArrayNotEmptyIgnoresAnNullArray() { + // GIVEN a null array + final String[] array = null; + + // WHEN setting the array + Settings.setArrayIfNotEmpty("key", array); + + // THEN the property was not set + assertThat("Expected the property to not be set", Settings.getString("key"), nullValue()); + } + + /** + * Assert {@link Settings#setArrayIfNotEmpty(String, String[])} with + * multiple values sets a delimited string. + */ + @Test + public void testSetArrayNotEmptySetsADelimitedString() { + // GIVEN an array with values + final String[] array = {"value1", "value2"}; + + // WHEN setting the array + Settings.setArrayIfNotEmpty("key", array); + + // THEN the property is set + assertThat("Expected the property to be set", Settings.getString("key"), is("value1,value2")); + } + + /** + * Assert {@link Settings#setArrayIfNotEmpty(String, String[])} with a + * single values sets a string. + */ + @Test + public void testSetArrayNotEmptyWithSingleValueSetsAString() { + // GIVEN an array with a value + final String[] array = {"value1"}; + + // WHEN setting the array + Settings.setArrayIfNotEmpty("key", array); + + // THEN the property is set + assertThat("Expected the property to be set", Settings.getString("key"), is("value1")); + } }