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