1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.ui;
17  
18  import junit.framework.TestCase;
19  import org.acegisecurity.AccountExpiredException;
20  import org.acegisecurity.Authentication;
21  import org.acegisecurity.AuthenticationException;
22  import org.acegisecurity.BadCredentialsException;
23  import org.acegisecurity.GrantedAuthority;
24  import org.acegisecurity.GrantedAuthorityImpl;
25  import org.acegisecurity.MockAuthenticationManager;
26  import org.acegisecurity.context.SecurityContextHolder;
27  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
28  import org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices;
29  import org.acegisecurity.ui.savedrequest.SavedRequest;
30  import org.acegisecurity.util.PortResolverImpl;
31  import org.springframework.mock.web.MockFilterConfig;
32  import org.springframework.mock.web.MockHttpServletRequest;
33  import org.springframework.mock.web.MockHttpServletResponse;
34  
35  import javax.servlet.Filter;
36  import javax.servlet.FilterChain;
37  import javax.servlet.FilterConfig;
38  import javax.servlet.ServletException;
39  import javax.servlet.ServletRequest;
40  import javax.servlet.ServletResponse;
41  import javax.servlet.http.HttpServletRequest;
42  import javax.servlet.http.HttpServletResponse;
43  import java.io.IOException;
44  import java.util.Properties;
45  
46  
47  /**
48   * Tests {@link AbstractProcessingFilter}.
49   *
50   * @author Ben Alex
51   * @version $Id: AbstractProcessingFilterTests.java 1861 2007-05-25 01:24:07Z benalex $
52   */
53  public class AbstractProcessingFilterTests extends TestCase {
54      //~ Constructors ===================================================================================================
55  
56      public AbstractProcessingFilterTests() {
57          super();
58      }
59  
60      public AbstractProcessingFilterTests(String arg0) {
61          super(arg0);
62      }
63  
64      //~ Methods ========================================================================================================
65  
66      private MockHttpServletRequest createMockRequest() {
67          MockHttpServletRequest request = new MockHttpServletRequest();
68  
69          request.setServletPath("/j_mock_post");
70          request.setScheme("http");
71          request.setServerName("www.example.com");
72          request.setRequestURI("/mycontext/j_mock_post");
73          request.setContextPath("/mycontext");
74  
75          return request;
76      }
77  
78      private void executeFilterInContainerSimulator(FilterConfig filterConfig, Filter filter, ServletRequest request,
79          ServletResponse response, FilterChain filterChain)
80          throws ServletException, IOException {
81          filter.init(filterConfig);
82          filter.doFilter(request, response, filterChain);
83          filter.destroy();
84      }
85  
86      public static void main(String[] args) {
87          junit.textui.TestRunner.run(AbstractProcessingFilterTests.class);
88      }
89  
90      private SavedRequest makeSavedRequestForUrl() {
91          MockHttpServletRequest request = createMockRequest();
92          request.setServletPath("/some_protected_file.html");
93          request.setScheme("http");
94          request.setServerName("www.example.com");
95          request.setRequestURI("/mycontext/some_protected_file.html");
96  
97          return new SavedRequest(request, new PortResolverImpl());
98      }
99  
100     protected void setUp() throws Exception {
101         super.setUp();
102         SecurityContextHolder.clearContext();
103     }
104 
105     protected void tearDown() throws Exception {
106         super.tearDown();
107         SecurityContextHolder.clearContext();
108     }
109 
110     public void testDefaultProcessesFilterUrlWithPathParameter() {
111         MockHttpServletRequest request = createMockRequest();
112         MockHttpServletResponse response = new MockHttpServletResponse();
113         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter();
114         filter.setFilterProcessesUrl("/j_acegi_security_check");
115 
116         request.setRequestURI("/mycontext/j_acegi_security_check;jsessionid=I8MIONOSTHOR");
117         assertTrue(filter.requiresAuthentication(request, response));
118     }
119 
120     public void testDoFilterWithNonHttpServletRequestDetected()
121         throws Exception {
122         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
123 
124         try {
125             filter.doFilter(null, new MockHttpServletResponse(), new MockFilterChain());
126             fail("Should have thrown ServletException");
127         } catch (ServletException expected) {
128             assertEquals("Can only process HttpServletRequest", expected.getMessage());
129         }
130     }
131 
132     public void testDoFilterWithNonHttpServletResponseDetected()
133         throws Exception {
134         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
135 
136         try {
137             filter.doFilter(new MockHttpServletRequest(null, null), null, new MockFilterChain());
138             fail("Should have thrown ServletException");
139         } catch (ServletException expected) {
140             assertEquals("Can only process HttpServletResponse", expected.getMessage());
141         }
142     }
143 
144     public void testFailedAuthenticationRedirectsAppropriately()
145         throws Exception {
146         // Setup our HTTP request
147         MockHttpServletRequest request = createMockRequest();
148 
149         // Setup our filter configuration
150         MockFilterConfig config = new MockFilterConfig(null, null);
151 
152         // Setup our expectation that the filter chain will not be invoked, as we redirect to authenticationFailureUrl
153         MockFilterChain chain = new MockFilterChain(false);
154         MockHttpServletResponse response = new MockHttpServletResponse();
155 
156         // Setup our test object, to deny access
157         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(false);
158         filter.setAuthenticationFailureUrl("/failed.jsp");
159 
160         // Test
161         executeFilterInContainerSimulator(config, filter, request, response, chain);
162 
163         assertEquals("/mycontext/failed.jsp", response.getRedirectedUrl());
164         assertNull(SecurityContextHolder.getContext().getAuthentication());
165 
166         //Prepare again, this time using the exception mapping
167         filter = new MockAbstractProcessingFilter(new AccountExpiredException("You're account is expired"));
168         filter.setAuthenticationFailureUrl("/failed.jsp");
169 
170         Properties exceptionMappings = filter.getExceptionMappings();
171         exceptionMappings.setProperty(AccountExpiredException.class.getName(), "/accountExpired.jsp");
172         filter.setExceptionMappings(exceptionMappings);
173         response = new MockHttpServletResponse();
174 
175         // Test
176         executeFilterInContainerSimulator(config, filter, request, response, chain);
177 
178         assertEquals("/mycontext/accountExpired.jsp", response.getRedirectedUrl());
179         assertNull(SecurityContextHolder.getContext().getAuthentication());
180         assertEquals(8*1024, response.getBufferSize());
181     }
182 
183     public void testFilterProcessesUrlVariationsRespected()
184         throws Exception {
185         // Setup our HTTP request
186         MockHttpServletRequest request = createMockRequest();
187         request.setServletPath("/j_OTHER_LOCATION");
188         request.setRequestURI("/mycontext/j_OTHER_LOCATION");
189 
190         // Setup our filter configuration
191         MockFilterConfig config = new MockFilterConfig(null, null);
192 
193         // Setup our expectation that the filter chain will not be invoked, as we redirect to defaultTargetUrl
194         MockFilterChain chain = new MockFilterChain(false);
195         MockHttpServletResponse response = new MockHttpServletResponse();
196 
197         // Setup our test object, to grant access
198         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
199         filter.setFilterProcessesUrl("/j_OTHER_LOCATION");
200         filter.setDefaultTargetUrl("/logged_in.jsp");
201 
202         // Test
203         executeFilterInContainerSimulator(config, filter, request, response, chain);
204         assertEquals("/mycontext/logged_in.jsp", response.getRedirectedUrl());
205         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
206         assertEquals("test", SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());
207         assertEquals(8*1024, response.getBufferSize());
208     }
209 
210     public void testGettersSetters() {
211         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
212         assertNotNull(filter.getRememberMeServices());
213         filter.setRememberMeServices(new TokenBasedRememberMeServices());
214         assertEquals(TokenBasedRememberMeServices.class, filter.getRememberMeServices().getClass());
215 
216         filter.setAuthenticationFailureUrl("/x");
217         assertEquals("/x", filter.getAuthenticationFailureUrl());
218 
219         filter.setAuthenticationManager(new MockAuthenticationManager());
220         assertTrue(filter.getAuthenticationManager() != null);
221 
222         filter.setDefaultTargetUrl("/default");
223         assertEquals("/default", filter.getDefaultTargetUrl());
224 
225         filter.setFilterProcessesUrl("/p");
226         assertEquals("/p", filter.getFilterProcessesUrl());
227 
228         filter.setAuthenticationFailureUrl("/fail");
229         assertEquals("/fail", filter.getAuthenticationFailureUrl());
230     }
231 
232     public void testDefaultUrlMuststartWithSlashOrHttpScheme() {
233         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
234 
235         filter.setDefaultTargetUrl("/acceptableRelativeUrl");
236         filter.setDefaultTargetUrl("http://some.site.org/index.html");
237         filter.setDefaultTargetUrl("https://some.site.org/index.html");
238 
239         try {
240             filter.setDefaultTargetUrl("missingSlash");
241             fail("Shouldn't accept default target without leading slash");
242         } catch (IllegalArgumentException expected) {}
243     }
244 
245     public void testIgnoresAnyServletPathOtherThanFilterProcessesUrl()
246         throws Exception {
247         // Setup our HTTP request
248         MockHttpServletRequest request = createMockRequest();
249         request.setServletPath("/some.file.html");
250         request.setRequestURI("/mycontext/some.file.html");
251 
252         // Setup our filter configuration
253         MockFilterConfig config = new MockFilterConfig(null, null);
254 
255         // Setup our expectation that the filter chain will be invoked, as our request is for a page the filter isn't monitoring
256         MockFilterChain chain = new MockFilterChain(true);
257         MockHttpServletResponse response = new MockHttpServletResponse();
258 
259         // Setup our test object, to deny access
260         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(false);
261 
262         // Test
263         executeFilterInContainerSimulator(config, filter, request, response, chain);
264     }
265 
266     public void testNormalOperationWithDefaultFilterProcessesUrl()
267         throws Exception {
268         // Setup our HTTP request
269         MockHttpServletRequest request = createMockRequest();
270 
271         // Setup our filter configuration
272         MockFilterConfig config = new MockFilterConfig(null, null);
273 
274         // Setup our expectation that the filter chain will not be invoked, as we redirect to defaultTargetUrl
275         MockFilterChain chain = new MockFilterChain(false);
276         MockHttpServletResponse response = new MockHttpServletResponse();
277 
278         // Setup our test object, to grant access
279         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
280         filter.setFilterProcessesUrl("/j_mock_post");
281         filter.setDefaultTargetUrl("/logged_in.jsp");
282         filter.setAuthenticationFailureUrl("/failure.jsp");
283         filter.setAuthenticationManager(new MockAuthenticationManager(true));
284         filter.afterPropertiesSet();
285 
286         // Test
287         executeFilterInContainerSimulator(config, filter, request, response, chain);
288         assertEquals("/mycontext/logged_in.jsp", response.getRedirectedUrl());
289         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
290         assertEquals("test", SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());
291         assertEquals(8*1024, response.getBufferSize());
292     }
293 
294     public void testStartupDetectsInvalidAuthenticationFailureUrl()
295         throws Exception {
296         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
297         filter.setAuthenticationManager(new MockAuthenticationManager());
298         filter.setDefaultTargetUrl("/");
299         filter.setFilterProcessesUrl("/j_acegi_security_check");
300 
301         try {
302             filter.afterPropertiesSet();
303             fail("Should have thrown IllegalArgumentException");
304         } catch (IllegalArgumentException expected) {
305             assertEquals("authenticationFailureUrl must be specified", expected.getMessage());
306         }
307     }
308 
309     public void testStartupDetectsInvalidAuthenticationManager()
310         throws Exception {
311         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
312         filter.setAuthenticationFailureUrl("/failed.jsp");
313         filter.setDefaultTargetUrl("/");
314         filter.setFilterProcessesUrl("/j_acegi_security_check");
315 
316         try {
317             filter.afterPropertiesSet();
318             fail("Should have thrown IllegalArgumentException");
319         } catch (IllegalArgumentException expected) {
320             assertEquals("authenticationManager must be specified", expected.getMessage());
321         }
322     }
323 
324     public void testStartupDetectsInvalidDefaultTargetUrl()
325         throws Exception {
326         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
327         filter.setAuthenticationFailureUrl("/failed.jsp");
328         filter.setAuthenticationManager(new MockAuthenticationManager());
329         filter.setFilterProcessesUrl("/j_acegi_security_check");
330 
331         try {
332             filter.afterPropertiesSet();
333             fail("Should have thrown IllegalArgumentException");
334         } catch (IllegalArgumentException expected) {
335             assertEquals("defaultTargetUrl must be specified", expected.getMessage());
336         }
337     }
338 
339     public void testStartupDetectsInvalidFilterProcessesUrl()
340         throws Exception {
341         AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
342         filter.setAuthenticationFailureUrl("/failed.jsp");
343         filter.setAuthenticationManager(new MockAuthenticationManager());
344         filter.setDefaultTargetUrl("/");
345         filter.setFilterProcessesUrl(null);
346 
347         try {
348             filter.afterPropertiesSet();
349             fail("Should have thrown IllegalArgumentException");
350         } catch (IllegalArgumentException expected) {
351             assertEquals("filterProcessesUrl must be specified", expected.getMessage());
352         }
353     }
354 
355     public void testSuccessLoginThenFailureLoginResultsInSessionLosingToken()
356         throws Exception {
357         // Setup our HTTP request
358         MockHttpServletRequest request = createMockRequest();
359 
360         // Setup our filter configuration
361         MockFilterConfig config = new MockFilterConfig(null, null);
362 
363         // Setup our expectation that the filter chain will not be invoked, as we redirect to defaultTargetUrl
364         MockFilterChain chain = new MockFilterChain(false);
365         MockHttpServletResponse response = new MockHttpServletResponse();
366 
367         // Setup our test object, to grant access
368         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
369         filter.setFilterProcessesUrl("/j_mock_post");
370         filter.setDefaultTargetUrl("/logged_in.jsp");
371 
372         // Test
373         executeFilterInContainerSimulator(config, filter, request, response, chain);
374         assertEquals("/mycontext/logged_in.jsp", response.getRedirectedUrl());
375         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
376         assertEquals("test", SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());
377         assertEquals(8*1024, response.getBufferSize());
378 
379         // Now try again but this time have filter deny access
380         // Setup our HTTP request
381         // Setup our expectation that the filter chain will not be invoked, as we redirect to authenticationFailureUrl
382         chain = new MockFilterChain(false);
383         response = new MockHttpServletResponse();
384 
385         // Setup our test object, to deny access
386         filter = new MockAbstractProcessingFilter(false);
387         filter.setFilterProcessesUrl("/j_mock_post");
388         filter.setAuthenticationFailureUrl("/failed.jsp");
389 
390         // Test
391         executeFilterInContainerSimulator(config, filter, request, response, chain);
392         assertNull(SecurityContextHolder.getContext().getAuthentication());
393     }
394 
395     public void testSuccessfulAuthenticationButWithAlwaysUseDefaultTargetUrlCausesRedirectToDefaultTargetUrl()
396         throws Exception {
397         // Setup our HTTP request
398         MockHttpServletRequest request = createMockRequest();
399         request.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SAVED_REQUEST_KEY, makeSavedRequestForUrl());
400 
401         // Setup our filter configuration
402         MockFilterConfig config = new MockFilterConfig(null, null);
403 
404         // Setup our expectation that the filter chain will be invoked, as we want to go to the location requested in the session
405         MockFilterChain chain = new MockFilterChain(true);
406         MockHttpServletResponse response = new MockHttpServletResponse();
407 
408         // Setup our test object, to grant access
409         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
410         filter.setFilterProcessesUrl("/j_mock_post");
411         filter.setDefaultTargetUrl("/foobar");
412         assertFalse(filter.isAlwaysUseDefaultTargetUrl()); // check default
413         filter.setAlwaysUseDefaultTargetUrl(true);
414         assertTrue(filter.isAlwaysUseDefaultTargetUrl()); // check changed
415 
416         // Test
417         executeFilterInContainerSimulator(config, filter, request, response, chain);
418         assertEquals("/mycontext/foobar", response.getRedirectedUrl());
419         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
420     }
421 
422     public void testSuccessfulAuthenticationCausesRedirectToSessionSpecifiedUrl()
423         throws Exception {
424         // Setup our HTTP request
425         MockHttpServletRequest request = createMockRequest();
426         request.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SAVED_REQUEST_KEY, makeSavedRequestForUrl());
427 
428         // Setup our filter configuration
429         MockFilterConfig config = new MockFilterConfig(null, null);
430 
431         // Setup our expectation that the filter chain will be invoked, as we want to go to the location requested in the session
432         MockFilterChain chain = new MockFilterChain(true);
433         MockHttpServletResponse response = new MockHttpServletResponse();
434 
435         // Setup our test object, to grant access
436         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
437         filter.setFilterProcessesUrl("/j_mock_post");
438 
439         // Test
440         executeFilterInContainerSimulator(config, filter, request, response, chain);
441         assertEquals(makeSavedRequestForUrl().getFullRequestUrl(), response.getRedirectedUrl());
442         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
443         assertEquals(8*1024, response.getBufferSize());
444     }
445 
446     /**
447      * SEC-297 fix. 
448      */
449     public void testFullDefaultTargetUrlDoesNotHaveContextPathPrepended() throws Exception {
450         MockHttpServletRequest request = createMockRequest();
451         MockFilterConfig config = new MockFilterConfig(null, null);
452 
453         MockFilterChain chain = new MockFilterChain(true);
454         MockHttpServletResponse response = new MockHttpServletResponse();
455 
456         // Setup our test object, to grant access
457         MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
458         filter.setFilterProcessesUrl("/j_mock_post");
459         filter.setDefaultTargetUrl("http://monkeymachine.co.uk/");
460         filter.setAlwaysUseDefaultTargetUrl(true);
461 
462         executeFilterInContainerSimulator(config, filter, request, response, chain);
463         assertEquals("http://monkeymachine.co.uk/", response.getRedirectedUrl());
464         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
465     }
466 
467     //~ Inner Classes ==================================================================================================
468 
469     private class MockAbstractProcessingFilter extends AbstractProcessingFilter {
470         private AuthenticationException exceptionToThrow;
471         private boolean grantAccess;
472 
473         public MockAbstractProcessingFilter(boolean grantAccess) {
474             this.grantAccess = grantAccess;
475             this.exceptionToThrow = new BadCredentialsException("Mock requested to do so");
476         }
477 
478         public MockAbstractProcessingFilter(AuthenticationException exceptionToThrow) {
479             this.grantAccess = false;
480             this.exceptionToThrow = exceptionToThrow;
481         }
482 
483         private MockAbstractProcessingFilter() {
484             super();
485         }
486 
487         public Authentication attemptAuthentication(HttpServletRequest request)
488             throws AuthenticationException {
489             if (grantAccess) {
490                 return new UsernamePasswordAuthenticationToken("test", "test",
491                     new GrantedAuthority[] {new GrantedAuthorityImpl("TEST")});
492             } else {
493                 throw exceptionToThrow;
494             }
495         }
496 
497         public String getDefaultFilterProcessesUrl() {
498             return "/j_mock_post";
499         }
500 
501         public void init(FilterConfig arg0) throws ServletException {}
502 
503         public boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
504             return super.requiresAuthentication(request, response);
505         }
506     }
507 
508     private class MockFilterChain implements FilterChain {
509         private boolean expectToProceed;
510 
511         public MockFilterChain(boolean expectToProceed) {
512             this.expectToProceed = expectToProceed;
513         }
514 
515         private MockFilterChain() {
516             super();
517         }
518 
519         public void doFilter(ServletRequest request, ServletResponse response)
520             throws IOException, ServletException {
521             if (expectToProceed) {
522                 assertTrue(true);
523             } else {
524                 fail("Did not expect filter chain to proceed");
525             }
526         }
527     }
528 }