/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.security.firewall;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests the {@link org.apereo.portal.security.firewall.RequestParameterPolicyEnforcementFilter}.
*
* <p>There are two kinds of testcases here.
*
* <p>First there are testcases for the Filter as a whole against the Filter API. The advantage of
* these is that they are testing at the level we care about, the way the filter will actually be
* used, against the API it really exposes to adopters. So, great. The disadvantage of these is that
* it's
*
* <p>Then there are testcases for bits of the implementation of the filter (namely, configuration
* parsing and policy enforcement).
*
* @since 4.0.15
*/
public final class RequestParameterPolicyEnforcementFilterTests {
/* ========================================================================================================== */
/* Tests for the Filter as a whole.
*/
/**
* Test that the Filter throws on init when unrecognized Filter init-param.
*
* @throws ServletException on test success.
*/
@Test(expected = ServletException.class)
public void testUnrecognizedInitParamFailsFilterInit() throws ServletException {
final Set<String> initParameterNames = new HashSet<String>();
initParameterNames.add("unrecognizedInitParameterName");
final Enumeration parameterNamesEnumeration = Collections.enumeration(initParameterNames);
final FilterConfig filterConfig = mock(FilterConfig.class);
when(filterConfig.getInitParameterNames()).thenReturn(parameterNamesEnumeration);
final RequestParameterPolicyEnforcementFilter filter =
new RequestParameterPolicyEnforcementFilter();
filter.init(filterConfig);
}
/**
* Test that if you configure the filter to forbid no characters and also to allow multi-valued
* parameters, filter init fails because the filter would be a no-op.
*/
@Test(expected = ServletException.class)
public void testNoOpConfigurationFailsFilterInit() throws ServletException {
final RequestParameterPolicyEnforcementFilter filter =
new RequestParameterPolicyEnforcementFilter();
// mock up filter config.
final Set<String> initParameterNames = new HashSet<String>();
initParameterNames.add(
RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS);
initParameterNames.add(RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID);
final Enumeration parameterNamesEnumeration = Collections.enumeration(initParameterNames);
final FilterConfig filterConfig = mock(FilterConfig.class);
when(filterConfig.getInitParameterNames()).thenReturn(parameterNamesEnumeration);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS))
.thenReturn("true");
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID))
.thenReturn("none");
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK))
.thenReturn(null);
filter.init(filterConfig);
}
/**
* Test that, in the default configuration, when presented with a multi-valued parameter that
* configured to check and configured to require not multi valued, rejects request.
*/
@Test(expected = ServletException.class)
public void testRejectsMultiValuedRequestParameter() throws IOException, ServletException {
final RequestParameterPolicyEnforcementFilter filter =
new RequestParameterPolicyEnforcementFilter();
// mock up filter config. Default configuration with no init-params for this use case.
final Set<String> initParameterNames = new HashSet<String>();
final Enumeration parameterNamesEnumeration = Collections.enumeration(initParameterNames);
final FilterConfig filterConfig = mock(FilterConfig.class);
when(filterConfig.getInitParameterNames()).thenReturn(parameterNamesEnumeration);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS))
.thenReturn(null);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID))
.thenReturn(null);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK))
.thenReturn(null);
// init the filter
try {
filter.init(filterConfig);
} catch (Exception e) {
Assert.fail("Should not have failed filter init.");
}
// mock up a request to filter
final Map<String, String[]> requestParameterMap = new HashMap<String, String[]>();
requestParameterMap.put("someName", new String[] {"someValue", "someOtherValue"});
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameterMap()).thenReturn(requestParameterMap);
final HttpServletResponse response = mock(HttpServletResponse.class);
final FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
}
/**
* Test that, when configured to allow multi-valued parameters, when presented with a
* multi-valued parameter that configured to check , rejects request.
*/
@Test
public void testAcceptsMultiValuedRequestParameter() throws IOException, ServletException {
final RequestParameterPolicyEnforcementFilter filter =
new RequestParameterPolicyEnforcementFilter();
// mock up filter config. Configure to allow multi-valued parameters.
final Set<String> initParameterNames = new HashSet<String>();
initParameterNames.add(
RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS);
final Enumeration parameterNamesEnumeration = Collections.enumeration(initParameterNames);
final FilterConfig filterConfig = mock(FilterConfig.class);
when(filterConfig.getInitParameterNames()).thenReturn(parameterNamesEnumeration);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS))
.thenReturn("true");
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID))
.thenReturn(null);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK))
.thenReturn(null);
// init the filter
try {
filter.init(filterConfig);
} catch (Exception e) {
Assert.fail("Should not have failed filter init.");
}
// mock up a request to filter, with a multi-valued checked parameter
final Map<String, String[]> requestParameterMap = new HashMap<String, String[]>();
requestParameterMap.put("someName", new String[] {"someValue", "someOtherValue"});
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameterMap()).thenReturn(requestParameterMap);
final HttpServletResponse response = mock(HttpServletResponse.class);
final FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
}
/**
* Test that, in the default configuration, when presented with a request parameter with an
* illicit character in it, blocks the request.
*
* @throws IOException
* @throws ServletException
*/
@Test(expected = ServletException.class)
public void testRejectsRequestWithIllicitCharacterInCheckedParameter()
throws IOException, ServletException {
final RequestParameterPolicyEnforcementFilter filter =
new RequestParameterPolicyEnforcementFilter();
// mock up filter config. Default configuration with no init-params for this use case.
final Set<String> initParameterNames = new HashSet<String>();
final Enumeration parameterNamesEnumeration = Collections.enumeration(initParameterNames);
final FilterConfig filterConfig = mock(FilterConfig.class);
when(filterConfig.getInitParameterNames()).thenReturn(parameterNamesEnumeration);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS))
.thenReturn(null);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID))
.thenReturn(null);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK))
.thenReturn(null);
// init the filter
try {
filter.init(filterConfig);
} catch (Exception e) {
Assert.fail("Should not have failed filter init.");
}
// mock up a request to filter
final Map<String, String[]> requestParameterMap = new HashMap<String, String[]>();
// percent character is illicit by default, so, illicit character in this parameter value
requestParameterMap.put("someName", new String[] {"someValue%40gmail.com"});
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameterMap()).thenReturn(requestParameterMap);
final HttpServletResponse response = mock(HttpServletResponse.class);
final FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
}
/**
* Test that when configured to only check some parameters, does not throw on forbidden
* character in unchecked parameter.
*/
@Test
public void testAllowsUncheckedParametersToHaveIllicitCharacters()
throws IOException, ServletException {
final RequestParameterPolicyEnforcementFilter filter =
new RequestParameterPolicyEnforcementFilter();
// mock up filter config. Default configuration with no init-params for this use case.
final Set<String> initParameterNames = new HashSet<String>();
initParameterNames.add(RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK);
final Enumeration parameterNamesEnumeration = Collections.enumeration(initParameterNames);
final FilterConfig filterConfig = mock(FilterConfig.class);
when(filterConfig.getInitParameterNames()).thenReturn(parameterNamesEnumeration);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS))
.thenReturn(null);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID))
.thenReturn(null);
when(filterConfig.getInitParameter(
RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK))
.thenReturn("ticket");
// init the filter
try {
filter.init(filterConfig);
} catch (Exception e) {
Assert.fail("Should not have failed filter init.");
}
// mock up a request to filter
final Map<String, String[]> requestParameterMap = new HashMap<String, String[]>();
// percent character is illicit by default, so, illicit character in this parameter value
// but this parameter name is unchecked
requestParameterMap.put("uncheckedName", new String[] {"someValue%40gmail.com"});
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameterMap()).thenReturn(requestParameterMap);
final HttpServletResponse response = mock(HttpServletResponse.class);
final FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
}
/* ========================================================================================================== */
/* Tests for throwIfUnrecognizedInitParamNames()
* These test that the fail-safe on unrecognized (and thus un-honored) configuration works as intended.
*/
/**
* Tests that the method checking for unrecognized parameters accepts the expected parameters.
*
* @throws ServletException on test failure
*/
@Test
public void testAcceptsExpectedParameterNames() throws ServletException {
final Set<String> parameterNames = new HashSet<String>();
parameterNames.add(RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID);
parameterNames.add(RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK);
parameterNames.add(RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS);
final Enumeration parameterNamesEnumeration = Collections.enumeration(parameterNames);
RequestParameterPolicyEnforcementFilter.throwIfUnrecognizedParamName(
parameterNamesEnumeration);
}
/**
* Test that the method checking for unrecognized parameters throws on an unrecognized
* parameter.
*
* @throws ServletException on test success
*/
@Test(expected = ServletException.class)
public void testRejectsUnExpectedParameterName() throws ServletException {
final Set<String> parameterNames = new HashSet<String>();
parameterNames.add(RequestParameterPolicyEnforcementFilter.CHARACTERS_TO_FORBID);
parameterNames.add(RequestParameterPolicyEnforcementFilter.PARAMETERS_TO_CHECK);
parameterNames.add(RequestParameterPolicyEnforcementFilter.ALLOW_MULTI_VALUED_PARAMETERS);
parameterNames.add("unexpectedParameterName");
final Enumeration parameterNamesEnumeration = Collections.enumeration(parameterNames);
RequestParameterPolicyEnforcementFilter.throwIfUnrecognizedParamName(
parameterNamesEnumeration);
}
/* ========================================================================================================== */
/* Tests for parseStringToBooleanDefaultingToFalse()
* These test that the boolean init parameter parsing works properly.
*/
/** Test that true parses to true. */
@Test
public void testParsesTrueToTrue() {
Assert.assertTrue(
RequestParameterPolicyEnforcementFilter.parseStringToBooleanDefaultingToFalse(
"true"));
}
/** Test that false parses to false. */
@Test
public void testParsesFalseToFalse() {
Assert.assertFalse(
RequestParameterPolicyEnforcementFilter.parseStringToBooleanDefaultingToFalse(
"false"));
}
/** Test that null parses to false. */
@Test
public void testParsesNullToFalse() {
Assert.assertFalse(
RequestParameterPolicyEnforcementFilter.parseStringToBooleanDefaultingToFalse(
null));
}
/** Test that maybe parses to illegal argument exception. */
@Test(expected = Exception.class)
public void testParsingMaybeYieldsException() {
RequestParameterPolicyEnforcementFilter.parseStringToBooleanDefaultingToFalse("maybe");
}
/* ========================================================================================================== */
/* Tests for parseParametersToCheck().
* Ensure that the Filter properly understands which parameters it ought to be checking.
*/
/** Test that a null parameter value parses to the empty set. */
@Test
public void testParsesNullToEmptySet() {
final Set<String> returnedSet =
RequestParameterPolicyEnforcementFilter.parseParametersToCheck(null);
Assert.assertTrue(returnedSet.isEmpty());
}
/** Test that a whitespace delimited list of parameter names parses as expected. */
@Test
public void testParsesWhiteSpaceDelimitedStringToSet() {
final String parameterValue = "service renew gateway";
final Set<String> expectedSet = new HashSet<String>();
expectedSet.add("service");
expectedSet.add("renew");
expectedSet.add("gateway");
final Set<String> returnedSet =
RequestParameterPolicyEnforcementFilter.parseParametersToCheck(parameterValue);
Assert.assertEquals(expectedSet, returnedSet);
}
/** Test that blank parses to exception. */
@Test(expected = Exception.class)
public void testParsingBlankParametersToCheckThrowsException() {
RequestParameterPolicyEnforcementFilter.parseParametersToCheck(" ");
}
/** Test the special parsing behavior of star parses to empty Set. */
@Test
public void testAsteriskParsesToEmptySetOfParametersToCheck() {
final Set<String> expectedSet = new HashSet<String>();
final Set<String> returnedSet =
RequestParameterPolicyEnforcementFilter.parseParametersToCheck("*");
Assert.assertEquals(expectedSet, returnedSet);
}
/** Test that encountering the star token among other tokens yields exception. */
@Test(expected = Exception.class)
public void testParsingAsteriskWithOtherTokensThrowsException() {
RequestParameterPolicyEnforcementFilter.parseParametersToCheck("renew * gateway");
}
/* ========================================================================================================== */
/* Tests for parseCharactersToForbid().
* Ensure that the Filter properly understands which characters it ought to be forbidding.
*/
/** Test that when the parameter is not set (is null) parses to a default set of character. */
@Test
public void testParsingNullYieldsPercentHashAmpersandAndQuestionMark() {
final Set<Character> expected = new HashSet<Character>();
expected.add('%');
expected.add('#');
expected.add('&');
expected.add('?');
final Set<Character> actual =
RequestParameterPolicyEnforcementFilter.parseCharactersToForbid(null);
Assert.assertEquals(expected, actual);
}
/** Test that when the parameter is set but blank throws an exception. */
@Test(expected = Exception.class)
public void testParsingBlankThrowsException() {
RequestParameterPolicyEnforcementFilter.parseCharactersToForbid(" ");
}
/** Test that when the parameter is set to the special value "none" returns empty Set. */
@Test
public void testParsesLiteralNoneToEmptySet() {
final Set<Character> expected = new HashSet<Character>();
final Set<Character> actual =
RequestParameterPolicyEnforcementFilter.parseCharactersToForbid("none");
Assert.assertEquals(expected, actual);
}
/** Test that parsing some characters works as expected. */
@Test
public void testParsingSomeCharactersWorks() {
final Set<Character> expected = new HashSet<Character>();
expected.add('&');
expected.add('%');
expected.add('*');
expected.add('#');
expected.add('@');
final Set<Character> actual =
RequestParameterPolicyEnforcementFilter.parseCharactersToForbid("& % * # @");
Assert.assertEquals(expected, actual);
}
/**
* The tokens are supposed to be single characters. If they are longer than that, the deployer
* may be confused as to how to configure this filter.
*/
@Test(expected = Exception.class)
public void testParsingMulticharacterTokensThrows() {
RequestParameterPolicyEnforcementFilter.parseCharactersToForbid("& %*# @");
}
/* ========================================================================================================== */
/* Tests for requireNotMultiValued().
* Ensure that runtime enforcement of not-multi-valued-ness work properly.
*/
/** Test that enforcing no-multi-value detects multi-valued parameter and throws. */
@Test(expected = IllegalStateException.class)
public void testRequireNotMultiValueBlocksMultiValue() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("dogName");
// set up a parameter map with a multi-valued parameter
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("dogName", new String[] {"Johan", "Cubby"});
RequestParameterPolicyEnforcementFilter.requireNotMultiValued(
parametersToCheck, parameterMap);
}
/** Test that enforcing no-multi-value allows single-valued parameter. */
@Test
public void testRequireNotMultiValuedAllowsSingleValued() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("dogName");
// set up a parameter map with single-valued parameter
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("dogName", new String[] {"Abbie"});
RequestParameterPolicyEnforcementFilter.requireNotMultiValued(
parametersToCheck, parameterMap);
}
/** Test that enforcing no-multi-value allows not-present parameter. */
@Test
public void testRequireNotMultiValuedIgnoresMissingParameter() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("dogName");
// set up a parameter map with no entries
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
RequestParameterPolicyEnforcementFilter.requireNotMultiValued(
parametersToCheck, parameterMap);
}
/**
* Test that enforcing no-multi-value allows multi-valued parameters not among those to check.
*/
@Test
public void testRequireNotMultiValueAllowsUncheckedMultiValue() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("dogName");
// set up a parameter map with a multi-valued parameter with a name not matching those to check
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("catName", new String[] {"Reggie", "Shenanigans"});
RequestParameterPolicyEnforcementFilter.requireNotMultiValued(
parametersToCheck, parameterMap);
}
/* ========================================================================================================== */
/* Tests for enforceParameterContentCharacterRestrictions().
* Ensure that runtime enforcement of what characters parameters contain works properly.
*/
/** Test that allows parameters not containing forbidden characters. */
@Test
public void testAllowsParametersNotContainingForbiddenCharacters() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("catName");
parametersToCheck.add("dogName");
final Set<Character> charactersToForbid = new HashSet<Character>();
charactersToForbid.add('&');
charactersToForbid.add('%');
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("catName", new String[] {"Reggie", "Shenanigans"});
parameterMap.put("dogName", new String[] {"Brutus", "Johan", "Cubby", "Abbie"});
parameterMap.put("plantName", new String[] {"Rex"});
RequestParameterPolicyEnforcementFilter.enforceParameterContentCharacterRestrictions(
parametersToCheck, charactersToForbid, parameterMap);
}
/** Test that when a checked parameter contains a forbidden character, throws. */
@Test(expected = Exception.class)
public void testThrowsOnParameterContainingForbiddenCharacter() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("catName");
parametersToCheck.add("plantName");
final Set<Character> charactersToForbid = new HashSet<Character>();
charactersToForbid.add('&');
charactersToForbid.add('%');
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("catName", new String[] {"Reggie", "Shenanigans"});
parameterMap.put("dogName", new String[] {"Brutus", "Johan", "Cubby", "Abbie"});
// plantName is checked, and contains a forbidden character
parameterMap.put("plantName", new String[] {"Rex&p0wned=true"});
RequestParameterPolicyEnforcementFilter.enforceParameterContentCharacterRestrictions(
parametersToCheck, charactersToForbid, parameterMap);
}
/**
* Test that when a checked parameter contains a forbidden character in a non-first value, still
* throws.
*/
@Test(expected = Exception.class)
public void testThrowsOnMultipleParameterContainingForbiddenCharacter() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("catName");
parametersToCheck.add("dogName");
final Set<Character> charactersToForbid = new HashSet<Character>();
charactersToForbid.add('!');
charactersToForbid.add('$');
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("catName", new String[] {"Reggie", "Shenanigans"});
// dogName is checked, and contains a forbidden character
parameterMap.put("dogName", new String[] {"Brutus", "Johan", "Cub!by", "Abbie"});
parameterMap.put("plantName", new String[] {"Rex"});
RequestParameterPolicyEnforcementFilter.enforceParameterContentCharacterRestrictions(
parametersToCheck, charactersToForbid, parameterMap);
}
/**
* Test that when an unchecked parameter contains a character that would be forbidden were the
* parameter checked, does not throw.
*/
@Test
public void testAllowsUncheckedParameterContainingForbiddenCharacter() {
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("catName");
parametersToCheck.add("dogName");
final Set<Character> charactersToForbid = new HashSet<Character>();
charactersToForbid.add('&');
charactersToForbid.add('$');
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("catName", new String[] {"Reggie", "Shenanigans"});
parameterMap.put("dogName", new String[] {"Brutus", "Johan", "Cubby", "Abbie"});
// plantName is not checked
parameterMap.put("plantName", new String[] {"Rex&ownage=true"});
RequestParameterPolicyEnforcementFilter.enforceParameterContentCharacterRestrictions(
parametersToCheck, charactersToForbid, parameterMap);
}
/** Test that not all parametersToCheck need be present on the request. */
@Test
public void testAllowsCheckedParameterNotPresent() {
// this test added in response to a stupid NullPointerException defect, to prevent regression.
final Set<String> parametersToCheck = new HashSet<String>();
parametersToCheck.add("catName");
parametersToCheck.add("dogName");
final Set<Character> charactersToForbid = new HashSet<Character>();
charactersToForbid.add('&');
charactersToForbid.add('$');
final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
parameterMap.put("dogName", new String[] {"Brutus", "Johan", "Cubby", "Abbie"});
RequestParameterPolicyEnforcementFilter.enforceParameterContentCharacterRestrictions(
parametersToCheck, charactersToForbid, parameterMap);
}
}