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