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.providers.dao;
17  
18  import junit.framework.TestCase;
19  
20  import org.acegisecurity.AccountExpiredException;
21  import org.acegisecurity.Authentication;
22  import org.acegisecurity.AuthenticationServiceException;
23  import org.acegisecurity.BadCredentialsException;
24  import org.acegisecurity.CredentialsExpiredException;
25  import org.acegisecurity.DisabledException;
26  import org.acegisecurity.GrantedAuthority;
27  import org.acegisecurity.GrantedAuthorityImpl;
28  import org.acegisecurity.LockedException;
29  
30  import org.acegisecurity.providers.TestingAuthenticationToken;
31  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
32  import org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache;
33  import org.acegisecurity.providers.dao.cache.NullUserCache;
34  import org.acegisecurity.providers.dao.salt.SystemWideSaltSource;
35  import org.acegisecurity.providers.encoding.ShaPasswordEncoder;
36  
37  import org.acegisecurity.userdetails.User;
38  import org.acegisecurity.userdetails.UserDetails;
39  import org.acegisecurity.userdetails.UserDetailsService;
40  import org.acegisecurity.userdetails.UsernameNotFoundException;
41  
42  import org.springframework.dao.DataAccessException;
43  import org.springframework.dao.DataRetrievalFailureException;
44  
45  import java.util.HashMap;
46  import java.util.Map;
47  
48  
49  /**
50   * Tests {@link DaoAuthenticationProvider}.
51   *
52   * @author Ben Alex
53   * @version $Id: DaoAuthenticationProviderTests.java 1857 2007-05-24 00:47:12Z benalex $
54   */
55  public class DaoAuthenticationProviderTests extends TestCase {
56      //~ Methods ========================================================================================================
57  
58      public static void main(String[] args) {
59          junit.textui.TestRunner.run(DaoAuthenticationProviderTests.class);
60      }
61  
62      public final void setUp() throws Exception {
63          super.setUp();
64      }
65  
66      public void testAuthenticateFailsForIncorrectPasswordCase() {
67          UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "KOala");
68  
69          DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
70          provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
71          provider.setUserCache(new MockUserCache());
72  
73          try {
74              provider.authenticate(token);
75              fail("Should have thrown BadCredentialsException");
76          } catch (BadCredentialsException expected) {
77              assertTrue(true);
78          }
79      }
80  
81      public void testReceivedBadCredentialsWhenCredentialsNotProvided() {
82      	// Test related to SEC-434
83          DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
84          provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
85          provider.setUserCache(new MockUserCache());
86  
87      	UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("marissa", null);
88      	try {
89      		provider.authenticate(authenticationToken); // null pointer exception
90      		fail("Expected BadCredenialsException");
91      	} catch (BadCredentialsException expected) {
92      		assertTrue(true);
93      	}
94      }
95      
96      public void testAuthenticateFailsIfAccountExpired() {
97          UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
98  
99          DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
100         provider.setUserDetailsService(new MockAuthenticationDaoUserPeterAccountExpired());
101         provider.setUserCache(new MockUserCache());
102 
103         try {
104             provider.authenticate(token);
105             fail("Should have thrown AccountExpiredException");
106         } catch (AccountExpiredException expected) {
107             assertTrue(true);
108         }
109     }
110 
111     public void testAuthenticateFailsIfAccountLocked() {
112         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
113 
114         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
115         provider.setUserDetailsService(new MockAuthenticationDaoUserPeterAccountLocked());
116         provider.setUserCache(new MockUserCache());
117 
118         try {
119             provider.authenticate(token);
120             fail("Should have thrown LockedException");
121         } catch (LockedException expected) {
122             assertTrue(true);
123         }
124     }
125 
126     public void testAuthenticateFailsIfCredentialsExpired() {
127         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
128 
129         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
130         provider.setUserDetailsService(new MockAuthenticationDaoUserPeterCredentialsExpired());
131         provider.setUserCache(new MockUserCache());
132 
133         try {
134             provider.authenticate(token);
135             fail("Should have thrown CredentialsExpiredException");
136         } catch (CredentialsExpiredException expected) {
137             assertTrue(true);
138         }
139 
140         // Check that wrong password causes BadCredentialsException, rather than CredentialsExpiredException
141         token = new UsernamePasswordAuthenticationToken("peter", "wrong_password");
142 
143         try {
144             provider.authenticate(token);
145             fail("Should have thrown BadCredentialsException");
146         } catch (BadCredentialsException expected) {
147             assertTrue(true);
148         }
149     }
150 
151     public void testAuthenticateFailsIfUserDisabled() {
152         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
153 
154         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
155         provider.setUserDetailsService(new MockAuthenticationDaoUserPeter());
156         provider.setUserCache(new MockUserCache());
157 
158         try {
159             provider.authenticate(token);
160             fail("Should have thrown DisabledException");
161         } catch (DisabledException expected) {
162             assertTrue(true);
163         }
164     }
165 
166     public void testAuthenticateFailsWhenAuthenticationDaoHasBackendFailure() {
167         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
168 
169         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
170         provider.setUserDetailsService(new MockAuthenticationDaoSimulateBackendError());
171         provider.setUserCache(new MockUserCache());
172 
173         try {
174             provider.authenticate(token);
175             fail("Should have thrown AuthenticationServiceException");
176         } catch (AuthenticationServiceException expected) {
177             assertTrue(true);
178         }
179     }
180 
181     public void testAuthenticateFailsWithEmptyUsername() {
182         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, "koala");
183 
184         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
185         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
186         provider.setUserCache(new MockUserCache());
187 
188         try {
189             provider.authenticate(token);
190             fail("Should have thrown BadCredentialsException");
191         } catch (BadCredentialsException expected) {
192             assertTrue(true);
193         }
194     }
195 
196     public void testAuthenticateFailsWithInvalidPassword() {
197         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa",
198                 "INVALID_PASSWORD");
199 
200         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
201         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
202         provider.setUserCache(new MockUserCache());
203 
204         try {
205             provider.authenticate(token);
206             fail("Should have thrown BadCredentialsException");
207         } catch (BadCredentialsException expected) {
208             assertTrue(true);
209         }
210     }
211 
212     public void testAuthenticateFailsWithInvalidUsernameAndHideUserNotFoundExceptionFalse() {
213         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("INVALID_USER", "koala");
214 
215         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
216         provider.setHideUserNotFoundExceptions(false); // we want UsernameNotFoundExceptions
217         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
218         provider.setUserCache(new MockUserCache());
219 
220         try {
221             provider.authenticate(token);
222             fail("Should have thrown UsernameNotFoundException");
223         } catch (UsernameNotFoundException expected) {
224             assertTrue(true);
225         }
226     }
227 
228     public void testAuthenticateFailsWithInvalidUsernameAndHideUserNotFoundExceptionsWithDefaultOfTrue() {
229         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("INVALID_USER", "koala");
230 
231         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
232         assertTrue(provider.isHideUserNotFoundExceptions());
233         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
234         provider.setUserCache(new MockUserCache());
235 
236         try {
237             provider.authenticate(token);
238             fail("Should have thrown BadCredentialsException");
239         } catch (BadCredentialsException expected) {
240             assertTrue(true);
241         }
242     }
243 
244     public void testAuthenticateFailsWithMixedCaseUsernameIfDefaultChanged() {
245         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("MaRiSSA", "koala");
246 
247         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
248         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
249         provider.setUserCache(new MockUserCache());
250 
251         try {
252             provider.authenticate(token);
253             fail("Should have thrown BadCredentialsException");
254         } catch (BadCredentialsException expected) {
255             assertTrue(true);
256         }
257     }
258 
259     public void testAuthenticates() {
260         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
261         token.setDetails("192.168.0.1");
262 
263         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
264         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
265         provider.setUserCache(new MockUserCache());
266 
267         Authentication result = provider.authenticate(token);
268 
269         if (!(result instanceof UsernamePasswordAuthenticationToken)) {
270             fail("Should have returned instance of UsernamePasswordAuthenticationToken");
271         }
272 
273         UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
274         assertEquals(User.class, castResult.getPrincipal().getClass());
275         assertEquals("koala", castResult.getCredentials());
276         assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
277         assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
278         assertEquals("192.168.0.1", castResult.getDetails());
279     }
280 
281     public void testAuthenticatesASecondTime() {
282         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
283 
284         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
285         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
286         provider.setUserCache(new MockUserCache());
287 
288         Authentication result = provider.authenticate(token);
289 
290         if (!(result instanceof UsernamePasswordAuthenticationToken)) {
291             fail("Should have returned instance of UsernamePasswordAuthenticationToken");
292         }
293 
294         // Now try to authenticate with the previous result (with its UserDetails)
295         Authentication result2 = provider.authenticate(result);
296 
297         if (!(result2 instanceof UsernamePasswordAuthenticationToken)) {
298             fail("Should have returned instance of UsernamePasswordAuthenticationToken");
299         }
300 
301         assertEquals(result.getCredentials(), result2.getCredentials());
302     }
303 
304     public void testAuthenticatesWhenASaltIsUsed() {
305         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
306 
307         SystemWideSaltSource salt = new SystemWideSaltSource();
308         salt.setSystemWideSalt("SYSTEM_SALT_VALUE");
309 
310         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
311         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissaWithSalt());
312         provider.setSaltSource(salt);
313         provider.setUserCache(new MockUserCache());
314 
315         Authentication result = provider.authenticate(token);
316 
317         if (!(result instanceof UsernamePasswordAuthenticationToken)) {
318             fail("Should have returned instance of UsernamePasswordAuthenticationToken");
319         }
320 
321         UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
322         assertEquals(User.class, castResult.getPrincipal().getClass());
323 
324         // We expect original credentials user submitted to be returned
325         assertEquals("koala", castResult.getCredentials());
326         assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
327         assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
328     }
329 
330     public void testAuthenticatesWithForcePrincipalAsString() {
331         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
332 
333         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
334         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
335         provider.setUserCache(new MockUserCache());
336         provider.setForcePrincipalAsString(true);
337 
338         Authentication result = provider.authenticate(token);
339 
340         if (!(result instanceof UsernamePasswordAuthenticationToken)) {
341             fail("Should have returned instance of UsernamePasswordAuthenticationToken");
342         }
343 
344         UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
345         assertEquals(String.class, castResult.getPrincipal().getClass());
346         assertEquals("marissa", castResult.getPrincipal());
347     }
348 
349     public void testDetectsNullBeingReturnedFromAuthenticationDao() {
350         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
351 
352         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
353         provider.setUserDetailsService(new MockAuthenticationDaoReturnsNull());
354 
355         try {
356             provider.authenticate(token);
357             fail("Should have thrown AuthenticationServiceException");
358         } catch (AuthenticationServiceException expected) {
359             assertEquals("UserDetailsService returned null, which is an interface contract violation",
360                 expected.getMessage());
361         }
362     }
363 
364     public void testGettersSetters() {
365         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
366         provider.setPasswordEncoder(new ShaPasswordEncoder());
367         assertEquals(ShaPasswordEncoder.class, provider.getPasswordEncoder().getClass());
368 
369         provider.setSaltSource(new SystemWideSaltSource());
370         assertEquals(SystemWideSaltSource.class, provider.getSaltSource().getClass());
371 
372         provider.setUserCache(new EhCacheBasedUserCache());
373         assertEquals(EhCacheBasedUserCache.class, provider.getUserCache().getClass());
374 
375         assertFalse(provider.isForcePrincipalAsString());
376         provider.setForcePrincipalAsString(true);
377         assertTrue(provider.isForcePrincipalAsString());
378     }
379 
380     public void testGoesBackToAuthenticationDaoToObtainLatestPasswordIfCachedPasswordSeemsIncorrect() {
381         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
382 
383         MockAuthenticationDaoUserMarissa authenticationDao = new MockAuthenticationDaoUserMarissa();
384         MockUserCache cache = new MockUserCache();
385         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
386         provider.setUserDetailsService(authenticationDao);
387         provider.setUserCache(cache);
388 
389         // This will work, as password still "koala"
390         provider.authenticate(token);
391 
392         // Check "marissa = koala" ended up in the cache
393         assertEquals("koala", cache.getUserFromCache("marissa").getPassword());
394 
395         // Now change the password the AuthenticationDao will return
396         authenticationDao.setPassword("easternLongNeckTurtle");
397 
398         // Now try authentication again, with the new password
399         token = new UsernamePasswordAuthenticationToken("marissa", "easternLongNeckTurtle");
400         provider.authenticate(token);
401 
402         // To get this far, the new password was accepted
403         // Check the cache was updated
404         assertEquals("easternLongNeckTurtle", cache.getUserFromCache("marissa").getPassword());
405     }
406 
407     public void testStartupFailsIfNoAuthenticationDao()
408         throws Exception {
409         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
410 
411         try {
412             provider.afterPropertiesSet();
413             fail("Should have thrown IllegalArgumentException");
414         } catch (IllegalArgumentException expected) {
415             assertTrue(true);
416         }
417     }
418 
419     public void testStartupFailsIfNoUserCacheSet() throws Exception {
420         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
421         provider.setUserDetailsService(new MockAuthenticationDaoUserMarissa());
422         assertEquals(NullUserCache.class, provider.getUserCache().getClass());
423         provider.setUserCache(null);
424 
425         try {
426             provider.afterPropertiesSet();
427             fail("Should have thrown IllegalArgumentException");
428         } catch (IllegalArgumentException expected) {
429             assertTrue(true);
430         }
431     }
432 
433     public void testStartupSuccess() throws Exception {
434         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
435         UserDetailsService userDetailsService = new MockAuthenticationDaoUserMarissa();
436         provider.setUserDetailsService(userDetailsService);
437         provider.setUserCache(new MockUserCache());
438         assertEquals(userDetailsService, provider.getUserDetailsService());
439         provider.afterPropertiesSet();
440         assertTrue(true);
441     }
442 
443     public void testSupports() {
444         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
445         assertTrue(provider.supports(UsernamePasswordAuthenticationToken.class));
446         assertTrue(!provider.supports(TestingAuthenticationToken.class));
447     }
448 
449     //~ Inner Classes ==================================================================================================
450 
451     private class MockAuthenticationDaoReturnsNull implements UserDetailsService {
452         public UserDetails loadUserByUsername(String username)
453             throws UsernameNotFoundException, DataAccessException {
454             return null;
455         }
456     }
457 
458     private class MockAuthenticationDaoSimulateBackendError implements UserDetailsService {
459         public UserDetails loadUserByUsername(String username)
460             throws UsernameNotFoundException, DataAccessException {
461             throw new DataRetrievalFailureException("This mock simulator is designed to fail");
462         }
463     }
464 
465     private class MockAuthenticationDaoUserMarissa implements UserDetailsService {
466         private String password = "koala";
467 
468         public UserDetails loadUserByUsername(String username)
469             throws UsernameNotFoundException, DataAccessException {
470             if ("marissa".equals(username)) {
471                 return new User("marissa", password, true, true, true, true,
472                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});
473             } else {
474                 throw new UsernameNotFoundException("Could not find: " + username);
475             }
476         }
477 
478         public void setPassword(String password) {
479             this.password = password;
480         }
481     }
482 
483     private class MockAuthenticationDaoUserMarissaWithSalt implements UserDetailsService {
484         public UserDetails loadUserByUsername(String username)
485             throws UsernameNotFoundException, DataAccessException {
486             if ("marissa".equals(username)) {
487                 return new User("marissa", "koala{SYSTEM_SALT_VALUE}", true, true, true, true,
488                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});
489             } else {
490                 throw new UsernameNotFoundException("Could not find: " + username);
491             }
492         }
493     }
494 
495     private class MockAuthenticationDaoUserPeter implements UserDetailsService {
496         public UserDetails loadUserByUsername(String username)
497             throws UsernameNotFoundException, DataAccessException {
498             if ("peter".equals(username)) {
499                 return new User("peter", "opal", false, true, true, true,
500                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});
501             } else {
502                 throw new UsernameNotFoundException("Could not find: " + username);
503             }
504         }
505     }
506 
507     private class MockAuthenticationDaoUserPeterAccountExpired implements UserDetailsService {
508         public UserDetails loadUserByUsername(String username)
509             throws UsernameNotFoundException, DataAccessException {
510             if ("peter".equals(username)) {
511                 return new User("peter", "opal", true, false, true, true,
512                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});
513             } else {
514                 throw new UsernameNotFoundException("Could not find: " + username);
515             }
516         }
517     }
518 
519     private class MockAuthenticationDaoUserPeterAccountLocked implements UserDetailsService {
520         public UserDetails loadUserByUsername(String username)
521             throws UsernameNotFoundException, DataAccessException {
522             if ("peter".equals(username)) {
523                 return new User("peter", "opal", true, true, true, false,
524                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});
525             } else {
526                 throw new UsernameNotFoundException("Could not find: " + username);
527             }
528         }
529     }
530 
531     private class MockAuthenticationDaoUserPeterCredentialsExpired implements UserDetailsService {
532         public UserDetails loadUserByUsername(String username)
533             throws UsernameNotFoundException, DataAccessException {
534             if ("peter".equals(username)) {
535                 return new User("peter", "opal", true, true, false, true,
536                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});
537             } else {
538                 throw new UsernameNotFoundException("Could not find: " + username);
539             }
540         }
541     }
542 
543     private class MockUserCache implements UserCache {
544         private Map cache = new HashMap();
545 
546         public UserDetails getUserFromCache(String username) {
547             return (User) cache.get(username);
548         }
549 
550         public void putUserInCache(UserDetails user) {
551             cache.put(user.getUsername(), user);
552         }
553 
554         public void removeUserFromCache(String username) {}
555     }
556 }