You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by kl...@apache.org on 2021/08/12 21:09:22 UTC
[geode] branch support/1.14 updated: GEODE-9354: Extract and test
ArgumentRedactorRegex (#6641) (#6747)
This is an automated email from the ASF dual-hosted git repository.
klund pushed a commit to branch support/1.14
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/support/1.14 by this push:
new b82e282 GEODE-9354: Extract and test ArgumentRedactorRegex (#6641) (#6747)
b82e282 is described below
commit b82e282b80ef27d9018509be0f2e62173a5a35d5
Author: Kirk Lund <kl...@apache.org>
AuthorDate: Thu Aug 12 14:08:31 2021 -0700
GEODE-9354: Extract and test ArgumentRedactorRegex (#6641) (#6747)
* Rename ArgumentRedactorJUnitTest to ArgumentRedactorTest.
* Reorganize and reformat ArgumentRedactor and its tests.
* Fix issues in regex found by new tests.
(cherry picked from commit 693e18c48d1c0e3601c517cb7c5493b54649dc10)
---
...scribeConfigAreFullyRedactedAcceptanceTest.java | 111 +--
.../redaction/ArgumentRedactorIntegrationTest.java | 52 +
.../apache/geode/codeAnalysis/excludedClasses.txt | 1 +
.../org/apache/geode/internal/AbstractConfig.java | 2 +-
.../geode/internal/util/ArgumentRedactor.java | 216 +----
.../redaction/CombinedSensitiveDictionary.java | 41 +
.../geode/internal/util/redaction/ParserRegex.java | 93 ++
.../internal/util/redaction/RedactionDefaults.java | 58 ++
.../internal/util/redaction/RedactionStrategy.java | 51 +
.../util/redaction/RegexRedactionStrategy.java | 50 +
.../util/redaction/SensitiveDataDictionary.java | 32 +
.../util/redaction/SensitivePrefixDictionary.java | 37 +
.../redaction/SensitiveSubstringDictionary.java | 37 +
.../internal/util/redaction/StringRedaction.java | 123 +++
.../internal/util/ArgumentRedactorJUnitTest.java | 221 -----
.../geode/internal/util/ArgumentRedactorTest.java | 675 +++++++++++++
.../redaction/CombinedSensitiveDictionaryTest.java | 130 +++
.../internal/util/redaction/ParserRegexTest.java | 1006 ++++++++++++++++++++
.../util/redaction/RedactionDefaultsTest.java | 80 ++
.../util/redaction/RegexRedactionStrategyTest.java | 396 ++++++++
.../redaction/SensitivePrefixDictionaryTest.java | 160 ++++
.../SensitiveSubstringDictionaryTest.java | 160 ++++
.../util/redaction/StringRedactionTest.java | 254 +++++
.../geode/test/junit/rules/RequiresGeodeHome.java | 9 +-
24 files changed, 3540 insertions(+), 455 deletions(-)
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 1c0cc17..e569709 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 @@ package org.apache.geode.management.internal.cli.commands;
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 @@ import org.apache.geode.test.junit.rules.gfsh.GfshRule;
@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<File> 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 class LogsAndDescribeConfigAreFullyRedactedAcceptanceTest {
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 0000000..be358e3
--- /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<String> 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 a96907f..99ab950 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 4daeb4e..03c4d6d 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 @@ public abstract class AbstractConfig implements Config {
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 172d449..9df1a4e 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<String> tabooToContain =
- Collections.unmodifiableList(ArrayUtils.asList("password"));
- @Immutable
- private static final List<String> 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.
- * <p>
- *
- * {@code ((?:^| )(?:--J=)?--?)([^\s=]+)(?=[ =])( *[ =] *)(?! *-)((?:"[^"]*"|\S+))}
+ * Parse a string to find key/value pairs and redact the values if identified as sensitive.
*
* <p>
- * This pattern consists of one captured boundary,
- * three additional capture groups, and two look-ahead boundaries.
+ * The following format is expected:<br>
+ * - Each key/value pair should be separated by spaces.<br>
+ * - The key must be preceded by '--', '-D', or '--J=-D'.<br>
+ * - The value may optionally be wrapped in quotation marks.<br>
+ * - The value is assigned to a key with '=', '=' padded with any number of optional spaces, or
+ * any number of spaces without '='.<br>
+ * - The value must not contain spaces without being wrapped in quotation marks.<br>
+ * - The value may contain spaces or any other symbols when wrapped in quotation marks.
*
* <p>
- * The four capture groups are:
- * <ul>
- * <li>[1] The beginning boundary, including at most one leading space,
- * possibly including "--J=", and including the option's leading "-" or "--"</li>
- * <li>[2] The option, which cannot include spaces</li>
- * <li>[3] The option / argument separator, consisting of at least one character
- * made of spaces and/or at most one "="</li>
- * <li>[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.</li>
- * </ul>
+ * Examples:
+ * <ol>
+ * <li>"--password=secret"
+ * <li>"--user me --password secret"
+ * <li>"-Dflag -Dopt=arg"
+ * <li>"--classpath=."
+ * <li>"password=secret"
+ * </ol>
*
- * 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"}`).
- * <p>
+ * @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<String> strings) {
+ return DELEGATE.redact(strings);
+ }
/**
- * Parse a string to find option/argument pairs and redact the arguments if necessary.<br>
- *
- * The following format is expected:<br>
- * - Each option/argument pair should be separated by spaces.<br>
- * - The option of each pair must be preceded by at least one hyphen '-'.<br>
- * - Arguments may or may not be wrapped in quotation marks.<br>
- * - Options and arguments may be separated by an equals sign '=' or any number of spaces.<br>
- * <br>
- * Examples:<br>
- * "--password=secret"<br>
- * "--user me --password secret"<br>
- * "-Dflag -Dopt=arg"<br>
- * "--classpath=."<br>
- *
- * 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<String> 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<String> redactEachInList(Collection<String> 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<String> redactEachInList(List<String> 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 0000000..b63efce
--- /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<SensitiveDataDictionary> 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 0000000..0bfe6d8
--- /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.
+ *
+ * <p>
+ * The raw regex string is:
+ *
+ * {@code (?<prefix>--J=-D|-D|--)(?<key>[^\s=]+)(?:(?! (?:--J=-D|-D|--))(?<assign> *[ =] *)(?! (?:--J=-D|-D|--))(?<value>"[^"]*"|\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", "(?<prefix>--J=-D|-D|--)"),
+
+ /** Key has a value or represents a flag when no value is assigned */
+ KEY(2, "key", "(?<key>[^\\s=]+)"),
+
+ /** Assign is an operator for assigning a value to a key and may be = or space */
+ ASSIGN(3, "assign", "(?<assign> *[ =] *)"),
+
+ /** Value is assigned to a key following an assign operator */
+ VALUE(4, "value", "(?<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 0000000..ced2b84
--- /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<String> SENSITIVE_SUBSTRINGS =
+ unmodifiableList(asList("password"));
+
+ /**
+ * Strings starting with these prefixes are flagged as sensitive.
+ */
+ @Immutable
+ static final List<String> 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 0000000..c305822
--- /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.
+ *
+ * <p>
+ * The following format is expected:<br>
+ * - Each key/value pair should be separated by spaces.<br>
+ * - The key must be preceded by '--', '-D', or '--J=-D'.<br>
+ * - The value may optionally be wrapped in quotation marks.<br>
+ * - The value is assigned to a key with '=', '=' padded with any number of optional spaces, or
+ * any number of spaces without '='.<br>
+ * - The value must not contain spaces without being wrapped in quotation marks.<br>
+ * - The value may contain spaces or any other symbols when wrapped in quotation marks.
+ *
+ * <p>
+ * Examples:
+ * <ol>
+ * <li>"--password=secret"
+ * <li>"--user me --password secret"
+ * <li>"-Dflag -Dopt=arg"
+ * <li>"--classpath=."
+ * <li>"password=secret"
+ * </ol>
+ *
+ * @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 0000000..1f758eb
--- /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<String, Boolean> isSensitive;
+ private final String redacted;
+
+ RegexRedactionStrategy(Function<String, Boolean> 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 0000000..deb73e4
--- /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 0000000..9d08670
--- /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<String> sensitivePrefixes;
+
+ SensitivePrefixDictionary(Iterable<String> 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 0000000..5cd4342
--- /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<String> sensitiveSubstrings;
+
+ SensitiveSubstringDictionary(Iterable<String> 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 0000000..ea514fc
--- /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.
+ *
+ * <p>
+ * The following format is expected:<br>
+ * - Each key/value pair should be separated by spaces.<br>
+ * - The key must be preceded by '--', '-D', or '--J=-D'.<br>
+ * - The value may optionally be wrapped in quotation marks.<br>
+ * - The value is assigned to a key with '=', '=' padded with any number of optional spaces, or
+ * any number of spaces without '='.<br>
+ * - The value must not contain spaces without being wrapped in quotation marks.<br>
+ * - The value may contain spaces or any other symbols when wrapped in quotation marks.
+ *
+ * <p>
+ * Examples:
+ * <ol>
+ * <li>"--password=secret"
+ * <li>"--user me --password secret"
+ * <li>"-Dflag -Dopt=arg"
+ * <li>"--classpath=."
+ * <li>"password=secret"
+ * </ol>
+ *
+ * @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<String> 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<String> redactEachInList(Collection<String> 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 129627c..0000000
--- 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<String> someTabooOptions =
- Arrays.asList("-Dgemfire.password=" + argumentThatShouldBeRedacted,
- "--password=" + argumentThatShouldBeRedacted,
- "--J=-Dgemfire.some.very.qualified.item.password=" + argumentThatShouldBeRedacted,
- "--J=-Dsysprop-secret.information=" + argumentThatShouldBeRedacted);
-
- List<String> fullyRedacted = redactEachInList(someTabooOptions);
- assertThat(fullyRedacted).doesNotContainAnyElementsOf(someTabooOptions);
- }
-
- @Test
- public void redactorWillIdentifySampleTabooProperties() {
- List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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 0000000..6a60c95
--- /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<String> 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<String> 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<String> 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<String> 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 0000000..ab691da
--- /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 0000000..bc14929
--- /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 0000000..de75a4c
--- /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 0000000..ba50c6c
--- /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 0000000..e3436c0
--- /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 0000000..b958676
--- /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 0000000..15896f1
--- /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<String> 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<String> 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<String> input = new ArrayList<>();
+ input.add(string1);
+ input.add(string2);
+ input.add(string3);
+
+ when(redactionStrategy.redact(anyString())).then(returnsFirstArg());
+
+ List<String> 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<String> input = Collections.emptyList();
+
+ when(redactionStrategy.redact(anyString())).then(returnsFirstArg());
+
+ List<String> result = stringRedaction.redactEachInList(input);
+
+ verifyNoInteractions(redactionStrategy);
+ assertThat(result).isEqualTo(input);
+ }
+
+ @Test
+ public void redactEachInListNullIterableThrowsNullPointerException() {
+ List<String> 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 9cc4f06..1741e3b 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 @@ package org.apache.geode.test.junit.rules;
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 @@ public class RequiresGeodeHome extends SerializableExternalResource {
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;
}