/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.springframework.security.web;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* @author Luke Taylor
* @author Rob Winch
*/
public class FilterChainProxyTests {
private FilterChainProxy fcp;
private RequestMatcher matcher;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private FilterChain chain;
private Filter filter;
@Before
public void setup() throws Exception {
matcher = mock(RequestMatcher.class);
filter = mock(Filter.class);
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock inv) throws Throwable {
Object[] args = inv.getArguments();
FilterChain fc = (FilterChain) args[2];
HttpServletRequestWrapper extraWrapper = new HttpServletRequestWrapper(
(HttpServletRequest) args[0]);
fc.doFilter(extraWrapper, (HttpServletResponse) args[1]);
return null;
}
}).when(filter).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class), any(FilterChain.class));
fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher,
Arrays.asList(filter)));
fcp.setFilterChainValidator(mock(FilterChainProxy.FilterChainValidator.class));
request = new MockHttpServletRequest();
request.setServletPath("/path");
response = new MockHttpServletResponse();
chain = mock(FilterChain.class);
}
@After
public void teardown() {
SecurityContextHolder.clearContext();
}
@Test
public void toStringCallSucceeds() throws Exception {
fcp.afterPropertiesSet();
fcp.toString();
}
@Test
public void securityFilterChainIsNotInvokedIfMatchFails() throws Exception {
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(false);
fcp.doFilter(request, response, chain);
assertThat(fcp.getFilterChains()).hasSize(1);
assertThat(fcp.getFilterChains().get(0).getFilters().get(0)).isSameAs(filter);
verifyZeroInteractions(filter);
// The actual filter chain should be invoked though
verify(chain).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class));
}
@Test
public void originalChainIsInvokedAfterSecurityChainIfMatchSucceeds()
throws Exception {
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
fcp.doFilter(request, response, chain);
verify(filter).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class), any(FilterChain.class));
verify(chain).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class));
}
@Test
public void originalFilterChainIsInvokedIfMatchingSecurityChainIsEmpty()
throws Exception {
List<Filter> noFilters = Collections.emptyList();
fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher, noFilters));
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
fcp.doFilter(request, response, chain);
verify(chain).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class));
}
@Test
public void requestIsWrappedForMatchingAndFilteringWhenMatchIsFound()
throws Exception {
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
fcp.doFilter(request, response, chain);
verify(matcher).matches(any(FirewalledRequest.class));
verify(filter).doFilter(any(FirewalledRequest.class),
any(HttpServletResponse.class), any(FilterChain.class));
verify(chain).doFilter(any(FirewalledRequest.class),
any(HttpServletResponse.class));
}
@Test
public void requestIsWrappedForMatchingAndFilteringWhenMatchIsNotFound()
throws Exception {
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(false);
fcp.doFilter(request, response, chain);
verify(matcher).matches(any(FirewalledRequest.class));
verifyZeroInteractions(filter);
verify(chain).doFilter(any(FirewalledRequest.class),
any(HttpServletResponse.class));
}
@Test
public void wrapperIsResetWhenNoMatchingFilters() throws Exception {
HttpFirewall fw = mock(HttpFirewall.class);
FirewalledRequest fwr = mock(FirewalledRequest.class);
when(fwr.getRequestURI()).thenReturn("/");
when(fwr.getContextPath()).thenReturn("");
fcp.setFirewall(fw);
when(fw.getFirewalledRequest(request)).thenReturn(fwr);
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(false);
fcp.doFilter(request, response, chain);
verify(fwr).reset();
}
// SEC-1639
@Test
public void bothWrappersAreResetWithNestedFcps() throws Exception {
HttpFirewall fw = mock(HttpFirewall.class);
FilterChainProxy firstFcp = new FilterChainProxy(new DefaultSecurityFilterChain(
matcher, fcp));
firstFcp.setFirewall(fw);
fcp.setFirewall(fw);
FirewalledRequest firstFwr = mock(FirewalledRequest.class, "firstFwr");
when(firstFwr.getRequestURI()).thenReturn("/");
when(firstFwr.getContextPath()).thenReturn("");
FirewalledRequest fwr = mock(FirewalledRequest.class, "fwr");
when(fwr.getRequestURI()).thenReturn("/");
when(fwr.getContextPath()).thenReturn("");
when(fw.getFirewalledRequest(request)).thenReturn(firstFwr);
when(fw.getFirewalledRequest(firstFwr)).thenReturn(fwr);
when(fwr.getRequest()).thenReturn(firstFwr);
when(firstFwr.getRequest()).thenReturn(request);
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
firstFcp.doFilter(request, response, chain);
verify(firstFwr).reset();
verify(fwr).reset();
}
@Test
public void doFilterClearsSecurityContextHolder() throws Exception {
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock inv) throws Throwable {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("username", "password"));
return null;
}
}).when(filter).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class), any(FilterChain.class));
fcp.doFilter(request, response, chain);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@Test
public void doFilterClearsSecurityContextHolderWithException() throws Exception {
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock inv) throws Throwable {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("username", "password"));
throw new ServletException("oops");
}
}).when(filter).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class), any(FilterChain.class));
try {
fcp.doFilter(request, response, chain);
fail("Expected Exception");
}
catch (ServletException success) {
}
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
// SEC-2027
@Test
public void doFilterClearsSecurityContextHolderOnceOnForwards() throws Exception {
final FilterChain innerChain = mock(FilterChain.class);
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock inv) throws Throwable {
TestingAuthenticationToken expected = new TestingAuthenticationToken(
"username", "password");
SecurityContextHolder.getContext().setAuthentication(expected);
doAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock inv) throws Throwable {
innerChain.doFilter(request, response);
return null;
}
}).when(filter).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class), any(FilterChain.class));
;
fcp.doFilter(request, response, innerChain);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(expected);
return null;
}
}).when(filter).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class), any(FilterChain.class));
fcp.doFilter(request, response, chain);
verify(innerChain).doFilter(any(HttpServletRequest.class),
any(HttpServletResponse.class));
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
}