Skip to content

Commit

Permalink
Add support for masking multiple JSON values in the same input (#175)
Browse files Browse the repository at this point in the history
Fixes #173
  • Loading branch information
gavlyukovskiy authored Sep 15, 2024
1 parent 4c98d6e commit 9568ca4
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 12 deletions.
19 changes: 14 additions & 5 deletions src/main/java/dev/blaauwendraad/masker/json/KeyContainsMasker.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ public byte[] mask(byte[] input) {
keyMaskingConfig = keyMatcher.getMaskConfigIfMatched(maskingState.getMessage(), -1, -1, maskingState.getCurrentJsonPathNode());
}

stepOverWhitespaceCharacters(maskingState);
visitValue(maskingState, keyMaskingConfig);
while (!maskingState.endOfJson()) {
stepOverWhitespaceCharacters(maskingState);
if (!visitValue(maskingState, keyMaskingConfig)) {
maskingState.next();
}
}

return maskingState.flushReplacementOperations();
} catch (ArrayIndexOutOfBoundsException | StackOverflowError e) {
Expand All @@ -63,10 +67,12 @@ public byte[] mask(byte[] input) {
* @param maskingState the current masking state
* @param keyMaskingConfig if not null it means that the current value is being masked otherwise the value is not
* being masked
*
* @return whether a value was found, if returned false the calling code must advance to avoid infinite loops
*/
private void visitValue(MaskingState maskingState, @Nullable KeyMaskingConfig keyMaskingConfig) {
private boolean visitValue(MaskingState maskingState, @Nullable KeyMaskingConfig keyMaskingConfig) {
if (maskingState.endOfJson()) {
return;
return true;
}
// using switch-case over 'if'-statements to improve performance by ~20% (measured in benchmarks)
switch (maskingState.byteAtCurrentIndex()) {
Expand Down Expand Up @@ -101,8 +107,11 @@ private void visitValue(MaskingState maskingState, @Nullable KeyMaskingConfig ke
}
}
case 'n' -> maskingState.incrementIndex(4);
default -> { /* return */ }
default -> {
return false;
}
}
return true;
}

/**
Expand Down
Binary file modified src/test/JSONTestSuite/masked/i_string_UTF-16LE_with_BOM.json
Binary file not shown.
Binary file modified src/test/JSONTestSuite/masked/i_string_utf16BE_no_BOM.json
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"a": "&&&"} "x"
{"a": "&&&"} "***"
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public class JSONTestSuiteTest {
private static final List<String> INVALID_UTF_8 = List.of(
"i_string_1st_surrogate_but_2nd_missing.json",
"i_string_1st_valid_surrogate_2nd_invalid.json",
"i_string_incomplete_surrogates_escape_valid.json",
"i_string_incomplete_surrogate_and_escape_valid.json",
"i_string_incomplete_surrogate_pair.json",
"i_string_incomplete_surrogates_escape_valid.json",
"i_string_invalid_lonely_surrogate.json",
"i_string_invalid_surrogate.json",
"i_string_inverted_surrogates_U+1D11E.json",
Expand All @@ -45,9 +45,9 @@ public class JSONTestSuiteTest {
"n_string_1_surrogate_then_escape_u1.json",
"n_string_1_surrogate_then_escape_u1x.json",
"n_string_backslash_00.json",
"n_string_escape_x.json",
"n_string_escaped_ctrl_char_tab.json",
"n_string_escaped_emoji.json",
"n_string_escape_x.json",
"n_string_incomplete_escaped_character.json",
"n_string_incomplete_surrogate.json",
"n_string_incomplete_surrogate_escape_invalid.json",
Expand All @@ -59,11 +59,13 @@ public class JSONTestSuiteTest {
"n_structure_open_open.json"
);

private static final List<String> JACKSON_IS_DIFFERENT_FROM_TO_STRING = List.of(
private static final List<String> INVALID_JSON_JACKSON_DIFFERENT_BEHAVIOR = List.of(
"i_string_UTF-16LE_with_BOM.json",
"i_string_UTF8_surrogate_U+D800.json",
"i_string_not_in_unicode_range.json",
"i_string_overlong_sequence_2_bytes.json",
"i_string_utf16LE_no_BOM.json",
"i_string_UTF8_surrogate_U+D800.json"
"i_string_utf16BE_no_BOM.json",
"i_string_utf16LE_no_BOM.json"
);
public static final Path JSON_TEST_SUITE_PATH = Path.of("src/test/JSONTestSuite/");

Expand Down Expand Up @@ -107,7 +109,7 @@ void mayPassSuiteWithNoopMaskerShouldNotFail(String testName, JsonTestSuiteFile

byte[] actual = jsonMasker.mask(file.originalContent);

if (JACKSON_IS_DIFFERENT_FROM_TO_STRING.contains(file.name)) {
if (INVALID_JSON_JACKSON_DIFFERENT_BEHAVIOR.contains(file.name)) {
// for these jackson behavior is different from java.lang.String parsing of UTF-8 characters
return;
}
Expand Down
81 changes: 81 additions & 0 deletions src/test/java/dev/blaauwendraad/masker/json/MultiJsonTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package dev.blaauwendraad.masker.json;

import dev.blaauwendraad.masker.json.config.JsonMaskingConfig;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Set;

public class MultiJsonTest {

@Test
void shouldMaskJsonLinesObjects() {
JsonMasker jsonMasker = JsonMasker.getMasker(Set.of("maskMe"));

Assertions.assertThat(jsonMasker.mask(
"""
{"maskMe":"secret"}
{"maskMe":"secret"}
{"maskMe":"secret"}
"""))
.isEqualTo(
"""
{"maskMe":"***"}
{"maskMe":"***"}
{"maskMe":"***"}
""");
}

@Test
void shouldMaskJsonLinesArrays() {
JsonMasker jsonMasker = JsonMasker.getMasker(Set.of("maskMe"));

Assertions.assertThat(jsonMasker.mask(
"""
[{"maskMe":"secret"}]
[{"maskMe":"secret"}]
[{"maskMe":"secret"}]
"""))
.isEqualTo(
"""
[{"maskMe":"***"}]
[{"maskMe":"***"}]
[{"maskMe":"***"}]
""");
}

@Test
void shouldMaskJsonLinesMixedTypes() {
JsonMasker jsonMasker = JsonMasker.getMasker(JsonMaskingConfig.builder().allowKeys().build());

Assertions.assertThat(jsonMasker.mask(
"""
"secret"
true
false
null
123
"""))
.isEqualTo(
"""
"***"
"&&&"
"&&&"
null
"###"
""");
}
@Test
void shouldMaskRepeatedJsonsWithoutNewLines() {
JsonMasker jsonMasker = JsonMasker.getMasker(Set.of("maskMe"));

Assertions.assertThat(jsonMasker.mask(
"""
{"maskMe":"secret"}{"maskMe":"secret"} {"maskMe":"secret"}
"""))
.isEqualTo(
"""
{"maskMe":"***"}{"maskMe":"***"} {"maskMe":"***"}
""");
}
}

0 comments on commit 9568ca4

Please sign in to comment.