    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 }