    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 }