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 0612c0781..96a111c8d 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 2ac6152c6..e1887e783 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,7 +273,7 @@ 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) + final Option suppressionFile = Option.builder().argName("file").hasArgs().longOpt(ARGUMENT.SUPPRESSION_FILES) .desc("The file path to the suppression XML file.") .build(); @@ -1020,12 +1020,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); } /** @@ -1363,9 +1363,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 048c1f193..d8d1f4376 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). | Optional \-h | \-\-help | | Print the help message. | Optional | \-\-advancedHelp | | Print the advanced help message. | Optional \-v | \-\-version | | Print the version information. | Optional @@ -64,4 +64,4 @@ Short | Argument Name        | Paramete | \-\-dbPassword | \ | The password for connecting to the database. |   | \-\-dbUser | \ | The username used to connect to the database. |   \-d | \-\-data | \ | The location of the data directory used to store persistent data. This option should generally not be set. |   - | \-\-purge | | Delete the local copy of the NVD. This is used to force a refresh of the data. |   \ No newline at end of file + | \-\-purge | | Delete the local copy of the NVD. This is used to force a refresh of the data. |   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 06528b0bd..0365bf296 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 @@ -153,7 +153,7 @@ public class AppTest { * @throws Exception the unexpected {@link Exception}. */ @Test - public void testPopulatingSuppressionSettings() throws Exception { + public void testPopulatingSuppressionSettingsWithASingleFile() throws Exception { // GIVEN CLI properties with the mandatory arguments File prop = new File(this.getClass().getClassLoader().getResource("sample.properties").toURI().getPath()); @@ -170,6 +170,29 @@ public class AppTest { 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 { Settings.initialize(); try { 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 da96ad1c6..da8b4435b 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 @@ -32,6 +32,8 @@ import java.net.URLDecoder; import java.util.Enumeration; import java.util.Properties; +import org.apache.commons.lang3.StringUtils; + /** * A simple settings container that wraps the dependencycheck.properties file. * @@ -47,6 +49,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. */ @@ -558,6 +564,18 @@ public final class Settings { LOGGER.debug("Setting: {}='{}'", key, value); } + /** + * Sets a property value from an array. + *

+ * Note: each value of the array will be joined by the delimiter {@link Settings#ARRAY_SEP}. + * + * @param key the key for the property + * @param value the value for the property + */ + static void setArray(String key, String[] value) { + setString(key, StringUtils.join(value, ARRAY_SEP)); + } + /** * Sets a property value only if the value is not null. * @@ -582,6 +600,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) { + setArray(key, value); + } + } + /** * Sets a property value. * 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..79199cbd7 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,17 @@ */ package org.owasp.dependencycheck.utils; +import static org.hamcrest.core.Is.is; +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 +36,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 +243,80 @@ public class SettingsTest extends BaseTest { File tmp = Settings.getTempDirectory(); Assert.assertTrue(tmp.exists()); } + + /** + * 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")); + } + + /** + * Assert {@link Settings#setArray(String, String[])} with multiple values sets a delimited string. + */ + @Test + public void testSetArraySetsADelimitedString() { + // GIVEN an array with values + final String[] array = { "value1", "value2" }; + + // WHEN setting the array + Settings.setArray("key", array); + + // THEN the property is set + assertThat("Expected the property to be set", Settings.getString("key"), is("value1,value2")); + } + }