/* * 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(); } }