diff --git a/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsService.java b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsService.java new file mode 100644 index 0000000000..bb8da5295a --- /dev/null +++ b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsService.java @@ -0,0 +1,58 @@ +package org.springframework.security.userdetails.decorator; + +import org.springframework.security.userdetails.UserDetailsService; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.userdetails.UsernameNotFoundException; +import org.springframework.security.LockedException; +import org.springframework.security.DisabledException; +import org.springframework.security.AccountExpiredException; +import org.springframework.security.CredentialsExpiredException; +import org.springframework.security.SpringSecurityMessageSource; +import org.springframework.security.AuthenticationException; +import org.springframework.dao.DataAccessException; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.util.Assert; + +/** + * Decorates a {@link UserDetailsService}, making it throw an exception if the account is locked, disabled etc. This + * removes the need for separate account status checks in classes which make use of a UserDetailsService. + * + * @author Luke Taylor + * @version $Id$ + */ +public class StatusCheckingUserDetailsService implements UserDetailsService { + private UserDetailsService delegate; + + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + + public StatusCheckingUserDetailsService(UserDetailsService userDetailsService) { + this.delegate = userDetailsService; + } + + public UserDetails loadUserByUsername(String username) throws AuthenticationException, DataAccessException { + + UserDetails user = delegate.loadUserByUsername(username); + + Assert.notNull(user, "UserDetailsService returned null user, an interface violation."); + + if (!user.isAccountNonLocked()) { + throw new LockedException(messages.getMessage("UserDetailsService.locked", "User account is locked")); + } + + if (!user.isEnabled()) { + throw new DisabledException(messages.getMessage("UserDetailsService.disabled", "User is disabled")); + } + + if (!user.isAccountNonExpired()) { + throw new AccountExpiredException(messages.getMessage("UserDetailsService.expired", + "User account has expired")); + } + + if (!user.isCredentialsNonExpired()) { + throw new CredentialsExpiredException(messages.getMessage("UserDetailsService.credentialsExpired", + "User credentials have expired")); + } + + return user; + } +} diff --git a/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsServiceTests.java b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsServiceTests.java new file mode 100644 index 0000000000..9146e82b1d --- /dev/null +++ b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsServiceTests.java @@ -0,0 +1,69 @@ +package org.springframework.security.userdetails.decorator; + +import org.springframework.security.userdetails.UserDetailsService; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.userdetails.UsernameNotFoundException; +import org.springframework.security.userdetails.User; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.LockedException; +import org.springframework.security.DisabledException; +import org.springframework.security.CredentialsExpiredException; +import org.springframework.security.AccountExpiredException; + +import org.springframework.dao.DataAccessException; + +import org.junit.Test; + +import java.util.Map; +import java.util.HashMap; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class StatusCheckingUserDetailsServiceTests { + private StatusCheckingUserDetailsService us = new StatusCheckingUserDetailsService(new MockUserDetailsService()); + + @Test + public void validAccountIsSuccessfullyLoaded() throws Exception { + us.loadUserByUsername("valid"); + } + + @Test(expected = LockedException.class) + public void lockedAccountThrowsLockedException() throws Exception { + us.loadUserByUsername("locked"); + } + + @Test(expected = DisabledException.class) + public void disabledAccountThrowsDisabledException() throws Exception { + us.loadUserByUsername("disabled"); + } + + @Test(expected = CredentialsExpiredException.class) + public void credentialsExpiredAccountThrowsCredentialsExpiredException() throws Exception { + us.loadUserByUsername("credentialsExpired"); + } + + @Test(expected = AccountExpiredException.class) + public void expiredAccountThrowsAccountExpiredException() throws Exception { + us.loadUserByUsername("expired"); + } + + class MockUserDetailsService implements UserDetailsService { + private Map users = new HashMap (); + private GrantedAuthority[] auths = new GrantedAuthority[] {new GrantedAuthorityImpl("A")}; + + MockUserDetailsService() { + users.put("valid", new User("valid", "",true,true,true,true,auths)); + users.put("locked", new User("locked", "",true,true,true,false,auths)); + users.put("disabled", new User("disabled", "",false,true,true,true,auths)); + users.put("credentialsExpired", new User("credentialsExpired", "",true,true,false,true,auths)); + users.put("expired", new User("expired", "",true,false,true,true,auths)); + } + + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + return users.get(username); + } + } +}