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 }