From 11b64f6a461ec3e078b4a5f6ba728897d6401197 Mon Sep 17 00:00:00 2001 From: rupert-griffin <167495366+rupert-griffin@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:49:29 -0400 Subject: [PATCH] Made AbstractFilter Denylist settings configurable (#925) --- .../output/filter/AbstractFilter.java | 67 +++++++++++------ .../output/filter/AbstractFilterTest.java | 72 +++++++++++++++++++ 2 files changed, 116 insertions(+), 23 deletions(-) diff --git a/src/main/java/emissary/output/filter/AbstractFilter.java b/src/main/java/emissary/output/filter/AbstractFilter.java index 387acdaa53..93be84df3d 100755 --- a/src/main/java/emissary/output/filter/AbstractFilter.java +++ b/src/main/java/emissary/output/filter/AbstractFilter.java @@ -88,6 +88,12 @@ public abstract class AbstractFilter implements IDropOffFilter { @Nullable protected DropOffUtil dropOffUtil = null; + protected String denylistAllowedNameChars = "a-zA-Z0-9_\\-"; + protected String denylistFiletypeFormat = "^[%s]+$"; + protected Pattern denylistFiletypeFormatPattern; + protected String denylistViewNameFormat = "^[%s]+(\\.[%s]+)?\\*?$"; + protected Pattern denylistViewNameFormatPattern; + /** * Initialization phase hook for the filter with default preferences for the runtime configuration of the filter */ @@ -118,7 +124,7 @@ public void initialize(final Configurator theConfigG, @Nullable final String fil initializeOutputTypes(this.filterConfig); } - private final void loadFilterCondition(final Configurator parentConfig) { + private void loadFilterCondition(final Configurator parentConfig) { this.filterConditionSpec = parentConfig.findStringEntry("FILTER_CONDITION_" + getFilterName(), null); // format FILTER_CONDITION_ = profilename:clazz just like dropoff filter config @@ -169,6 +175,7 @@ private final void loadFilterCondition(final Configurator parentConfig) { */ protected void initializeOutputTypes(@Nullable final Configurator config) { if (config != null) { + this.loadNameValidationPatterns(config); this.outputTypes = config.findEntriesAsSet("OUTPUT_TYPE"); this.logger.debug("Loaded {} output types for filter {}", this.outputTypes.size(), this.outputTypes); this.initializeDenylist(config); @@ -177,36 +184,34 @@ protected void initializeOutputTypes(@Nullable final Configurator config) { } } - protected void initializeDenylist(final Configurator config) { - Pattern charSetOrdering = Pattern.compile("^[\\w*]+(\\.[\\w*]+)*$"); // Match if acceptable characters are in correct order - Pattern viewNameFormat = Pattern.compile("^\\w+(\\.\\w+)?\\*?$"); // Match if String is word sequence with optional `*` suffix + protected void loadNameValidationPatterns(final Configurator config) { + denylistAllowedNameChars = config.findStringEntry("DENYLIST_ALLOWED_NAME_CHARS", denylistAllowedNameChars); + denylistFiletypeFormat = config.findStringEntry("DENYLIST_FILETYPE_FORMAT", denylistFiletypeFormat); + denylistFiletypeFormatPattern = Pattern.compile(denylistFiletypeFormat.replace("%s", denylistAllowedNameChars)); + denylistViewNameFormat = config.findStringEntry("DENYLIST_VIEW_NAME_FORMAT", denylistViewNameFormat); + denylistViewNameFormatPattern = Pattern.compile(denylistViewNameFormat.replace("%s", denylistAllowedNameChars)); + } + protected void initializeDenylist(final Configurator config) { for (String entry : config.findEntriesAsSet("DENYLIST")) { - if (charSetOrdering.matcher(entry).matches()) { - String viewName = validateAndRemoveDenylistFiletype(entry); - + String viewName = validateAndRemoveDenylistFiletype(entry); + if (matchesDenylistViewNameFormatPattern(viewName)) { if (viewName.chars().filter(ch -> ch == '.').count() > 0) { logger.warn("`DENYLIST = \"{}\"` viewName `{}` should not contain any `.` characters", entry, viewName); } - if (viewNameFormat.matcher(viewName).matches()) { - if (viewName.endsWith("*")) { - String strippedEntry = entry.substring(0, entry.length() - 1); - this.wildCardDenylist.add(strippedEntry); - } else { - this.denylist.add(entry); - } + + if (viewName.endsWith("*")) { + String strippedEntry = entry.substring(0, entry.length() - 1); + this.wildCardDenylist.add(strippedEntry); } else { - throw new EmissaryRuntimeException(String.format( - "Invalid filter configuration: `DENYLIST = \"%s\"` " + - "viewName `%s` must be a sequence of [A-Z, a-z, 0-9, _] with optional wildcard `*` suffix.", - entry, viewName)); + this.denylist.add(entry); } } else { throw new EmissaryRuntimeException(String.format( "Invalid filter configuration: `DENYLIST = \"%s\"` " + - "must be one sequence of [A-Z, a-z, 0-9, _] or two sequences separated with `.` delimiter.", - entry)); + "entry `%s` must match pattern `%s`.", + entry, entry, getDenylistViewNameFormat())); } } @@ -226,11 +231,11 @@ protected String validateAndRemoveDenylistFiletype(final String entry) { "Invalid filter configuration: `DENYLIST = \"%s\"` " + "wildcarded filetypes not allowed in denylist - Did you mean `DENYLIST = \"%s\"`?", entry, viewName)); - } else if (!filetype.chars().allMatch(ch -> Character.isLetterOrDigit(ch) || ch == '_')) { // DENYLIST = "*." not allowed + } else if (!matchesDenylistFiletypeFormatPattern(filetype)) { throw new EmissaryRuntimeException(String.format( "Invalid filter configuration: `DENYLIST = \"%s\"` " + - "filetype `%s` must be a sequence of [A-Z, a-z, 0-9, _]", - entry, filetype)); + "filetype `%s` must match pattern `%s`", + entry, filetype, getDenylistFiletypeFormat())); } return viewName; } @@ -573,4 +578,20 @@ public String getErrorSpec() { public Collection getOutputTypes() { return new HashSet<>(this.outputTypes); } + + public boolean matchesDenylistFiletypeFormatPattern(String str) { + return denylistFiletypeFormatPattern.matcher(str).matches(); + } + + public String getDenylistFiletypeFormat() { + return denylistFiletypeFormatPattern.pattern(); + } + + public boolean matchesDenylistViewNameFormatPattern(String str) { + return denylistViewNameFormatPattern.matcher(str).matches(); + } + + public String getDenylistViewNameFormat() { + return denylistViewNameFormatPattern.pattern(); + } } diff --git a/src/test/java/emissary/output/filter/AbstractFilterTest.java b/src/test/java/emissary/output/filter/AbstractFilterTest.java index 0b3e6efb61..64c4b92ba4 100644 --- a/src/test/java/emissary/output/filter/AbstractFilterTest.java +++ b/src/test/java/emissary/output/filter/AbstractFilterTest.java @@ -7,13 +7,16 @@ import emissary.core.IBaseDataObject; import emissary.test.core.junit5.UnitTest; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -48,6 +51,75 @@ IBaseDataObject getTestPayload(final String filetype, final List altView return payload; } + @Nested + class DefaultRegexPatternTests { + AbstractFilter f = getDenylistFilter(Collections.emptyList()); + + @Test + void testDefaultDenylistFiletypeFormat() { + String pattern = f.getDenylistFiletypeFormat(); + assertEquals("^[a-zA-Z0-9_\\-]+$", pattern); + + assertFalse(f.matchesDenylistFiletypeFormatPattern(""), + String.format("Unexpected match [empty string] for Pattern %s", pattern)); + + String formatString = "Fi1e%sTyp3"; + + String noMatchChars = " !@#$%^&*()+=\\/.,"; + for (char noMatchChar : noMatchChars.toCharArray()) { + String noMatch = String.format(formatString, noMatchChar); + assertFalse(f.matchesDenylistFiletypeFormatPattern(noMatch), + String.format("Unexpected match %s for Pattern %s", noMatch, pattern)); + } + + String nullInsert = String.format(formatString, ""); + assertTrue(f.matchesDenylistFiletypeFormatPattern(nullInsert), + String.format("Expected match %s for Pattern %s", nullInsert, pattern)); + + String matchChars = "-_"; + for (char matchChar : matchChars.toCharArray()) { + String match = String.format(formatString, matchChar); + assertTrue(f.matchesDenylistFiletypeFormatPattern(match), + String.format("Expected match %s for Pattern %s", match, pattern)); + } + } + + @Test + void testDefaultDenylistViewNameFormat() { + String pattern = f.getDenylistViewNameFormat(); + assertEquals("^[a-zA-Z0-9_\\-]+(\\.[a-zA-Z0-9_\\-]+)?\\*?$", pattern); + + assertFalse(f.matchesDenylistViewNameFormatPattern(""), + String.format("Unexpected match [empty string] for Pattern %s", pattern)); + + assertFalse(f.matchesDenylistViewNameFormatPattern("pt1.PT2.pt3"), + String.format("Unexpected match pt1.PT2.pt3 for Pattern %s", pattern)); + + List formatStrings = Arrays.asList("view%sN4ME", "view%sN4ME*", "pt1.PT2%spt3", "pt1.PT2%spt3*"); + for (String formatString : formatStrings) { + + String noMatchChars = " !@#$%^&*()+=\\/,"; + for (char noMatchChar : noMatchChars.toCharArray()) { + String noMatch = String.format(formatString, noMatchChar); + assertFalse(f.matchesDenylistViewNameFormatPattern(noMatch), + String.format("Unexpected match %s for Pattern %s", noMatch, pattern)); + } + + String nullInsert = String.format(formatString, ""); + assertTrue(f.matchesDenylistViewNameFormatPattern(nullInsert), + String.format("Expected match %s for Pattern %s", nullInsert, pattern)); + + String matchChars = "-_"; + for (char matchChar : matchChars.toCharArray()) { + String match = String.format(formatString, matchChar); + assertTrue(f.matchesDenylistViewNameFormatPattern(match), + String.format("Expected match %s for Pattern %s", match, pattern)); + } + } + } + + } + @Test void testIncorrectConfigs() { List invalidEntries = Arrays.asList(