diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/cli/commands/LogsAndDescribeConfigAreFullyRedactedAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/cli/commands/LogsAndDescribeConfigAreFullyRedactedAcceptanceTest.java index 1c0cc17751b5..e5697093078c 100644 --- a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/cli/commands/LogsAndDescribeConfigAreFullyRedactedAcceptanceTest.java +++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/cli/commands/LogsAndDescribeConfigAreFullyRedactedAcceptanceTest.java @@ -16,25 +16,24 @@ import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL; import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER; +import static org.apache.geode.examples.security.ExampleSecurityManager.SECURITY_JSON; +import static org.apache.geode.test.util.ResourceUtils.createFileFromResource; import static org.apache.geode.test.util.ResourceUtils.getResource; import static org.assertj.core.api.Assertions.assertThat; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.util.Collection; import java.util.Properties; -import java.util.Scanner; -import org.apache.commons.io.FileUtils; -import org.assertj.core.api.SoftAssertions; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.TemporaryFolder; import org.apache.geode.examples.security.ExampleSecurityManager; import org.apache.geode.management.internal.cli.util.CommandStringBuilder; +import org.apache.geode.test.assertj.LogFileAssert; import org.apache.geode.test.junit.categories.LoggingTest; import org.apache.geode.test.junit.categories.SecurityTest; import org.apache.geode.test.junit.rules.RequiresGeodeHome; @@ -53,94 +52,82 @@ @Category({SecurityTest.class, LoggingTest.class}) public class LogsAndDescribeConfigAreFullyRedactedAcceptanceTest { - private static final String sharedPasswordString = "abcdefg"; - - private File propertyFile; - private File securityPropertyFile; + private static final String PASSWORD = "abcdefg"; + @Rule + public RequiresGeodeHome geodeHome = new RequiresGeodeHome(); @Rule public GfshRule gfsh = new GfshRule(); - @Rule - public RequiresGeodeHome geodeHome = new RequiresGeodeHome(); + public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Before public void createDirectoriesAndFiles() throws Exception { - propertyFile = gfsh.getTemporaryFolder().newFile("geode.properties"); - securityPropertyFile = gfsh.getTemporaryFolder().newFile("security.properties"); - - Properties properties = new Properties(); - properties.setProperty(LOG_LEVEL, "debug"); - properties.setProperty("security-username", "propertyFileUser"); - properties.setProperty("security-password", sharedPasswordString + "-propertyFile"); - try (FileOutputStream fileOutputStream = new FileOutputStream(propertyFile)) { - properties.store(fileOutputStream, null); + File geodePropertiesFile = temporaryFolder.newFile("geode.properties"); + File securityPropertiesFile = temporaryFolder.newFile("security.properties"); + + Properties geodeProperties = new Properties(); + geodeProperties.setProperty(LOG_LEVEL, "debug"); + geodeProperties.setProperty("security-username", "propertyFileUser"); + geodeProperties.setProperty("security-password", PASSWORD + "-propertyFile"); + + try (FileOutputStream fileOutputStream = new FileOutputStream(geodePropertiesFile)) { + geodeProperties.store(fileOutputStream, null); } Properties securityProperties = new Properties(); securityProperties.setProperty(SECURITY_MANAGER, ExampleSecurityManager.class.getName()); - securityProperties.setProperty(ExampleSecurityManager.SECURITY_JSON, "security.json"); + securityProperties.setProperty(SECURITY_JSON, "security.json"); securityProperties.setProperty("security-file-username", "securityPropertyFileUser"); - securityProperties.setProperty("security-file-password", - sharedPasswordString + "-securityPropertyFile"); - try (FileOutputStream fileOutputStream = new FileOutputStream(securityPropertyFile)) { + securityProperties.setProperty("security-file-password", PASSWORD + "-securityPropertyFile"); + + try (FileOutputStream fileOutputStream = new FileOutputStream(securityPropertiesFile)) { securityProperties.store(fileOutputStream, null); } - startSecureLocatorAndServer(); - } - - private void startSecureLocatorAndServer() { // The json is in the root resource directory. - String securityJson = - getResource(LogsAndDescribeConfigAreFullyRedactedAcceptanceTest.class, - "/security.json").getPath(); - // We want to add the folder to the classpath, so we strip off the filename. - securityJson = securityJson.substring(0, securityJson.length() - "security.json".length()); - String startLocatorCmd = - new CommandStringBuilder("start locator").addOption("name", "test-locator") - .addOption("properties-file", propertyFile.getAbsolutePath()) - .addOption("security-properties-file", securityPropertyFile.getAbsolutePath()) - .addOption("J", "-Dsecure-username-jd=user-jd") - .addOption("J", "-Dsecure-password-jd=password-jd") - .addOption("classpath", securityJson).getCommandString(); + createFileFromResource(getResource("/security.json"), temporaryFolder.getRoot(), + "security.json"); + + String startLocatorCmd = new CommandStringBuilder("start locator") + .addOption("name", "test-locator") + .addOption("properties-file", geodePropertiesFile.getAbsolutePath()) + .addOption("security-properties-file", securityPropertiesFile.getAbsolutePath()) + .addOption("J", "-Dsecure-username-jd=user-jd") + .addOption("J", "-Dsecure-password-jd=password-jd") + .addOption("classpath", temporaryFolder.getRoot().getAbsolutePath()) + .getCommandString(); String startServerCmd = new CommandStringBuilder("start server") - .addOption("name", "test-server").addOption("user", "viaStartMemberOptions") - .addOption("password", sharedPasswordString + "-viaStartMemberOptions") - .addOption("properties-file", propertyFile.getAbsolutePath()) - .addOption("security-properties-file", securityPropertyFile.getAbsolutePath()) + .addOption("name", "test-server") + .addOption("user", "viaStartMemberOptions") + .addOption("password", PASSWORD + "-viaStartMemberOptions") + .addOption("properties-file", geodePropertiesFile.getAbsolutePath()) + .addOption("security-properties-file", securityPropertiesFile.getAbsolutePath()) .addOption("J", "-Dsecure-username-jd=user-jd") - .addOption("J", "-Dsecure-password-jd=" + sharedPasswordString + "-password-jd") + .addOption("J", "-Dsecure-password-jd=" + PASSWORD + "-password-jd") .addOption("server-port", "0") - .addOption("classpath", securityJson).getCommandString(); + .addOption("classpath", temporaryFolder.getRoot().getAbsolutePath()) + .getCommandString(); gfsh.execute(startLocatorCmd, startServerCmd); } @Test - public void logsDoNotContainStringThatShouldBeRedacted() throws FileNotFoundException { - Collection logs = - FileUtils.listFiles(gfsh.getTemporaryFolder().getRoot(), new String[] {"log"}, true); - - // Use soft assertions to report all redaction failures, not just the first one. - SoftAssertions softly = new SoftAssertions(); - for (File logFile : logs) { - Scanner scanner = new Scanner(logFile); - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - softly.assertThat(line).describedAs("File: %s, Line: %s", logFile.getAbsolutePath(), line) - .doesNotContain(sharedPasswordString); - } + public void logsDoNotContainStringThatShouldBeRedacted() { + File dir = gfsh.getTemporaryFolder().getRoot(); + File[] logFiles = dir.listFiles((d, name) -> name.endsWith(".log")); + + for (File logFile : logFiles) { + LogFileAssert.assertThat(logFile).doesNotContain(PASSWORD); } - softly.assertAll(); } @Test public void describeConfigRedactsJvmArguments() { String connectCommand = new CommandStringBuilder("connect") .addOption("user", "viaStartMemberOptions") - .addOption("password", sharedPasswordString + "-viaStartMemberOptions").getCommandString(); + .addOption("password", PASSWORD + "-viaStartMemberOptions").getCommandString(); String describeLocatorConfigCommand = new CommandStringBuilder("describe config") .addOption("hide-defaults", "false").addOption("member", "test-locator").getCommandString(); @@ -150,6 +137,6 @@ public void describeConfigRedactsJvmArguments() { GfshExecution execution = gfsh.execute(connectCommand, describeLocatorConfigCommand, describeServerConfigCommand); - assertThat(execution.getOutputText()).doesNotContain(sharedPasswordString); + assertThat(execution.getOutputText()).doesNotContain(PASSWORD); } } diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/util/redaction/ArgumentRedactorIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/util/redaction/ArgumentRedactorIntegrationTest.java new file mode 100644 index 000000000000..be358e357889 --- /dev/null +++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/util/redaction/ArgumentRedactorIntegrationTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; + +import org.apache.geode.internal.logging.Banner; + +public class ArgumentRedactorIntegrationTest { + + private static final String someProperty = "redactorTest.someProperty"; + private static final String somePasswordProperty = "redactorTest.aPassword"; + private static final String someOtherPasswordProperty = + "redactorTest.aPassword-withCharactersAfterward"; + + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + @Test + public void systemPropertiesGetRedactedInBanner() { + System.setProperty(someProperty, "isNotRedacted"); + System.setProperty(somePasswordProperty, "isRedacted"); + System.setProperty(someOtherPasswordProperty, "isRedacted"); + + List args = asList("--user=me", "--password=isRedacted", + "--another-password-for-some-reason =isRedacted", "--yet-another-password = isRedacted", + "--one-more-password isRedacted"); + + String banner = new Banner().getString(args.toArray(new String[0])); + + assertThat(banner).doesNotContain("isRedacted"); + } +} diff --git a/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt b/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt index a96907f933ef..99ab950e5412 100644 --- a/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt +++ b/geode-core/src/integrationTest/resources/org/apache/geode/codeAnalysis/excludedClasses.txt @@ -60,6 +60,7 @@ org/apache/geode/internal/shared/TCPSocketOptions org/apache/geode/internal/statistics/platform/LinuxProcFsStatistics$CPU org/apache/geode/internal/tcp/VersionedByteBufferInputStream org/apache/geode/internal/util/concurrent/StoppableReadWriteLock +org/apache/geode/internal/util/redaction/ParserRegex$Group org/apache/geode/logging/internal/LogMessageRegex$Group org/apache/geode/logging/internal/log4j/LogWriterLogger org/apache/geode/logging/internal/spi/LogLevelUpdateOccurs diff --git a/geode-core/src/main/java/org/apache/geode/internal/AbstractConfig.java b/geode-core/src/main/java/org/apache/geode/internal/AbstractConfig.java index 4daeb4e188f9..03c4d6d59834 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/AbstractConfig.java +++ b/geode-core/src/main/java/org/apache/geode/internal/AbstractConfig.java @@ -384,7 +384,7 @@ private void printSourceSection(ConfigSource source, PrintWriter printWriter) { attributeValueToPrint = getAttribute(name); } else if (sourceIsSecured) { // Never show secure sources - attributeValueToPrint = ArgumentRedactor.redacted; + attributeValueToPrint = ArgumentRedactor.getRedacted(); } else { // Otherwise, redact based on the key string attributeValueToPrint = diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/ArgumentRedactor.java b/geode-core/src/main/java/org/apache/geode/internal/util/ArgumentRedactor.java index 172d449a52ff..9df1a4e6a6e1 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/util/ArgumentRedactor.java +++ b/geode-core/src/main/java/org/apache/geode/internal/util/ArgumentRedactor.java @@ -12,209 +12,89 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package org.apache.geode.internal.util; -import java.util.Collections; +import java.util.Collection; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import org.apache.geode.annotations.Immutable; -import org.apache.geode.distributed.ConfigurationProperties; -import org.apache.geode.distributed.internal.DistributionConfig; +import org.apache.geode.internal.util.redaction.StringRedaction; public class ArgumentRedactor { - public static final String redacted = "********"; @Immutable - private static final List tabooToContain = - Collections.unmodifiableList(ArrayUtils.asList("password")); - @Immutable - private static final List tabooForOptionToStartWith = - Collections.unmodifiableList(ArrayUtils.asList(DistributionConfig.SYS_PROP_NAME, - DistributionConfig.SSL_SYSTEM_PROPS_NAME, - ConfigurationProperties.SECURITY_PREFIX)); - - private static final Pattern optionWithArgumentPattern = getOptionWithArgumentPattern(); + private static final StringRedaction DELEGATE = new StringRedaction(); + private ArgumentRedactor() { + // do not instantiate + } /** - * This method returns the {@link java.util.regex.Pattern} given below, used to capture - * command-line options that accept an argument. For clarity, the regex is given here without - * the escape characters required by Java's string handling. - *

- * - * {@code ((?:^| )(?:--J=)?--?)([^\s=]+)(?=[ =])( *[ =] *)(?! *-)((?:"[^"]*"|\S+))} + * Parse a string to find key/value pairs and redact the values if identified as sensitive. * *

- * This pattern consists of one captured boundary, - * three additional capture groups, and two look-ahead boundaries. + * The following format is expected:
+ * - Each key/value pair should be separated by spaces.
+ * - The key must be preceded by '--', '-D', or '--J=-D'.
+ * - The value may optionally be wrapped in quotation marks.
+ * - The value is assigned to a key with '=', '=' padded with any number of optional spaces, or + * any number of spaces without '='.
+ * - The value must not contain spaces without being wrapped in quotation marks.
+ * - The value may contain spaces or any other symbols when wrapped in quotation marks. * *

- * The four capture groups are: - *

    - *
  • [1] The beginning boundary, including at most one leading space, - * possibly including "--J=", and including the option's leading "-" or "--"
  • - *
  • [2] The option, which cannot include spaces
  • - *
  • [3] The option / argument separator, consisting of at least one character - * made of spaces and/or at most one "="
  • - *
  • [4] The argument, which terminates at the next space unless it is encapsulated by - * quotation-marks, in which case it terminates at the next quotation mark.
  • - *
+ * Examples: + *
    + *
  1. "--password=secret" + *
  2. "--user me --password secret" + *
  3. "-Dflag -Dopt=arg" + *
  4. "--classpath=." + *
  5. "password=secret" + *
* - * Look-ahead groups avoid falsely identifying two flag options (e.g. `{@code --help --all}`) from - * interpreting the second flag as the argument to the first option - * (here, misinterpreting as `{@code --help="--all"}`). - *

+ * @param string The string input to be parsed * - * Note that at time of writing, the argument (capture group 4) is not consumed by this class's - * logic, but its capture has proven repeatedly useful during iteration and testing. + * @return A string that has sensitive data redacted. */ - private static Pattern getOptionWithArgumentPattern() { - String capture_beginningBoundary; - { - String spaceOrBeginningAnchor = "(?:^| )"; - String maybeLeadingWithDashDashJEquals = "(?:--J=)?"; - String oneOrTwoDashes = "--?"; - capture_beginningBoundary = - "(" + spaceOrBeginningAnchor + maybeLeadingWithDashDashJEquals + oneOrTwoDashes + ")"; - } - - String capture_optionNameHasNoSpaces = "([^\\s=]+)"; - - String boundary_lookAheadForSpaceOrEquals = "(?=[ =])"; - - String capture_optionArgumentSeparator = "( *[ =] *)"; - - String boundary_negativeLookAheadToPreventNextOptionAsThisArgument = "(?! *-)"; - - String capture_Argument; - { - String argumentCanBeAnythingBetweenQuotes = "\"[^\"]*\""; - String argumentCanHaveNoSpacesWithoutQuotes = "\\S+"; - String argumentCanBeEitherOfTheAbove = "(?:" + argumentCanBeAnythingBetweenQuotes + "|" - + argumentCanHaveNoSpacesWithoutQuotes + ")"; - capture_Argument = "(" + argumentCanBeEitherOfTheAbove + ")"; - } - - String fullPattern = capture_beginningBoundary + capture_optionNameHasNoSpaces - + boundary_lookAheadForSpaceOrEquals + capture_optionArgumentSeparator - + boundary_negativeLookAheadToPreventNextOptionAsThisArgument + capture_Argument; - return Pattern.compile(fullPattern); + public static String redact(String string) { + return DELEGATE.redact(string); } - private ArgumentRedactor() {} + public static String redact(Iterable strings) { + return DELEGATE.redact(strings); + } /** - * Parse a string to find option/argument pairs and redact the arguments if necessary.
- * - * The following format is expected:
- * - Each option/argument pair should be separated by spaces.
- * - The option of each pair must be preceded by at least one hyphen '-'.
- * - Arguments may or may not be wrapped in quotation marks.
- * - Options and arguments may be separated by an equals sign '=' or any number of spaces.
- *
- * Examples:
- * "--password=secret"
- * "--user me --password secret"
- * "-Dflag -Dopt=arg"
- * "--classpath=."
- * - * See {@link #getOptionWithArgumentPattern()} for more information on - * the regular expression used. + * Return the redacted value string if the provided key is identified as sensitive, otherwise + * return the original value. * - * @param line The argument input to be parsed - * @param permitFirstPairWithoutHyphen When true, prepends the line with a "-", which is later - * removed. This allows the use on, e.g., "password=secret" rather than "--password=secret" + * @param key A string such as a system property, java option, or command-line key. + * @param value The string value for the key. * - * @return A redacted string that has sensitive information obscured. - */ - public static String redact(String line, boolean permitFirstPairWithoutHyphen) { - - boolean wasPaddedWithHyphen = false; - if (!line.trim().startsWith("-") && permitFirstPairWithoutHyphen) { - line = "-" + line.trim(); - wasPaddedWithHyphen = true; - } - - Matcher matcher = optionWithArgumentPattern.matcher(line); - while (matcher.find()) { - String option = matcher.group(2); - if (!isTaboo(option)) { - continue; - } - - String leadingBoundary = matcher.group(1); - String separator = matcher.group(3); - String withRedaction = leadingBoundary + option + separator + redacted; - line = line.replace(matcher.group(), withRedaction); - } - - if (wasPaddedWithHyphen) { - line = line.substring(1); - } - return line; - } - - /** - * Alias for {@code redact(line, true)}. See - * {@link org.apache.geode.internal.util.ArgumentRedactor#redact(java.lang.String, boolean)} + * @return The redacted string if the key is identified as sensitive, otherwise the original + * value. */ - public static String redact(String line) { - return redact(line, true); - } - - public static String redact(final List args) { - return redact(String.join(" ", args)); + public static String redactArgumentIfNecessary(String key, String value) { + return DELEGATE.redactArgumentIfNecessary(key, value); } - /** - * Return the redaction string if the provided option's argument should be redacted. - * Otherwise, return the provided argument unchanged. - * - * @param option A string such as a system property, jvm parameter or command-line option. - * @param argument A string that is the argument assigned to the option. - * - * @return A redacted string if the option indicates it should be redacted, otherwise the - * provided argument. - */ - public static String redactArgumentIfNecessary(String option, String argument) { - if (isTaboo(option)) { - return redacted; - } - return argument; + public static List redactEachInList(Collection strings) { + return DELEGATE.redactEachInList(strings); } /** - * Determine whether a option's argument should be redacted. + * Returns true if a string identifies sensitive data. For example, a string containing + * the word "password" identifies data that is sensitive and should be secured. * - * @param option The option in question. + * @param key The string to be evaluated. * - * @return true if the value should be redacted, otherwise false. + * @return true if the string identifies sensitive data. */ - static boolean isTaboo(String option) { - if (option == null) { - return false; - } - for (String taboo : tabooForOptionToStartWith) { - // If a parameter is passed with -Dsecurity-option=argument, the option option is - // "Dsecurity-option". - // With respect to taboo words, also check for the addition of the extra D - if (option.toLowerCase().startsWith(taboo) || option.toLowerCase().startsWith("d" + taboo)) { - return true; - } - } - for (String taboo : tabooToContain) { - if (option.toLowerCase().contains(taboo)) { - return true; - } - } - return false; + public static boolean isSensitive(String key) { + return DELEGATE.isSensitive(key); } - public static List redactEachInList(List argList) { - return argList.stream().map(ArgumentRedactor::redact).collect(Collectors.toList()); + public static String getRedacted() { + return DELEGATE.getRedacted(); } } diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/CombinedSensitiveDictionary.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/CombinedSensitiveDictionary.java new file mode 100644 index 000000000000..b63efce2b28e --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/CombinedSensitiveDictionary.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static java.util.Arrays.stream; + +import java.util.stream.Collectors; + +/** + * Delegates to multiple instances of SensitiveDataDictionary. + */ +class CombinedSensitiveDictionary implements SensitiveDataDictionary { + + private final Iterable dictionaries; + + CombinedSensitiveDictionary(SensitiveDataDictionary... dictionaries) { + this.dictionaries = stream(dictionaries).collect(Collectors.toSet()); + } + + @Override + public boolean isSensitive(String string) { + for (SensitiveDataDictionary dictionary : dictionaries) { + if (dictionary.isSensitive(string)) { + return true; + } + } + return false; + } +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/ParserRegex.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/ParserRegex.java new file mode 100644 index 000000000000..0bfe6d82e6ff --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/ParserRegex.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.apache.geode.internal.util.redaction.ParserRegex.Group.ASSIGN; +import static org.apache.geode.internal.util.redaction.ParserRegex.Group.KEY; +import static org.apache.geode.internal.util.redaction.ParserRegex.Group.PREFIX; +import static org.apache.geode.internal.util.redaction.ParserRegex.Group.VALUE; + +import java.util.regex.Pattern; + +/** + * Regex with named capture groups that can be used to match strings containing a key with value or + * a flag. + * + *

+ * The raw regex string is: + * + * {@code (?--J=-D|-D|--)(?[^\s=]+)(?:(?! (?:--J=-D|-D|--))(? *[ =] *)(?! (?:--J=-D|-D|--))(?"[^"]*"|\S+))?} + */ +class ParserRegex { + + private static final String REGEX = + String.valueOf(PREFIX) + + KEY + + "(?:(?! (?:--J=-D|-D|--))" + + ASSIGN + "(?! (?:--J=-D|-D|--))" + + VALUE + + ")?"; + + private static final Pattern PATTERN = Pattern.compile(REGEX); + + static String getRegex() { + return REGEX; + } + + static Pattern getPattern() { + return PATTERN; + } + + enum Group { + /** Prefix precedes each key and may be --, -D, or --J=-D */ + PREFIX(1, "prefix", "(?--J=-D|-D|--)"), + + /** Key has a value or represents a flag when no value is assigned */ + KEY(2, "key", "(?[^\\s=]+)"), + + /** Assign is an operator for assigning a value to a key and may be = or space */ + ASSIGN(3, "assign", "(? *[ =] *)"), + + /** Value is assigned to a key following an assign operator */ + VALUE(4, "value", "(?\"[^\"]*\"|\\S+)"); + + private final int index; + private final String name; + private final String regex; + + Group(final int index, final String name, final String regex) { + this.index = index; + this.name = name; + this.regex = regex; + } + + int getIndex() { + return index; + } + + String getName() { + return name; + } + + String getRegex() { + return regex; + } + + @Override + public String toString() { + return regex; + } + } +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RedactionDefaults.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RedactionDefaults.java new file mode 100644 index 000000000000..ced2b84eaef7 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RedactionDefaults.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static java.util.Collections.unmodifiableList; +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_PREFIX; +import static org.apache.geode.distributed.internal.DistributionConfig.SSL_SYSTEM_PROPS_NAME; +import static org.apache.geode.distributed.internal.DistributionConfig.SYS_PROP_NAME; +import static org.apache.geode.internal.util.ArrayUtils.asList; + +import java.util.List; + +import org.apache.geode.annotations.Immutable; + +/** + * Default strings that indicate sensitive data requiring redaction. + */ +class RedactionDefaults { + + static final String REDACTED = "********"; + + private static final String JAVA_OPTION_D = "-D"; + private static final String GFSH_OPTION_JD = "--J=-D"; + + /** + * Strings containing these substrings are flagged as sensitive. + */ + @Immutable + static final List SENSITIVE_SUBSTRINGS = + unmodifiableList(asList("password")); + + /** + * Strings starting with these prefixes are flagged as sensitive. + */ + @Immutable + static final List SENSITIVE_PREFIXES = + unmodifiableList(asList(SYS_PROP_NAME, + SSL_SYSTEM_PROPS_NAME, + SECURITY_PREFIX, + JAVA_OPTION_D + SYS_PROP_NAME, + JAVA_OPTION_D + SSL_SYSTEM_PROPS_NAME, + JAVA_OPTION_D + SECURITY_PREFIX, + GFSH_OPTION_JD + SYS_PROP_NAME, + GFSH_OPTION_JD + SSL_SYSTEM_PROPS_NAME, + GFSH_OPTION_JD + SECURITY_PREFIX)); +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RedactionStrategy.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RedactionStrategy.java new file mode 100644 index 000000000000..c30582204224 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RedactionStrategy.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +/** + * Defines the algorithm for scanning input strings for redaction of sensitive strings. + */ +@FunctionalInterface +interface RedactionStrategy { + + /** + * Parse a string to find key/value pairs and redact the values if identified as sensitive. + * + *

+ * The following format is expected:
+ * - Each key/value pair should be separated by spaces.
+ * - The key must be preceded by '--', '-D', or '--J=-D'.
+ * - The value may optionally be wrapped in quotation marks.
+ * - The value is assigned to a key with '=', '=' padded with any number of optional spaces, or + * any number of spaces without '='.
+ * - The value must not contain spaces without being wrapped in quotation marks.
+ * - The value may contain spaces or any other symbols when wrapped in quotation marks. + * + *

+ * Examples: + *

    + *
  1. "--password=secret" + *
  2. "--user me --password secret" + *
  3. "-Dflag -Dopt=arg" + *
  4. "--classpath=." + *
  5. "password=secret" + *
+ * + * @param string The string input to be parsed + * + * @return A string that has sensitive data redacted. + */ + String redact(String string); +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RegexRedactionStrategy.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RegexRedactionStrategy.java new file mode 100644 index 000000000000..1f758eb15c7e --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/RegexRedactionStrategy.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import java.util.function.Function; +import java.util.regex.Matcher; + +/** + * Simple redaction strategy using regex to parse options. + */ +class RegexRedactionStrategy implements RedactionStrategy { + + private final Function isSensitive; + private final String redacted; + + RegexRedactionStrategy(Function isSensitive, String redacted) { + this.isSensitive = isSensitive; + this.redacted = redacted; + } + + @Override + public String redact(String string) { + Matcher matcher = ParserRegex.getPattern().matcher(string); + while (matcher.find()) { + String option = matcher.group(2); + if (!isSensitive.apply(option)) { + continue; + } + + String leadingBoundary = matcher.group(1); + String separator = matcher.group(3); + String withRedaction = leadingBoundary + option + separator + redacted; + string = string.replace(matcher.group(), withRedaction); + } + + return string; + } +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitiveDataDictionary.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitiveDataDictionary.java new file mode 100644 index 000000000000..deb73e4993d0 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitiveDataDictionary.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +/** + * Evaluates strings to determine if they identify sensitive data. + */ +@FunctionalInterface +interface SensitiveDataDictionary { + + /** + * Returns true if a string identifies sensitive data. For example, a string containing + * the word "password" identifies data that is sensitive and should be secured. + * + * @param string The string to be evaluated. + * + * @return true if the string identifies sensitive data. + */ + boolean isSensitive(String string); +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitivePrefixDictionary.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitivePrefixDictionary.java new file mode 100644 index 000000000000..9d0867014672 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitivePrefixDictionary.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +class SensitivePrefixDictionary implements SensitiveDataDictionary { + + private final Iterable sensitivePrefixes; + + SensitivePrefixDictionary(Iterable sensitivePrefixes) { + this.sensitivePrefixes = sensitivePrefixes; + } + + @Override + public boolean isSensitive(String string) { + if (string == null) { + return false; + } + for (String prefix : sensitivePrefixes) { + if (string.toLowerCase().startsWith(prefix.toLowerCase())) { + return true; + } + } + return false; + } +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitiveSubstringDictionary.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitiveSubstringDictionary.java new file mode 100644 index 000000000000..5cd4342c17dd --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/SensitiveSubstringDictionary.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +class SensitiveSubstringDictionary implements SensitiveDataDictionary { + + private final Iterable sensitiveSubstrings; + + SensitiveSubstringDictionary(Iterable sensitiveSubstrings) { + this.sensitiveSubstrings = sensitiveSubstrings; + } + + @Override + public boolean isSensitive(String string) { + if (string == null) { + return false; + } + for (String substring : sensitiveSubstrings) { + if (string.toLowerCase().contains(substring)) { + return true; + } + } + return false; + } +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/util/redaction/StringRedaction.java b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/StringRedaction.java new file mode 100644 index 000000000000..ea514fc8628b --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/util/redaction/StringRedaction.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static java.util.stream.Collectors.toList; +import static org.apache.geode.internal.util.redaction.RedactionDefaults.REDACTED; +import static org.apache.geode.internal.util.redaction.RedactionDefaults.SENSITIVE_PREFIXES; +import static org.apache.geode.internal.util.redaction.RedactionDefaults.SENSITIVE_SUBSTRINGS; + +import java.util.Collection; +import java.util.List; + +import org.apache.geode.annotations.VisibleForTesting; + +/** + * Redacts value strings for keys that are identified as sensitive data. + */ +public class StringRedaction implements SensitiveDataDictionary { + + private final String redacted; + private final SensitiveDataDictionary sensitiveDataDictionary; + private final RedactionStrategy redactionStrategy; + + public StringRedaction() { + this(REDACTED, + new CombinedSensitiveDictionary( + new SensitivePrefixDictionary(SENSITIVE_PREFIXES), + new SensitiveSubstringDictionary(SENSITIVE_SUBSTRINGS))); + } + + private StringRedaction(String redacted, SensitiveDataDictionary sensitiveDataDictionary) { + this(redacted, + sensitiveDataDictionary, + new RegexRedactionStrategy(sensitiveDataDictionary::isSensitive, redacted)); + } + + @VisibleForTesting + StringRedaction(String redacted, SensitiveDataDictionary sensitiveDataDictionary, + RedactionStrategy redactionStrategy) { + this.redacted = redacted; + this.sensitiveDataDictionary = sensitiveDataDictionary; + this.redactionStrategy = redactionStrategy; + } + + /** + * Parse a string to find key/value pairs and redact the values if identified as sensitive. + * + *

+ * The following format is expected:
+ * - Each key/value pair should be separated by spaces.
+ * - The key must be preceded by '--', '-D', or '--J=-D'.
+ * - The value may optionally be wrapped in quotation marks.
+ * - The value is assigned to a key with '=', '=' padded with any number of optional spaces, or + * any number of spaces without '='.
+ * - The value must not contain spaces without being wrapped in quotation marks.
+ * - The value may contain spaces or any other symbols when wrapped in quotation marks. + * + *

+ * Examples: + *

    + *
  1. "--password=secret" + *
  2. "--user me --password secret" + *
  3. "-Dflag -Dopt=arg" + *
  4. "--classpath=." + *
  5. "password=secret" + *
+ * + * @param string The string input to be parsed + * + * @return A string that has sensitive data redacted. + */ + public String redact(String string) { + return redactionStrategy.redact(string); + } + + public String redact(Iterable strings) { + return redact(String.join(" ", strings)); + } + + /** + * Return the redacted value string if the provided key is identified as sensitive, otherwise + * return the original value. + * + * @param key A string such as a system property, java option, or command-line key. + * @param value The string value for the key. + * + * @return The redacted string if the key is identified as sensitive, otherwise the original + * value. + */ + public String redactArgumentIfNecessary(String key, String value) { + if (isSensitive(key)) { + return redacted; + } + return value; + } + + public List redactEachInList(Collection strings) { + return strings.stream() + .map(this::redact) + .collect(toList()); + } + + @Override + public boolean isSensitive(String key) { + return sensitiveDataDictionary.isSensitive(key); + } + + public String getRedacted() { + return redacted; + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/ArgumentRedactorJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/ArgumentRedactorJUnitTest.java deleted file mode 100644 index 129627cea3ef..000000000000 --- a/geode-core/src/test/java/org/apache/geode/internal/util/ArgumentRedactorJUnitTest.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You 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. - */ - -package org.apache.geode.internal.util; - -import static org.apache.geode.distributed.ConfigurationProperties.CLUSTER_SSL_ENABLED; -import static org.apache.geode.distributed.ConfigurationProperties.CLUSTER_SSL_TRUSTSTORE_PASSWORD; -import static org.apache.geode.distributed.ConfigurationProperties.CONSERVE_SOCKETS; -import static org.apache.geode.distributed.ConfigurationProperties.GATEWAY_SSL_TRUSTSTORE_PASSWORD; -import static org.apache.geode.distributed.ConfigurationProperties.SERVER_SSL_KEYSTORE_PASSWORD; -import static org.apache.geode.internal.util.ArgumentRedactor.isTaboo; -import static org.apache.geode.internal.util.ArgumentRedactor.redact; -import static org.apache.geode.internal.util.ArgumentRedactor.redactEachInList; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.Test; - -import org.apache.geode.internal.logging.Banner; - -public class ArgumentRedactorJUnitTest { - private static final String someProperty = "redactorTest.someProperty"; - private static final String somePasswordProperty = "redactorTest.aPassword"; - private static final String someOtherPasswordProperty = - "redactorTest.aPassword-withCharactersAfterward"; - - @Test - public void theseLinesShouldRedact() { - String argumentThatShouldBeRedacted = "__this_should_be_redacted__"; - List someTabooOptions = - Arrays.asList("-Dgemfire.password=" + argumentThatShouldBeRedacted, - "--password=" + argumentThatShouldBeRedacted, - "--J=-Dgemfire.some.very.qualified.item.password=" + argumentThatShouldBeRedacted, - "--J=-Dsysprop-secret.information=" + argumentThatShouldBeRedacted); - - List fullyRedacted = redactEachInList(someTabooOptions); - assertThat(fullyRedacted).doesNotContainAnyElementsOf(someTabooOptions); - } - - @Test - public void redactorWillIdentifySampleTabooProperties() { - List shouldBeRedacted = Arrays.asList("gemfire.security-password", "password", - "other-password-option", CLUSTER_SSL_TRUSTSTORE_PASSWORD, GATEWAY_SSL_TRUSTSTORE_PASSWORD, - SERVER_SSL_KEYSTORE_PASSWORD, "security-username", "security-manager", - "security-important-property", "javax.net.ssl.keyStorePassword", - "javax.net.ssl.some.security.item", "javax.net.ssl.keyStoreType", "sysprop-secret-prop"); - for (String option : shouldBeRedacted) { - assertThat(isTaboo(option)) - .describedAs("This option should be identified as taboo: " + option).isTrue(); - } - } - - @Test - public void redactorWillAllowSampleMiscProperties() { - List shouldNotBeRedacted = Arrays.asList("gemfire.security-manager", - CLUSTER_SSL_ENABLED, CONSERVE_SOCKETS, "username", "just-an-option"); - for (String option : shouldNotBeRedacted) { - assertThat(isTaboo(option)) - .describedAs("This option should not be identified as taboo: " + option).isFalse(); - } - } - - @Test - public void argListOfPasswordsAllRedact() { - List argList = new ArrayList<>(); - argList.add("--gemfire.security-password=secret"); - argList.add("--login-password=secret"); - argList.add("--gemfire-password = super-secret"); - argList.add("--geode-password= confidential"); - argList.add("--some-other-password =shhhh"); - argList.add("--justapassword =failed"); - String redacted = redact(argList); - assertThat(redacted).contains("--gemfire.security-password=********"); - assertThat(redacted).contains("--login-password=********"); - assertThat(redacted).contains("--gemfire-password = ********"); - assertThat(redacted).contains("--geode-password= ********"); - assertThat(redacted).contains("--some-other-password =********"); - assertThat(redacted).contains("--justapassword =********"); - } - - @Test - public void argListOfPasswordsAllRedactViaRedactEachInList() { - List argList = new ArrayList<>(); - argList.add("--gemfire.security-password=secret"); - argList.add("--login-password=secret"); - argList.add("--gemfire-password = super-secret"); - argList.add("--geode-password= confidential"); - argList.add("--some-other-password =shhhh"); - argList.add("--justapassword =failed"); - List redacted = redactEachInList(argList); - assertThat(redacted).contains("--gemfire.security-password=********"); - assertThat(redacted).contains("--login-password=********"); - assertThat(redacted).contains("--gemfire-password = ********"); - assertThat(redacted).contains("--geode-password= ********"); - assertThat(redacted).contains("--some-other-password =********"); - assertThat(redacted).contains("--justapassword =********"); - } - - - @Test - public void argListOfMiscOptionsDoNotRedact() { - List argList = new ArrayList<>(); - argList.add("--gemfire.security-properties=./security.properties"); - argList.add("--gemfire.sys.security-option=someArg"); - argList.add("--gemfire.use-cluster-configuration=true"); - argList.add("--someotherstringoption"); - argList.add("--login-name=admin"); - argList.add("--myArg --myArg2 --myArg3=-arg4"); - argList.add("--myArg --myArg2 --myArg3=\"-arg4\""); - String redacted = redact(argList); - assertThat(redacted).contains("--gemfire.security-properties=./security.properties"); - assertThat(redacted).contains("--gemfire.sys.security-option=someArg"); - assertThat(redacted).contains("--gemfire.use-cluster-configuration=true"); - assertThat(redacted).contains("--someotherstringoption"); - assertThat(redacted).contains("--login-name=admin"); - assertThat(redacted).contains("--myArg --myArg2 --myArg3=-arg4"); - assertThat(redacted).contains("--myArg --myArg2 --myArg3=\"-arg4\""); - } - - @Test - public void protectedIndividualOptionsRedact() { - String arg; - - arg = "-Dgemfire.security-password=secret"; - assertThat(redact(arg)).endsWith("password=********"); - - arg = "--J=-Dsome.highly.qualified.password=secret"; - assertThat(redact(arg)).endsWith("password=********"); - - arg = "--password=foo"; - assertThat(redact(arg)).isEqualToIgnoringWhitespace("--password=********"); - - arg = "-Dgemfire.security-properties=\"c:\\Program Files (x86)\\My Folder\""; - assertThat(redact(arg)).isEqualTo(arg); - } - - @Test - public void miscIndividualOptionsDoNotRedact() { - String arg; - - arg = "-Dgemfire.security-properties=./security-properties"; - assertThat(redact(arg)).isEqualTo(arg); - - arg = "-J-Dgemfire.sys.security-option=someArg"; - assertThat(redact(arg)).isEqualTo(arg); - - arg = "-Dgemfire.sys.option=printable"; - assertThat(redact(arg)).isEqualTo(arg); - - arg = "-Dgemfire.use-cluster-configuration=true"; - assertThat(redact(arg)).isEqualTo(arg); - - arg = "someotherstringoption"; - assertThat(redact(arg)).isEqualTo(arg); - - arg = "--classpath=."; - assertThat(redact(arg)).isEqualTo(arg); - } - - @Test - public void wholeLinesAreProperlyRedacted() { - String arg; - arg = "-DmyArg -Duser-password=foo --classpath=."; - assertThat(redact(arg)).isEqualTo("-DmyArg -Duser-password=******** --classpath=."); - - arg = "-DmyArg -Duser-password=foo -DOtherArg -Dsystem-password=bar"; - assertThat(redact(arg)) - .isEqualTo("-DmyArg -Duser-password=******** -DOtherArg -Dsystem-password=********"); - - arg = - "-Dlogin-password=secret -Dlogin-name=admin -Dgemfire-password = super-secret --geode-password= confidential -J-Dsome-other-password =shhhh"; - String redacted = redact(arg); - assertThat(redacted).contains("login-password=********"); - assertThat(redacted).contains("login-name=admin"); - assertThat(redacted).contains("gemfire-password = ********"); - assertThat(redacted).contains("geode-password= ********"); - assertThat(redacted).contains("some-other-password =********"); - } - - @Test - public void redactScriptLine() { - assertThat(redact("connect --password=test --user=test")) - .isEqualTo("connect --password=******** --user=test"); - - assertThat(redact("connect --test-password=test --product-password=test1")) - .isEqualTo("connect --test-password=******** --product-password=********"); - } - - @Test - public void systemPropertiesGetRedactedInBanner() { - try { - System.setProperty(someProperty, "isNotRedacted"); - System.setProperty(somePasswordProperty, "isRedacted"); - System.setProperty(someOtherPasswordProperty, "isRedacted"); - - List args = ArrayUtils.asList("--user=me", "--password=isRedacted", - "--another-password-for-some-reason =isRedacted", "--yet-another-password = isRedacted"); - String banner = new Banner().getString(args.toArray(new String[0])); - assertThat(banner).doesNotContain("isRedacted"); - } finally { - System.clearProperty(someProperty); - System.clearProperty(somePasswordProperty); - System.clearProperty(someOtherPasswordProperty); - } - } -} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/ArgumentRedactorTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/ArgumentRedactorTest.java new file mode 100644 index 000000000000..6a60c95048db --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/ArgumentRedactorTest.java @@ -0,0 +1,675 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util; + +import static org.apache.geode.internal.util.ArgumentRedactor.getRedacted; +import static org.apache.geode.internal.util.ArgumentRedactor.isSensitive; +import static org.apache.geode.internal.util.ArgumentRedactor.redact; +import static org.apache.geode.internal.util.ArgumentRedactor.redactEachInList; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Test; + +public class ArgumentRedactorTest { + + @Test + public void isSensitive_isTrueForGemfireSecurityPassword() { + String input = "gemfire.security-password"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForPassword() { + String input = "password"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForOptionContainingPassword() { + String input = "other-password-option"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForClusterSslTruststorePassword() { + String input = "cluster-ssl-truststore-password"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForGatewaySslTruststorePassword() { + String input = "gateway-ssl-truststore-password"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForServerSslKeystorePassword() { + String input = "server-ssl-keystore-password"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForSecurityUsername() { + String input = "security-username"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForSecurityManager() { + String input = "security-manager"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForOptionStartingWithSecurityHyphen() { + String input = "security-important-property"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForJavaxNetSslKeyStorePassword() { + String input = "javax.net.ssl.keyStorePassword"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForOptionStartingWithJavaxNetSsl() { + String input = "javax.net.ssl.some.security.item"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForJavaxNetSslKeyStoreType() { + String input = "javax.net.ssl.keyStoreType"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isTrueForOptionStartingWithSyspropHyphen() { + String input = "sysprop-secret-prop"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isTrue(); + } + + @Test + public void isSensitive_isFalseForGemfireSecurityManager() { + String input = "gemfire.security-manager"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isFalse(); + } + + @Test + public void isSensitive_isFalseForClusterSslEnabled() { + String input = "cluster-ssl-enabled"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isFalse(); + } + + @Test + public void isSensitive_isFalseForConserveSockets() { + String input = "conserve-sockets"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isFalse(); + } + + @Test + public void isSensitive_isFalseForUsername() { + String input = "username"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isFalse(); + } + + @Test + public void isSensitive_isFalseForNonMatchingStringContainingHyphens() { + String input = "just-an-option"; + + boolean output = isSensitive(input); + + assertThat(output) + .as("output of isSensitive(" + input + ")") + .isFalse(); + } + + @Test + public void redactString_redactsGemfirePasswordWithHyphenD() { + String string = "-Dgemfire.password=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsPasswordWithHyphens() { + String string = "--password=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsOptionEndingWithPasswordWithHyphensJDd() { + String string = "--J=-Dgemfire.some.very.qualified.item.password=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsOptionStartingWithSyspropHyphenWithHyphensJD() { + String string = "--J=-Dsysprop-secret.information=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsGemfireSecurityPasswordWithHyphenD() { + String string = "-Dgemfire.security-password=%s"; + String sensitive = "secret"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(expected); + } + + @Test + public void redactString_doesNotRedactOptionEndingWithSecurityPropertiesWithHyphenD1() { + String input = "-Dgemfire.security-properties=argument-value"; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_doesNotRedactOptionEndingWithSecurityPropertiesWithHyphenD2() { + String input = "-Dgemfire.security-properties=\"c:\\Program Files (x86)\\My Folder\""; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_doesNotRedactOptionEndingWithSecurityPropertiesWithHyphenD3() { + String input = "-Dgemfire.security-properties=./security-properties"; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_doesNotRedactOptionContainingSecurityHyphenWithHyphensJD() { + String input = "--J=-Dgemfire.sys.security-option=someArg"; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_doesNotRedactNonMatchingGemfireOptionWithHyphenD() { + String input = "-Dgemfire.sys.option=printable"; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_redactsGemfireUseClusterConfigurationWithHyphenD() { + String input = "-Dgemfire.use-cluster-configuration=true"; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_returnsNonMatchingString() { + String input = "someotherstringoption"; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_doesNotRedactClasspathWithHyphens() { + String input = "--classpath=."; + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactString_redactsMatchingOptionWithNonMatchingOptionAndFlagAndMultiplePrefixes() { + String string = "--J=-Dflag -Duser-password=%s --classpath=."; + String sensitive = "foo"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsMultipleMatchingOptionsWithFlags() { + String string = "-DmyArg -Duser-password=%s -DOtherArg -Dsystem-password=%s"; + String sensitive1 = "foo"; + String sensitive2 = "bar"; + String input = String.format(string, sensitive1, sensitive2); + String expected = String.format(string, getRedacted(), getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive1) + .doesNotContain(sensitive2) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsMultipleMatchingOptionsWithMultipleNonMatchingOptionsAndMultiplePrefixes() { + String string = + "-Dlogin-password=%s -Dlogin-name=%s -Dgemfire-password = %s --geode-password= %s --J=-Dsome-other-password =%s"; + String sensitive1 = "secret"; + String nonSensitive = "admin"; + String sensitive2 = "super-secret"; + String sensitive3 = "confidential"; + String sensitive4 = "shhhh"; + String input = String.format( + string, sensitive1, nonSensitive, sensitive2, sensitive3, sensitive4); + String expected = String.format( + string, getRedacted(), nonSensitive, getRedacted(), getRedacted(), getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive1) + .contains(nonSensitive) + .doesNotContain(sensitive2) + .doesNotContain(sensitive3) + .doesNotContain(sensitive4) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsMatchingOptionWithNonMatchingOptionAfterCommand() { + String string = "connect --password=%s --user=%s"; + String reusedSensitive = "test"; + String input = String.format(string, reusedSensitive, reusedSensitive); + String expected = String.format(string, getRedacted(), reusedSensitive); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .contains(reusedSensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsMultipleMatchingOptionsButNotKeyUsingSameStringAsValue() { + String string = "connect --%s-password=%s --product-password=%s"; + String reusedSensitive = "test"; + String sensitive = "test1"; + String input = String.format(string, reusedSensitive, reusedSensitive, sensitive); + String expected = String.format(string, reusedSensitive, getRedacted(), getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .contains(reusedSensitive) + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactRedactsGemfireSslTruststorePassword() { + String string = "-Dgemfire.ssl-truststore-password=%s"; + String sensitive = "gibberish"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsGemfireSslKeystorePassword() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "gibberish"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsValueEndingWithHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "supersecret-"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsValueContainingHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "super-secret"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsValueContainingManyHyphens() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "this-is-super-secret"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsValueStartingWithHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "-supersecret"; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactString_redactsQuotedValueStartingWithHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "\"-supersecret\""; + String input = String.format(string, sensitive); + String expected = String.format(string, getRedacted()); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactIterable_redactsMultipleMatchingOptions() { + String sensitive1 = "secret"; + String sensitive2 = "super-secret"; + String sensitive3 = "confidential"; + String sensitive4 = "shhhh"; + String sensitive5 = "failed"; + + Collection input = new ArrayList<>(); + input.add("--gemfire.security-password=" + sensitive1); + input.add("--login-password=" + sensitive1); + input.add("--gemfire-password = " + sensitive2); + input.add("--geode-password= " + sensitive3); + input.add("--some-other-password =" + sensitive4); + input.add("--justapassword =" + sensitive5); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive1) + .doesNotContain(sensitive2) + .doesNotContain(sensitive3) + .doesNotContain(sensitive4) + .doesNotContain(sensitive5) + .contains("--gemfire.security-password=" + getRedacted()) + .contains("--login-password=" + getRedacted()) + .contains("--gemfire-password = " + getRedacted()) + .contains("--geode-password= " + getRedacted()) + .contains("--some-other-password =" + getRedacted()) + .contains("--justapassword =" + getRedacted()); + } + + @Test + public void redactIterable_doesNotRedactMultipleNonMatchingOptions() { + Collection input = new ArrayList<>(); + input.add("--gemfire.security-properties=./security.properties"); + input.add("--gemfire.sys.security-option=someArg"); + input.add("--gemfire.use-cluster-configuration=true"); + input.add("--someotherstringoption"); + input.add("--login-name=admin"); + input.add("--myArg --myArg2 --myArg3=-arg4"); + input.add("--myArg --myArg2 --myArg3=\"-arg4\""); + + String output = redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .contains("--gemfire.security-properties=./security.properties") + .contains("--gemfire.sys.security-option=someArg") + .contains("--gemfire.use-cluster-configuration=true") + .contains("--someotherstringoption") + .contains("--login-name=admin") + .contains("--myArg --myArg2 --myArg3=-arg4") + .contains("--myArg --myArg2 --myArg3=\"-arg4\""); + } + + @Test + public void redactEachInList_redactsCollectionOfMatchingOptions() { + String sensitive1 = "secret"; + String sensitive2 = "super-secret"; + String sensitive3 = "confidential"; + String sensitive4 = "shhhh"; + String sensitive5 = "failed"; + + Collection input = new ArrayList<>(); + input.add("--gemfire.security-password=" + sensitive1); + input.add("--login-password=" + sensitive1); + input.add("--gemfire-password = " + sensitive2); + input.add("--geode-password= " + sensitive3); + input.add("--some-other-password =" + sensitive4); + input.add("--justapassword =" + sensitive5); + + List output = redactEachInList(input); + + assertThat(output) + .as("output of redactEachInList(" + input + ")") + .doesNotContain(sensitive1) + .doesNotContain(sensitive2) + .doesNotContain(sensitive3) + .doesNotContain(sensitive4) + .doesNotContain(sensitive5) + .contains("--gemfire.security-password=" + getRedacted()) + .contains("--login-password=" + getRedacted()) + .contains("--gemfire-password = " + getRedacted()) + .contains("--geode-password= " + getRedacted()) + .contains("--some-other-password =" + getRedacted()) + .contains("--justapassword =" + getRedacted()); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/redaction/CombinedSensitiveDictionaryTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/CombinedSensitiveDictionaryTest.java new file mode 100644 index 000000000000..ab691da2e837 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/CombinedSensitiveDictionaryTest.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +public class CombinedSensitiveDictionaryTest { + + @Test + public void isFalseWhenZeroDelegates() { + CombinedSensitiveDictionary combined = new CombinedSensitiveDictionary(); + + boolean result = combined.isSensitive("string"); + + assertThat(result).isFalse(); + } + + @Test + public void delegatesInputToSingleDictionary() { + String input = "string"; + SensitiveDataDictionary dictionary = mock(SensitiveDataDictionary.class); + CombinedSensitiveDictionary combined = new CombinedSensitiveDictionary(dictionary); + + combined.isSensitive(input); + + verify(dictionary).isSensitive(same(input)); + } + + @Test + public void delegatesInputToTwoDictionaries() { + String input = "string"; + SensitiveDataDictionary dictionary1 = mock(SensitiveDataDictionary.class); + SensitiveDataDictionary dictionary2 = mock(SensitiveDataDictionary.class); + CombinedSensitiveDictionary combined = + new CombinedSensitiveDictionary(dictionary1, dictionary2); + + combined.isSensitive(input); + + verify(dictionary1).isSensitive(same(input)); + verify(dictionary2).isSensitive(same(input)); + } + + @Test + public void delegatesInputToManyDictionaries() { + String input = "string"; + SensitiveDataDictionary dictionary1 = mock(SensitiveDataDictionary.class); + SensitiveDataDictionary dictionary2 = mock(SensitiveDataDictionary.class); + SensitiveDataDictionary dictionary3 = mock(SensitiveDataDictionary.class); + SensitiveDataDictionary dictionary4 = mock(SensitiveDataDictionary.class); + CombinedSensitiveDictionary combined = + new CombinedSensitiveDictionary(dictionary1, dictionary2, dictionary3, dictionary4); + + combined.isSensitive(input); + + verify(dictionary1).isSensitive(same(input)); + verify(dictionary2).isSensitive(same(input)); + verify(dictionary3).isSensitive(same(input)); + verify(dictionary4).isSensitive(same(input)); + } + + @Test + public void isFalseWhenManyDictionariesAreFalse() { + String input = "string"; + SensitiveDataDictionary dictionary1 = createDictionary(false); + SensitiveDataDictionary dictionary2 = createDictionary(false); + SensitiveDataDictionary dictionary3 = createDictionary(false); + SensitiveDataDictionary dictionary4 = createDictionary(false); + CombinedSensitiveDictionary combined = + new CombinedSensitiveDictionary(dictionary1, dictionary2, dictionary3, dictionary4); + + boolean result = combined.isSensitive(input); + + assertThat(result).isFalse(); + } + + @Test + public void isTrueWhenManyDictionariesAreTrue() { + String input = "string"; + SensitiveDataDictionary dictionary1 = createDictionary(true); + SensitiveDataDictionary dictionary2 = createDictionary(true); + SensitiveDataDictionary dictionary3 = createDictionary(true); + SensitiveDataDictionary dictionary4 = createDictionary(true); + CombinedSensitiveDictionary combined = + new CombinedSensitiveDictionary(dictionary1, dictionary2, dictionary3, dictionary4); + + boolean result = combined.isSensitive(input); + + assertThat(result).isTrue(); + } + + @Test + public void isTrueWhenOneOfManyDictionariesIsTrue() { + String input = "string"; + SensitiveDataDictionary dictionary1 = createDictionary(false); + SensitiveDataDictionary dictionary2 = createDictionary(false); + SensitiveDataDictionary dictionary3 = createDictionary(false); + SensitiveDataDictionary dictionary4 = createDictionary(true); + CombinedSensitiveDictionary combined = + new CombinedSensitiveDictionary(dictionary1, dictionary2, dictionary3, dictionary4); + + boolean result = combined.isSensitive(input); + + assertThat(result).isTrue(); + } + + private SensitiveDataDictionary createDictionary(boolean isSensitive) { + SensitiveDataDictionary dictionary = mock(SensitiveDataDictionary.class); + when(dictionary.isSensitive(anyString())).thenReturn(isSensitive); + return dictionary; + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/redaction/ParserRegexTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/ParserRegexTest.java new file mode 100644 index 000000000000..bc14929a97bb --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/ParserRegexTest.java @@ -0,0 +1,1006 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.apache.geode.internal.util.redaction.ParserRegex.getPattern; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.Test; + +import org.apache.geode.internal.util.redaction.ParserRegex.Group; + +public class ParserRegexTest { + + @Test + public void capturesOption() { + String input = "--option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenPrefixIsHyphenD() { + String input = "-Doption=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenPrefixIsHyphensJD() { + String input = "--J=-Doption=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--J=-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenAssignIsSpace() { + String input = "--option argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenAssignIsSpaceEquals() { + String input = "--option =argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" ="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenAssignIsEqualsSpace() { + String input = "--option= argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("= "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenAssignIsSpaceEqualsSpace() { + String input = "--option = argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" = "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenKeyContainsHyphens() { + String input = "--this-is-the-option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("this-is-the-option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenValueContainsHyphens() { + String input = "--option=this-is-the-argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("this-is-the-argument"); + } + + @Test + public void capturesOptionWhenValueIsQuoted() { + String input = "--option=\"argument\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"argument\""); + } + + @Test + public void capturesOptionWhenValueIsQuotedAndAssignIsSpace() { + String input = "--option \"argument\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"argument\""); + } + + @Test + public void capturesOptionWhenAssignContainsTwoSpaces() { + String input = "--option argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenAssignContainsManySpaces() { + String input = "--option argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithHyphen() { + String input = "--option=-argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("-argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithHyphenAndAssignIsSpace() { + String input = "--option -argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("-argument"); + } + + @Test + public void capturesOptionWhenQuotedValueBeginsWithHyphen() { + String input = "--option=\"-argument\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"-argument\""); + } + + @Test + public void capturesOptionWhenValueBeginsWithTwoHyphens() { + String input = "--option=--argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("--argument"); + } + + @Test + public void capturesFlag() { + String input = "--flag"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesFlagWhenPrefixIsHyphenD() { + String input = "-Dflag"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesFlagWhenPrefixIsHyphensJD() { + String input = "--J=-Dflag"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--J=-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesTwoFlags() { + String input = "--option --argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("argument"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesTwoFlagsWhenPrefixIsHyphenD() { + String input = "-Doption -Dargument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("argument"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesTwoFlagsWhenPrefixIsHyphensJD() { + String input = "--J=-Doption --J=-Dargument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--J=-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--J=-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("argument"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesOptionWhenQuotedValueBeginsWithTwoHyphens() { + String input = "--option=\"--argument\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"--argument\""); + } + + @Test + public void capturesOptionWhenQuotedValueBeginsWithHyphenD() { + String input = "--option=\"-Dargument\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"-Dargument\""); + } + + @Test + public void capturesOptionWhenQuotedValueBeginsWithHyphensJD() { + String input = "--option=\"--J=-Dargument\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"--J=-Dargument\""); + } + + @Test + public void capturesOptionWhenValueBeginsWithManyHyphens() { + String input = "--option=---argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("---argument"); + } + + @Test + public void capturesTwoFlagsWhenValueBeginsWithManyHyphensAndAssignIsSpace() { + String input = "--option ---argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("-argument"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesOptionWhenQuotedValueBeginsWithManyHyphens() { + String input = "--option=\"---argument\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"---argument\""); + } + + @Test + public void capturesOptionWhenValueBeginsWithHyphenD() { + String input = "--option=-Dargument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("-Dargument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithHyphensJD() { + String input = "--option=--J=-Dargument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("--J=-Dargument"); + } + + @Test + public void capturesOptionWithPartialValueWhenValueContainsSpace() { + String input = "--option=foo bar"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("foo"); + + assertThat(matcher.find()).isFalse(); + } + + @Test + public void capturesOptionWithPartialValueWhenValueContainsSpaceAndSingleHyphens() { + String input = "--option=-foo -bar"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("-foo"); + + assertThat(matcher.find()).isFalse(); + } + + @Test + public void capturesOptionWhenQuotedValueContainsSpaceAndSingleHyphens() { + String input = "--option=\"-foo -bar\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"-foo -bar\""); + } + + @Test + public void capturesOptionAndFlagWhenValueContainsSpaceAndDoubleHyphens() { + String input = "--option=--foo --bar"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("--foo"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("bar"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesOptionWhenQuotedValueContainsSpaceAndDoubleHyphens() { + String input = "--option=\"--foo --bar\""; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("\"--foo --bar\""); + } + + @Test + public void capturesOptionWhenKeyContainsUnderscores() { + String input = "--this_is_the_option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("this_is_the_option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenValueContainsUnderscores() { + String input = "--option=this_is_the_argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("this_is_the_argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithUnderscore() { + String input = "--option=_argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("_argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithUnderscoreAndAssignIsSpace() { + String input = "--option _argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("_argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithManyUnderscores() { + String input = "--option=___argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("___argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithManyUnderscoresAndAssignIsSpace() { + String input = "--option ___argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("___argument"); + } + + @Test + public void capturesOptionWhenKeyContainsPeriods() { + String input = "--this.is.the.option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("this.is.the.option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void capturesOptionWhenValueContainsPeriods() { + String input = "--option=this.is.the.argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("this.is.the.argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithPeriod() { + String input = "--option=.argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo(".argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithPeriodAndAssignIsSpace() { + String input = "--option .argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo(".argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithManyPeriods() { + String input = "--option=...argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("...argument"); + } + + @Test + public void capturesOptionWhenValueBeginsWithManyPeriodsAndAssignIsSpace() { + String input = "--option ...argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("...argument"); + } + + @Test + public void doesNotMatchWhenPrefixIsSingleHyphen() { + String input = "-option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isFalse(); + } + + @Test + public void doesNotMatchWhenPrefixIsMissing() { + String input = "option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isFalse(); + } + + @Test + public void groupZeroCapturesFullInputWhenValid() { + String input = "--option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(0)).isEqualTo(input); + } + + @Test + public void groupValuesHasSizeEqualToGroupCount() { + String input = "--option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(Group.values()).hasSize(matcher.groupCount()); + } + + @Test + public void groupPrefixCapturesHyphens() { + String input = "--option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + } + + @Test + public void groupPrefixCapturesHyphenD() { + String input = "-Doption=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("-D"); + } + + @Test + public void groupPrefixCapturesHyphensJD() { + String input = "--J=-Doption=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--J=-D"); + } + + @Test + public void groupPrefixCapturesIsolatedHyphens() { + String prefix = "--"; + Matcher matcher = Pattern.compile(Group.PREFIX.getRegex()).matcher(prefix); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group()).isEqualTo(prefix); + } + + @Test + public void groupPrefixCapturesIsolatedHyphenD() { + String prefix = "-D"; + Matcher matcher = Pattern.compile(Group.PREFIX.getRegex()).matcher(prefix); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group()).isEqualTo(prefix); + } + + @Test + public void groupPrefixCapturesIsolatedHyphensJD() { + String prefix = "--J=-D"; + Matcher matcher = Pattern.compile(Group.PREFIX.getRegex()).matcher(prefix); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group()).isEqualTo(prefix); + } + + @Test + public void groupKeyCapturesKey() { + String input = "--option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option"); + } + + @Test + public void groupKeyCapturesIsolatedKey() { + String option = "option"; + Matcher matcher = Pattern.compile(Group.KEY.getRegex()).matcher(option); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group()).isEqualTo(option); + } + + @Test + public void groupAssignCapturesEquals() { + String input = "--option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + } + + @Test + public void groupAssignCapturesSpace() { + String input = "--option argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo(" "); + } + + @Test + public void groupAssignCapturesIsolatedEqualsSurroundedBySpaces() { + String assignment = " = "; + Matcher matcher = Pattern.compile(Group.ASSIGN.getRegex()).matcher(assignment); + assertThat(matcher.matches()).isTrue(); + + assertThat(matcher.group()).isEqualTo(assignment); + } + + @Test + public void groupValueCapturesValue() { + String input = "--option=argument"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument"); + } + + @Test + public void groupValueCapturesIsolatedValue() { + String argument = "argument"; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + assertThat(matcher.matches()).isTrue(); + + assertThat(matcher.group()).isEqualTo(argument); + } + + @Test + public void groupValueCapturesIsolatedValueStartingWithManyHyphens() { + String argument = "---argument"; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + assertThat(matcher.matches()).isTrue(); + + assertThat(matcher.group()).isEqualTo(argument); + } + + @Test + public void groupValueDoesNotMatchIsolatedValueContainingSpaces() { + String argument = "foo bar oi vey"; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + + assertThat(matcher.matches()).isFalse(); + } + + @Test + public void groupValueDoesNotMatchIsolatedValueContainingDoubleHyphensAndSpaces() { + String argument = "--foo --bar --oi --vey"; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + + assertThat(matcher.matches()).isFalse(); + } + + @Test + public void groupValueCapturesIsolatedQuotedValueContainingDoubleHyphensAndSpaces() { + String argument = "\"--foo --bar --oi --vey\""; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + assertThat(matcher.matches()).isTrue(); + + assertThat(matcher.group()).isEqualTo(argument); + } + + @Test + public void groupValueCapturesIsolatedValueEndingWithHyphen() { + String argument = "value-"; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + assertThat(matcher.matches()).isTrue(); + + assertThat(matcher.group()).isEqualTo(argument); + } + + @Test + public void groupValueCapturesIsolatedValueEndingWithQuote() { + String argument = "value\""; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + assertThat(matcher.matches()).isTrue(); + + assertThat(matcher.group()).isEqualTo(argument); + } + + @Test + public void groupValueCapturesIsolatedValueContainingSymbols() { + String argument = "'v@lu!\"t"; + Matcher matcher = Pattern.compile(Group.VALUE.getRegex()).matcher(argument); + assertThat(matcher.matches()).isTrue(); + + assertThat(matcher.group()).isEqualTo(argument); + } + + @Test + public void capturesMultipleOptions() { + String input = "--option1=argument1 --option2=argument2"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option1"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument1"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option2"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument2"); + } + + @Test + public void capturesFlagAfterMultipleOptions() { + String input = "--option1=argument1 --option2=argument2 --flag"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option1"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument1"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option2"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("argument2"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + } + + @Test + public void capturesMultipleOptionsAfterFlag() { + String input = "--flag --option1=foo --option2=."; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option1"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("foo"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option2"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("."); + } + + @Test + public void capturesMultipleOptionsSurroundingFlag() { + String input = "--option1=foo --flag --option2=."; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option1"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("foo"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("option2"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("."); + } + + @Test + public void capturesMultipleOptionsAfterCommand() { + String input = "command --key=value --foo=bar"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("key"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("value"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("foo"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("bar"); + } + + @Test + public void capturesMultipleOptionsSurroundingFlagAfterCommand() { + String input = "command --key=value --flag --foo=bar"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("key"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("value"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("foo"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("bar"); + } + + @Test + public void capturesMultipleOptionsWithVariousPrefixes() { + String input = "--key=value -Dflag --J=-Dfoo=bar"; + Matcher matcher = getPattern().matcher(input); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("key"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("value"); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("flag"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isNull(); + assertThat(matcher.group(Group.VALUE.getIndex())).isNull(); + + assertThat(matcher.find()).isTrue(); + assertThat(matcher.group(Group.PREFIX.getIndex())).isEqualTo("--J=-D"); + assertThat(matcher.group(Group.KEY.getIndex())).isEqualTo("foo"); + assertThat(matcher.group(Group.ASSIGN.getIndex())).isEqualTo("="); + assertThat(matcher.group(Group.VALUE.getIndex())).isEqualTo("bar"); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/redaction/RedactionDefaultsTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/RedactionDefaultsTest.java new file mode 100644 index 000000000000..de75a4c7c7a6 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/RedactionDefaultsTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class RedactionDefaultsTest { + + @Test + public void sensitiveSubstringsContainsOnlyPassword() { + assertThat(RedactionDefaults.SENSITIVE_SUBSTRINGS).containsOnly("password"); + } + + @Test + public void sensitivePrefixesContainsSyspropHyphen() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("sysprop-"); + } + + @Test + public void sensitivePrefixesContainsHyphenDSyspropHyphen() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("-Dsysprop-"); + } + + @Test + public void sensitivePrefixesContainsHyphensJDSyspropHyphen() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("--J=-Dsysprop-"); + } + + @Test + public void sensitivePrefixesContainsJavaxDotNetDotSsl() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("javax.net.ssl"); + } + + @Test + public void sensitivePrefixesContainsHyphenDJavaxDotNetDotSsl() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("-Djavax.net.ssl"); + } + + @Test + public void sensitivePrefixesContainsHyphensJDJavaxDotNetDotSsl() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("--J=-Djavax.net.ssl"); + } + + @Test + public void sensitivePrefixesContainsSecurityHyphen() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("security-"); + } + + @Test + public void sensitivePrefixesContainsHyphenDSecurityHyphen() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("-Dsecurity-"); + } + + @Test + public void sensitivePrefixesContainsHyphensJDSecurityHyphen() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES).contains("--J=-Dsecurity-"); + } + + @Test + public void sensitivePrefixesContainsOnlyExpectedStrings() { + assertThat(RedactionDefaults.SENSITIVE_PREFIXES) + .containsOnly("sysprop-", "javax.net.ssl", "security-", + "-Dsysprop-", "-Djavax.net.ssl", "-Dsecurity-", + "--J=-Dsysprop-", "--J=-Djavax.net.ssl", "--J=-Dsecurity-"); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/redaction/RegexRedactionStrategyTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/RegexRedactionStrategyTest.java new file mode 100644 index 000000000000..ba50c6cfc96b --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/RegexRedactionStrategyTest.java @@ -0,0 +1,396 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.apache.geode.internal.util.redaction.RedactionDefaults.SENSITIVE_PREFIXES; +import static org.apache.geode.internal.util.redaction.RedactionDefaults.SENSITIVE_SUBSTRINGS; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; + +public class RegexRedactionStrategyTest { + + private static final String REDACTED = "redacted"; + + private RegexRedactionStrategy regexRedactionStrategy; + + @Before + public void setUp() { + SensitiveDataDictionary sensitiveDataDictionary = new CombinedSensitiveDictionary( + new SensitivePrefixDictionary(SENSITIVE_PREFIXES), + new SensitiveSubstringDictionary(SENSITIVE_SUBSTRINGS)); + + regexRedactionStrategy = + new RegexRedactionStrategy(sensitiveDataDictionary::isSensitive, REDACTED); + } + + @Test + public void redactsGemfirePasswordWithHyphenD() { + String string = "-Dgemfire.password=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsPasswordWithHyphens() { + String string = "--password=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsOptionEndingWithPasswordWithHyphensJDd() { + String string = "--J=-Dgemfire.some.very.qualified.item.password=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsOptionStartingWithSyspropHyphenWithHyphensJD() { + String string = "--J=-Dsysprop-secret.information=%s"; + String sensitive = "__this_should_be_redacted__"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsGemfireSecurityPasswordWithHyphenD() { + String string = "-Dgemfire.security-password=%s"; + String sensitive = "secret"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(expected); + } + + @Test + public void doesNotRedactOptionEndingWithSecurityPropertiesWithHyphenD1() { + String input = "-Dgemfire.security-properties=argument-value"; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void doesNotRedactOptionEndingWithSecurityPropertiesWithHyphenD2() { + String input = "-Dgemfire.security-properties=\"c:\\Program Files (x86)\\My Folder\""; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void doesNotRedactOptionEndingWithSecurityPropertiesWithHyphenD3() { + String input = "-Dgemfire.security-properties=./security-properties"; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void doesNotRedactOptionContainingSecurityHyphenWithHyphensJD() { + String input = "--J=-Dgemfire.sys.security-option=someArg"; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void doesNotRedactNonMatchingGemfireOptionWithHyphenD() { + String input = "-Dgemfire.sys.option=printable"; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactsGemfireUseClusterConfigurationWithHyphenD() { + String input = "-Dgemfire.use-cluster-configuration=true"; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void returnsNonMatchingString() { + String input = "someotherstringoption"; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void doesNotRedactClasspathWithHyphens() { + String input = "--classpath=."; + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .isEqualTo(input); + } + + @Test + public void redactsMatchingOptionWithNonMatchingOptionAndFlagAndMultiplePrefixes() { + String string = "--J=-Dflag -Duser-password=%s --classpath=."; + String sensitive = "foo"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsMultipleMatchingOptionsWithFlags() { + String string = "-DmyArg -Duser-password=%s -DOtherArg -Dsystem-password=%s"; + String sensitive1 = "foo"; + String sensitive2 = "bar"; + String input = String.format(string, sensitive1, sensitive2); + String expected = String.format(string, REDACTED, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive1) + .doesNotContain(sensitive2) + .isEqualTo(expected); + } + + @Test + public void redactsMultipleMatchingOptionsWithMultipleNonMatchingOptionsAndMultiplePrefixes() { + String string = + "-Dlogin-password=%s -Dlogin-name=%s -Dgemfire-password = %s --geode-password= %s --J=-Dsome-other-password =%s"; + String sensitive1 = "secret"; + String nonSensitive = "admin"; + String sensitive2 = "super-secret"; + String sensitive3 = "confidential"; + String sensitive4 = "shhhh"; + String input = String.format( + string, sensitive1, nonSensitive, sensitive2, sensitive3, sensitive4); + String expected = String.format( + string, REDACTED, nonSensitive, REDACTED, REDACTED, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive1) + .contains(nonSensitive) + .doesNotContain(sensitive2) + .doesNotContain(sensitive3) + .doesNotContain(sensitive4) + .isEqualTo(expected); + } + + @Test + public void redactsMatchingOptionWithNonMatchingOptionAfterCommand() { + String string = "connect --password=%s --user=%s"; + String reusedSensitive = "test"; + String input = String.format(string, reusedSensitive, reusedSensitive); + String expected = String.format(string, REDACTED, reusedSensitive); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .contains(reusedSensitive) + .isEqualTo(expected); + } + + @Test + public void redactsMultipleMatchingOptionsButNotKeyUsingSameStringAsValue() { + String string = "connect --%s-password=%s --product-password=%s"; + String reusedSensitive = "test"; + String sensitive = "test1"; + String input = String.format(string, reusedSensitive, reusedSensitive, sensitive); + String expected = String.format(string, reusedSensitive, REDACTED, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .contains(reusedSensitive) + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactRedactsGemfireSslTruststorePassword() { + String string = "-Dgemfire.ssl-truststore-password=%s"; + String sensitive = "gibberish"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsGemfireSslKeystorePassword() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "gibberish"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsValueEndingWithHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "supersecret-"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsValueContainingHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "super-secret"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsValueContainingManyHyphens() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "this-is-super-secret"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsValueStartingWithHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "-supersecret"; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } + + @Test + public void redactsQuotedValueStartingWithHyphen() { + String string = "-Dgemfire.ssl-keystore-password=%s"; + String sensitive = "\"-supersecret\""; + String input = String.format(string, sensitive); + String expected = String.format(string, REDACTED); + + String output = regexRedactionStrategy.redact(input); + + assertThat(output) + .as("output of redact(" + input + ")") + .doesNotContain(sensitive) + .isEqualTo(expected); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/redaction/SensitivePrefixDictionaryTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/SensitivePrefixDictionaryTest.java new file mode 100644 index 000000000000..e3436c0df7d4 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/SensitivePrefixDictionaryTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; + +public class SensitivePrefixDictionaryTest { + + private SensitivePrefixDictionary dictionary; + + @Before + public void setUp() { + dictionary = new SensitivePrefixDictionary(RedactionDefaults.SENSITIVE_PREFIXES); + } + + @Test + public void startsWithSyspropHyphenIsTrue() { + assertThat(dictionary.isSensitive("sysprop-something")).isTrue(); + } + + @Test + public void startsWithJavaxNetSslIsTrue() { + assertThat(dictionary.isSensitive("javax.net.ssl.something")).isTrue(); + } + + @Test + public void startsWithSecurityHyphenIsTrue() { + assertThat(dictionary.isSensitive("security-something")).isTrue(); + } + + @Test + public void nullStringIsFalse() { + assertThat(dictionary.isSensitive(null)).isFalse(); + } + + @Test + public void emptyStringIsFalse() { + assertThat(dictionary.isSensitive("")).isFalse(); + } + + @Test + public void passwordLowerCaseIsFalse() { + assertThat(dictionary.isSensitive("password")).isFalse(); + } + + @Test + public void passwordUpperCaseIsFalse() { + assertThat(dictionary.isSensitive("PASSWORD")).isFalse(); + } + + @Test + public void startsWithPasswordIsFalse() { + assertThat(dictionary.isSensitive("passwordforsomething")).isFalse(); + } + + @Test + public void endsWithPasswordIsFalse() { + assertThat(dictionary.isSensitive("mypassword")).isFalse(); + } + + @Test + public void containsPasswordIsFalse() { + assertThat(dictionary.isSensitive("mypasswordforsomething")).isFalse(); + } + + @Test + public void passwordWithLeadingHyphenIsFalse() { + assertThat(dictionary.isSensitive("-password")).isFalse(); + } + + @Test + public void passwordWithTrailingHyphenIsFalse() { + assertThat(dictionary.isSensitive("password-")).isFalse(); + } + + @Test + public void passwordWithMiddleHyphenIsFalse() { + assertThat(dictionary.isSensitive("pass-word")).isFalse(); + } + + @Test + public void startsWithSyspropWithoutHyphenIsFalse() { + assertThat(dictionary.isSensitive("syspropsomething")).isFalse(); + } + + @Test + public void containsSyspropWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-sysprop-something")).isFalse(); + } + + @Test + public void endsWithSyspropWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-sysprop-")).isFalse(); + } + + @Test + public void syspropIsFalse() { + assertThat(dictionary.isSensitive("sysprop")).isFalse(); + } + + @Test + public void startsWithJavaxSslIsFalse() { + assertThat(dictionary.isSensitive("javax.ssl.something")).isFalse(); + } + + @Test + public void startsWithJavaxNetIsFalse() { + assertThat(dictionary.isSensitive("javax.net.something")).isFalse(); + } + + @Test + public void startsWithJavaxWithoutNetWithSslIsFalse() { + assertThat(dictionary.isSensitive("javax.ssl.something")).isFalse(); + } + + @Test + public void containsJavaxNetSslIsFalse() { + assertThat(dictionary.isSensitive("my.javax.net.ssl.something")).isFalse(); + } + + @Test + public void endsWithJavaxNetSslIsFalse() { + assertThat(dictionary.isSensitive("my.javax.net.ssl")).isFalse(); + } + + @Test + public void startsWithSecurityWithoutHyphenIsFalse() { + assertThat(dictionary.isSensitive("securitysomething")).isFalse(); + } + + @Test + public void containsSecurityWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-security-something")).isFalse(); + } + + @Test + public void endsWithSecurityWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-security-")).isFalse(); + } + + @Test + public void securityIsFalse() { + assertThat(dictionary.isSensitive("security")).isFalse(); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/redaction/SensitiveSubstringDictionaryTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/SensitiveSubstringDictionaryTest.java new file mode 100644 index 000000000000..b958676ae44f --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/SensitiveSubstringDictionaryTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; + +public class SensitiveSubstringDictionaryTest { + + private SensitiveSubstringDictionary dictionary; + + @Before + public void setUp() { + dictionary = new SensitiveSubstringDictionary(RedactionDefaults.SENSITIVE_SUBSTRINGS); + } + + @Test + public void passwordLowerCaseIsTrue() { + assertThat(dictionary.isSensitive("password")).isTrue(); + } + + @Test + public void passwordUpperCaseIsTrue() { + assertThat(dictionary.isSensitive("PASSWORD")).isTrue(); + } + + @Test + public void startsWithPasswordIsTrue() { + assertThat(dictionary.isSensitive("passwordforsomething")).isTrue(); + } + + @Test + public void endsWithPasswordIsTrue() { + assertThat(dictionary.isSensitive("mypassword")).isTrue(); + } + + @Test + public void containsPasswordIsTrue() { + assertThat(dictionary.isSensitive("mypasswordforsomething")).isTrue(); + } + + @Test + public void passwordWithLeadingHyphenIsTrue() { + assertThat(dictionary.isSensitive("-password")).isTrue(); + } + + @Test + public void passwordWithTrailingHyphenIsTrue() { + assertThat(dictionary.isSensitive("password-")).isTrue(); + } + + @Test + public void nullStringIsFalse() { + assertThat(dictionary.isSensitive(null)).isFalse(); + } + + @Test + public void emptyStringIsFalse() { + assertThat(dictionary.isSensitive("")).isFalse(); + } + + @Test + public void passwordWithMiddleHyphenIsFalse() { + assertThat(dictionary.isSensitive("pass-word")).isFalse(); + } + + @Test + public void startsWithSyspropHyphenIsFalse() { + assertThat(dictionary.isSensitive("sysprop-something")).isFalse(); + } + + @Test + public void startsWithSyspropWithoutHyphenIsFalse() { + assertThat(dictionary.isSensitive("syspropsomething")).isFalse(); + } + + @Test + public void containsSyspropWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-sysprop-something")).isFalse(); + } + + @Test + public void endsWithSyspropWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-sysprop-")).isFalse(); + } + + @Test + public void syspropIsFalse() { + assertThat(dictionary.isSensitive("sysprop")).isFalse(); + } + + @Test + public void startsWithJavaxNetSslIsFalse() { + assertThat(dictionary.isSensitive("javax.net.ssl.something")).isFalse(); + } + + @Test + public void startsWithJavaxSslIsFalse() { + assertThat(dictionary.isSensitive("javax.ssl.something")).isFalse(); + } + + @Test + public void startsWithJavaxNetIsFalse() { + assertThat(dictionary.isSensitive("javax.net.something")).isFalse(); + } + + @Test + public void startsWithJavaxWithoutNetWithSslIsFalse() { + assertThat(dictionary.isSensitive("javax.ssl.something")).isFalse(); + } + + @Test + public void containsJavaxNetSslIsFalse() { + assertThat(dictionary.isSensitive("my.javax.net.ssl.something")).isFalse(); + } + + @Test + public void endsWithJavaxNetSslIsFalse() { + assertThat(dictionary.isSensitive("my.javax.net.ssl")).isFalse(); + } + + @Test + public void startsWithSecurityHyphenIsFalse() { + assertThat(dictionary.isSensitive("security-something")).isFalse(); + } + + @Test + public void startsWithSecurityWithoutHyphenIsFalse() { + assertThat(dictionary.isSensitive("securitysomething")).isFalse(); + } + + @Test + public void containsSecurityWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-security-something")).isFalse(); + } + + @Test + public void endsWithSecurityWithHyphenIsFalse() { + assertThat(dictionary.isSensitive("my-security-")).isFalse(); + } + + @Test + public void securityIsFalse() { + assertThat(dictionary.isSensitive("security")).isFalse(); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/util/redaction/StringRedactionTest.java b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/StringRedactionTest.java new file mode 100644 index 000000000000..15896f10d235 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/util/redaction/StringRedactionTest.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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. + */ +package org.apache.geode.internal.util.redaction; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.AdditionalAnswers.returnsFirstArg; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +public class StringRedactionTest { + + private static final String REDACTED = "redacted"; + + private SensitiveDataDictionary sensitiveDataDictionary; + private RedactionStrategy redactionStrategy; + + private StringRedaction stringRedaction; + + @Before + public void setUp() { + sensitiveDataDictionary = mock(SensitiveDataDictionary.class); + redactionStrategy = mock(RedactionStrategy.class); + + stringRedaction = + new StringRedaction(REDACTED, sensitiveDataDictionary, redactionStrategy); + } + + @Test + public void redactDelegatesString() { + String input = "line"; + String expected = "expected"; + + when(redactionStrategy.redact(input)).thenReturn(expected); + + String result = stringRedaction.redact(input); + + verify(redactionStrategy).redact(input); + assertThat(result).isEqualTo(expected); + } + + @Test + public void redactDelegatesNullString() { + String input = null; + + stringRedaction.redact(input); + + verify(redactionStrategy).redact(input); + } + + @Test + public void redactDelegatesEmptyString() { + String input = ""; + + stringRedaction.redact(input); + + verify(redactionStrategy).redact(input); + } + + @Test + public void redactDelegatesIterable() { + String line1 = "line1"; + String line2 = "line2"; + String line3 = "line3"; + Collection input = new ArrayList<>(); + input.add(line1); + input.add(line2); + input.add(line3); + String joinedLine = String.join(" ", input); + String expected = "expected"; + + when(redactionStrategy.redact(joinedLine)).thenReturn(expected); + + String result = stringRedaction.redact(input); + + verify(redactionStrategy).redact(joinedLine); + assertThat(result).isEqualTo(expected); + } + + @Test + public void redactNullIterableThrowsNullPointerException() { + Collection input = null; + + Throwable thrown = catchThrowable(() -> { + stringRedaction.redact(input); + }); + + assertThat(thrown).isInstanceOf(NullPointerException.class); + } + + @Test + public void redactArgumentIfNecessaryDelegatesSensitiveKey() { + String key = "key"; + String value = "value"; + + when(sensitiveDataDictionary.isSensitive(key)).thenReturn(true); + + String result = stringRedaction.redactArgumentIfNecessary(key, value); + + verify(sensitiveDataDictionary).isSensitive(key); + assertThat(result).isEqualTo(REDACTED); + } + + @Test + public void redactArgumentIfNecessaryDelegatesNonSensitiveKey() { + String key = "key"; + String value = "value"; + + when(sensitiveDataDictionary.isSensitive(key)).thenReturn(false); + + String result = stringRedaction.redactArgumentIfNecessary(key, value); + + verify(sensitiveDataDictionary).isSensitive(key); + assertThat(result).isEqualTo(value); + } + + @Test + public void redactArgumentIfNecessaryDelegatesNullKey() { + String key = null; + + stringRedaction.redactArgumentIfNecessary(key, "value"); + + verify(sensitiveDataDictionary).isSensitive(key); + } + + @Test + public void redactArgumentIfNecessaryDelegatesEmptyKey() { + String key = ""; + + stringRedaction.redactArgumentIfNecessary(key, "value"); + + verify(sensitiveDataDictionary).isSensitive(key); + } + + @Test + public void redactArgumentIfNecessaryReturnsNullValue() { + String value = null; + + String result = stringRedaction.redactArgumentIfNecessary("key", value); + + assertThat(result).isEqualTo(value); + } + + @Test + public void redactArgumentIfNecessaryReturnsEmptyValue() { + String value = ""; + + String result = stringRedaction.redactArgumentIfNecessary("key", value); + + assertThat(result).isEqualTo(value); + } + + @Test + public void redactEachInListDelegatesEachStringInIterable() { + String string1 = "string1"; + String string2 = "string2"; + String string3 = "string3"; + List input = new ArrayList<>(); + input.add(string1); + input.add(string2); + input.add(string3); + + when(redactionStrategy.redact(anyString())).then(returnsFirstArg()); + + List result = stringRedaction.redactEachInList(input); + + verify(redactionStrategy).redact(string1); + verify(redactionStrategy).redact(string2); + verify(redactionStrategy).redact(string3); + assertThat(result).isEqualTo(input); + } + + @Test + public void redactEachInListDoesNotDelegateEmptyIterable() { + List input = Collections.emptyList(); + + when(redactionStrategy.redact(anyString())).then(returnsFirstArg()); + + List result = stringRedaction.redactEachInList(input); + + verifyNoInteractions(redactionStrategy); + assertThat(result).isEqualTo(input); + } + + @Test + public void redactEachInListNullIterableThrowsNullPointerException() { + List input = null; + + when(redactionStrategy.redact(anyString())).then(returnsFirstArg()); + + Throwable thrown = catchThrowable(() -> { + stringRedaction.redactEachInList(input); + }); + + assertThat(thrown).isInstanceOf(NullPointerException.class); + } + + @Test + public void isSensitiveDelegatesString() { + String input = "input"; + + when(sensitiveDataDictionary.isSensitive(anyString())).thenReturn(true); + + boolean result = stringRedaction.isSensitive(input); + + assertThat(result).isTrue(); + } + + @Test + public void isSensitiveDelegatesNullString() { + String input = null; + + when(sensitiveDataDictionary.isSensitive(isNull())).thenReturn(true); + + boolean result = stringRedaction.isSensitive(input); + + assertThat(result).isTrue(); + } + + @Test + public void isSensitiveDelegatesEmptyString() { + String input = ""; + + when(sensitiveDataDictionary.isSensitive(anyString())).thenReturn(true); + + boolean result = stringRedaction.isSensitive(input); + + assertThat(result).isTrue(); + } +} diff --git a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RequiresGeodeHome.java b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RequiresGeodeHome.java index 9cc4f0686b9c..1741e3bac4e5 100644 --- a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RequiresGeodeHome.java +++ b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RequiresGeodeHome.java @@ -16,7 +16,6 @@ import static java.lang.System.lineSeparator; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertNotNull; import java.io.File; @@ -42,10 +41,14 @@ protected void before() { public File getGeodeHome() { String geodeHomePath = System.getenv("GEODE_HOME"); - assertNotNull(GEODE_HOME_NOT_SET_MESSAGE, geodeHomePath); + assertThat(geodeHomePath) + .withFailMessage(GEODE_HOME_NOT_SET_MESSAGE) + .isNotNull(); File geodeHome = new File(geodeHomePath); - assertThat(geodeHome).exists(); + assertThat(geodeHome) + .exists() + .isDirectoryContaining(file -> file.getName().startsWith("bin")); return geodeHome; }