Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made AbstractFilter Denylist settings configurable #925

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 44 additions & 23 deletions src/main/java/emissary/output/filter/AbstractFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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_<filtername> = profilename:clazz just like dropoff filter config
Expand Down Expand Up @@ -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);
Expand All @@ -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()));
}
}

Expand All @@ -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 = "<type>*.<viewName>" 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;
}
Expand Down Expand Up @@ -573,4 +578,20 @@ public String getErrorSpec() {
public Collection<String> 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();
}
}
72 changes: 72 additions & 0 deletions src/test/java/emissary/output/filter/AbstractFilterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +51,75 @@ IBaseDataObject getTestPayload(final String filetype, final List<String> 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<String> 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<String> invalidEntries = Arrays.asList(
Expand Down