Skip to content

Commit

Permalink
Made AbstractFilter Denylist settings configurable (NationalSecurityA…
Browse files Browse the repository at this point in the history
  • Loading branch information
rupert-griffin authored Sep 17, 2024
1 parent 36623d1 commit 11b64f6
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 23 deletions.
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

0 comments on commit 11b64f6

Please sign in to comment.