    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.rememberme;
    17
    18  import junit.framework.TestCase;
    19
    20  import org.acegisecurity.Authentication;
    21  import org.acegisecurity.GrantedAuthority;
    22  import org.acegisecurity.GrantedAuthorityImpl;
    23
    24
    25  import org.acegisecurity.providers.TestingAuthenticationToken;
    26  import org.acegisecurity.userdetails.UserDetailsService;
    27  import org.acegisecurity.userdetails.User;
    28  import org.acegisecurity.userdetails.UserDetails;
    29  import org.acegisecurity.userdetails.UsernameNotFoundException;
    30
    31  import org.apache.commons.codec.binary.Base64;
    32  import org.apache.commons.codec.digest.DigestUtils;
    33
    34  import org.springframework.dao.DataAccessException;
    35
    36  import org.springframework.util.StringUtils;
    37  import org.springframework.mock.web.MockHttpServletRequest;
    38  import org.springframework.mock.web.MockHttpServletResponse;
    39
    40  import java.util.Date;
    41
    42  import javax.servlet.http.Cookie;
    43
    44
    45  /***
    46   * Tests {@link
    47   * org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices}.
    48   *
    49   * @author Ben Alex
    50   * @version $Id: TokenBasedRememberMeServicesTests.java,v 1.6 2005/11/30 00:20:13 benalex Exp $
    51   */
    52  public class TokenBasedRememberMeServicesTests extends TestCase {
    53      //~ Constructors ===========================================================
    54
    55      public TokenBasedRememberMeServicesTests() {
    56          super();
    57      }
    58
    59      public TokenBasedRememberMeServicesTests(String arg0) {
    60          super(arg0);
    61      }
    62
    63      //~ Methods ================================================================
    64
    65      public static void main(String[] args) {
    66          junit.textui.TestRunner.run(TokenBasedRememberMeServicesTests.class);
    67      }
    68
    69      public void testAutoLoginIfDoesNotPresentAnyCookies()
    70          throws Exception {
    71          TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    72          services.setKey("key");
    73          services.setUserDetailsService(new MockAuthenticationDao(null, true));
    74          services.afterPropertiesSet();
    75
    76          MockHttpServletRequest request = new MockHttpServletRequest();
    77          request.setRequestURI("dc");
    78          MockHttpServletResponse response = new MockHttpServletResponse();
    79
    80          Authentication result = services.autoLogin(request, response);
    81
    82          assertNull(result);
    83
    84          Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    85          assertNull(returnedCookie); // shouldn't try to invalidate our cookie
    86      }
    87
    88      public void testAutoLoginIfDoesNotPresentRequiredCookie()
    89          throws Exception {
    90          TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    91          services.setKey("key");
    92          services.setUserDetailsService(new MockAuthenticationDao(null, true));
    93          services.afterPropertiesSet();
    94
    95          Cookie cookie = new Cookie("unrelated_cookie", "foobar");
    96          MockHttpServletRequest request = new MockHttpServletRequest();
    97          request.setCookies(new Cookie[] {cookie});
    98          MockHttpServletResponse response = new MockHttpServletResponse();
    99
    100         Authentication result = services.autoLogin(request, response);
    101
    102         assertNull(result);
    103
    104         Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    105         assertNull(returnedCookie); // shouldn't try to invalidate our cookie
    106     }
    107
    108     public void testAutoLoginIfExpired() throws Exception {
    109         UserDetails user = new User("someone", "password", true, true, true,
    110                 true,
    111                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
    112
    113         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    114         services.setKey("key");
    115         services.setUserDetailsService(new MockAuthenticationDao(user, false));
    116         services.afterPropertiesSet();
    117
    118         Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
    119                 generateCorrectCookieContentForToken(System.currentTimeMillis()
    120                     - 1000000, "someone", "password", "key"));
    121         MockHttpServletRequest request = new MockHttpServletRequest();
    122         request.setCookies(new Cookie[] {cookie});
    123         MockHttpServletResponse response = new MockHttpServletResponse();
    124
    125         Authentication result = services.autoLogin(request, response);
    126
    127         assertNull(result);
    128
    129         Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    130         assertNotNull(returnedCookie);
    131         assertEquals(0, returnedCookie.getMaxAge());
    132     }
    133
    134     public void testAutoLoginIfMissingThreeTokensInCookieValue()
    135         throws Exception {
    136         UserDetails user = new User("someone", "password", true, true, true,
    137                 true,
    138                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
    139
    140         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    141         services.setKey("key");
    142         services.setUserDetailsService(new MockAuthenticationDao(user, false));
    143         services.afterPropertiesSet();
    144
    145         Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
    146                 new String(Base64.encodeBase64("x".getBytes())));
    147         MockHttpServletRequest request = new MockHttpServletRequest();
    148         request.setCookies(new Cookie[] {cookie});
    149         MockHttpServletResponse response = new MockHttpServletResponse();
    150
    151         Authentication result = services.autoLogin(request, response);
    152
    153         assertNull(result);
    154
    155         Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    156         assertNotNull(returnedCookie);
    157         assertEquals(0, returnedCookie.getMaxAge());
    158     }
    159
    160     public void testAutoLoginIfNotBase64Encoded() throws Exception {
    161         UserDetails user = new User("someone", "password", true, true, true,
    162                 true,
    163                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
    164
    165         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    166         services.setKey("key");
    167         services.setUserDetailsService(new MockAuthenticationDao(user, false));
    168         services.afterPropertiesSet();
    169
    170         Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
    171                 "NOT_BASE_64_ENCODED");
    172         MockHttpServletRequest request = new MockHttpServletRequest();
    173         request.setCookies(new Cookie[] {cookie});
    174         MockHttpServletResponse response = new MockHttpServletResponse();
    175
    176         Authentication result = services.autoLogin(request, response);
    177
    178         assertNull(result);
    179
    180         Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    181         assertNotNull(returnedCookie);
    182         assertEquals(0, returnedCookie.getMaxAge());
    183     }
    184
    185     public void testAutoLoginIfSignatureBlocksDoesNotMatchExpectedValue()
    186         throws Exception {
    187         UserDetails user = new User("someone", "password", true, true, true,
    188                 true,
    189                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
    190
    191         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    192         services.setKey("key");
    193         services.setUserDetailsService(new MockAuthenticationDao(user, false));
    194         services.afterPropertiesSet();
    195
    196         Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
    197                 generateCorrectCookieContentForToken(System.currentTimeMillis()
    198                     + 1000000, "someone", "password", "WRONG_KEY"));
    199         MockHttpServletRequest request = new MockHttpServletRequest();
    200         request.setCookies(new Cookie[] {cookie});
    201         MockHttpServletResponse response = new MockHttpServletResponse();
    202
    203         Authentication result = services.autoLogin(request, response);
    204
    205         assertNull(result);
    206
    207         Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    208         assertNotNull(returnedCookie);
    209         assertEquals(0, returnedCookie.getMaxAge());
    210     }
    211
    212     public void testAutoLoginIfTokenDoesNotContainANumberInCookieValue()
    213         throws Exception {
    214         UserDetails user = new User("someone", "password", true, true, true,
    215                 true,
    216                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
    217
    218         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    219         services.setKey("key");
    220         services.setUserDetailsService(new MockAuthenticationDao(user, false));
    221         services.afterPropertiesSet();
    222
    223         Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
    224                 new String(Base64.encodeBase64(
    225                         "username:NOT_A_NUMBER:signature".getBytes())));
    226         MockHttpServletRequest request = new MockHttpServletRequest();
    227         request.setCookies(new Cookie[] {cookie});
    228         MockHttpServletResponse response = new MockHttpServletResponse();
    229
    230         Authentication result = services.autoLogin(request, response);
    231
    232         assertNull(result);
    233
    234         Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    235         assertNotNull(returnedCookie);
    236         assertEquals(0, returnedCookie.getMaxAge());
    237     }
    238
    239     public void testAutoLoginIfUserNotFound() throws Exception {
    240         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    241         services.setKey("key");
    242         services.setUserDetailsService(new MockAuthenticationDao(null, true));
    243         services.afterPropertiesSet();
    244
    245         Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
    246                 generateCorrectCookieContentForToken(System.currentTimeMillis()
    247                     + 1000000, "someone", "password", "key"));
    248         MockHttpServletRequest request = new MockHttpServletRequest();
    249         request.setCookies(new Cookie[] {cookie});
    250         MockHttpServletResponse response = new MockHttpServletResponse();
    251
    252         Authentication result = services.autoLogin(request, response);
    253
    254         assertNull(result);
    255
    256         Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    257         assertNotNull(returnedCookie);
    258         assertEquals(0, returnedCookie.getMaxAge());
    259     }
    260
    261     public void testAutoLoginWithValidToken() throws Exception {
    262         UserDetails user = new User("someone", "password", true, true, true,
    263                 true,
    264                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
    265
    266         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    267         services.setKey("key");
    268         services.setUserDetailsService(new MockAuthenticationDao(user, false));
    269         services.afterPropertiesSet();
    270
    271         Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
    272                 generateCorrectCookieContentForToken(System.currentTimeMillis()
    273                     + 1000000, "someone", "password", "key"));
    274         MockHttpServletRequest request = new MockHttpServletRequest();
    275         request.setCookies(new Cookie[] {cookie});
    276         MockHttpServletResponse response = new MockHttpServletResponse();
    277
    278         Authentication result = services.autoLogin(request, response);
    279
    280         assertNotNull(result);
    281
    282         UserDetails resultingUserDetails = (UserDetails) result.getPrincipal();
    283
    284         assertEquals(user, resultingUserDetails);
    285     }
    286
    287     public void testGettersSetters() {
    288         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    289         services.setUserDetailsService(new MockAuthenticationDao(null, false));
    290         assertTrue(services.getUserDetailsService() != null);
    291
    292         services.setKey("d");
    293         assertEquals("d", services.getKey());
    294
    295         assertEquals(TokenBasedRememberMeServices.DEFAULT_PARAMETER,
    296             services.getParameter());
    297         services.setParameter("some_param");
    298         assertEquals("some_param", services.getParameter());
    299
    300         services.setTokenValiditySeconds(12);
    301         assertEquals(12, services.getTokenValiditySeconds());
    302     }
    303
    304     public void testLoginFail() {
    305         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    306         MockHttpServletRequest request = new MockHttpServletRequest();
    307         request.setRequestURI("fv");
    308         MockHttpServletResponse response = new MockHttpServletResponse();
    309         services.loginFail(request, response);
    310
    311         Cookie cookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    312         assertNotNull(cookie);
    313         assertEquals(0, cookie.getMaxAge());
    314     }
    315
    316     public void testLoginSuccessIgnoredIfParameterNotSetOrFalse() {
    317         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    318         MockHttpServletRequest request = new MockHttpServletRequest();
    319         request.setRequestURI("d");
    320         request.addParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER,
    321             "false");
    322
    323         MockHttpServletResponse response = new MockHttpServletResponse();
    324         services.loginSuccess(request, response,
    325             new TestingAuthenticationToken("someone", "password",
    326                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}));
    327
    328         Cookie cookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    329         assertNull(cookie);
    330     }
    331
    332     public void testLoginSuccessNormalWithNonUserDetailsBasedPrincipal() {
    333         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    334         MockHttpServletRequest request = new MockHttpServletRequest();
    335         request.setRequestURI("d");
    336         request.addParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER, "true");
    337
    338         MockHttpServletResponse response = new MockHttpServletResponse();
    339         services.loginSuccess(request, response,
    340             new TestingAuthenticationToken("someone", "password",
    341                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}));
    342
    343         Cookie cookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    344         assertNotNull(cookie);
    345         assertEquals(60 * 60 * 24 * 365 * 5, cookie.getMaxAge()); // 5 years
    346         assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes()));
    347         assertTrue(new Date().before(
    348                 new Date(determineExpiryTimeFromBased64EncodedToken(
    349                         cookie.getValue()))));
    350     }
    351
    352     public void testLoginSuccessNormalWithUserDetailsBasedPrincipal() {
    353         TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
    354         MockHttpServletRequest request = new MockHttpServletRequest();
    355         request.setRequestURI("d");
    356         request.addParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER, "true");
    357
    358         MockHttpServletResponse response = new MockHttpServletResponse();
    359         UserDetails user = new User("someone", "password", true, true, true,
    360                 true,
    361                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
    362         services.loginSuccess(request, response,
    363             new TestingAuthenticationToken(user, "ignored",
    364                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}));
    365
    366         Cookie cookie = response.getCookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
    367         assertNotNull(cookie);
    368         assertEquals(60 * 60 * 24 * 365 * 5, cookie.getMaxAge()); // 5 years
    369         assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes()));
    370         assertTrue(new Date().before(
    371                 new Date(determineExpiryTimeFromBased64EncodedToken(
    372                         cookie.getValue()))));
    373     }
    374
    375     private long determineExpiryTimeFromBased64EncodedToken(String validToken) {
    376         String cookieAsPlainText = new String(Base64.decodeBase64(
    377                     validToken.getBytes()));
    378         String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText,
    379                 ":");
    380
    381         if (cookieTokens.length == 3) {
    382             try {
    383                 return new Long(cookieTokens[1]).longValue();
    384             } catch (NumberFormatException nfe) {}
    385         }
    386
    387         return -1;
    388     }
    389
    390     private String generateCorrectCookieContentForToken(long expiryTime,
    391         String username, String password, String key) {
    392         // format is:
    393         //     username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
    394         String signatureValue = new String(DigestUtils.md5Hex(username + ":"
    395                     + expiryTime + ":" + password + ":" + key));
    396         String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
    397         String tokenValueBase64 = new String(Base64.encodeBase64(
    398                     tokenValue.getBytes()));
    399
    400         return tokenValueBase64;
    401     }
    402
    403     //~ Inner Classes ==========================================================
    404
    405     private class MockAuthenticationDao implements UserDetailsService {
    406         private UserDetails toReturn;
    407         private boolean throwException;
    408
    409         public MockAuthenticationDao(UserDetails toReturn,
    410             boolean throwException) {
    411             this.toReturn = toReturn;
    412             this.throwException = throwException;
    413         }
    414
    415         public UserDetails loadUserByUsername(String username)
    416             throws UsernameNotFoundException, DataAccessException {
    417             if (throwException) {
    418                 throw new UsernameNotFoundException("as requested by mock");
    419             }
    420
    421             return toReturn;
    422         }
    423     }
    424 }