Add account expiration and credentials expiration capabilities.

This commit is contained in:
Ben Alex 2005-01-03 01:14:26 +00:00
parent db51400570
commit c939bcb176
30 changed files with 499 additions and 42 deletions

View File

@ -0,0 +1,49 @@
/* Copyright 2004 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity;
/**
* Thrown if an authentication request is rejected because the account has
* expired. Makes no assertion as to whether or not the credentials were
* valid.
*
* @author Ben Alex
* @version $Id$
*/
public class AccountExpiredException extends AuthenticationException {
//~ Constructors ===========================================================
/**
* Constructs a <code>AccountExpiredException</code> with the specified
* message.
*
* @param msg the detail message
*/
public AccountExpiredException(String msg) {
super(msg);
}
/**
* Constructs a <code>AccountExpiredException</code> with the specified
* message and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public AccountExpiredException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -0,0 +1,49 @@
/* Copyright 2004 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity;
/**
* Thrown if an authentication request is rejected because the account's
* credentials have expired. Makes no assertion as to whether or not the
* credentials were valid.
*
* @author Ben Alex
* @version $Id$
*/
public class CredentialsExpiredException extends AuthenticationException {
//~ Constructors ===========================================================
/**
* Constructs a <code>CredentialsExpiredException</code> with the specified
* message.
*
* @param msg the detail message
*/
public CredentialsExpiredException(String msg) {
super(msg);
}
/**
* Constructs a <code>CredentialsExpiredException</code> with the specified
* message and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public CredentialsExpiredException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -15,16 +15,20 @@
package net.sf.acegisecurity.providers.dao;
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.AuthenticationServiceException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.CredentialsExpiredException;
import net.sf.acegisecurity.DisabledException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.AuthenticationProvider;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureAccountExpiredEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureCredentialsExpiredEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureDisabledEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailurePasswordEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureUsernameNotFoundEvent;
@ -228,7 +232,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
authentication,
new User("".equals(username)
? "EMPTY_STRING_PROVIDED" : username, "*****",
false, new GrantedAuthority[0])));
false, false, false, new GrantedAuthority[0])));
}
throw ex;
@ -244,6 +248,25 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
throw new DisabledException("User is disabled");
}
if (!user.isAccountNonExpired()) {
if (this.context != null) {
context.publishEvent(new AuthenticationFailureAccountExpiredEvent(
authentication, user));
}
throw new AccountExpiredException("User account has expired");
}
if (!user.isCredentialsNonExpired()) {
if (this.context != null) {
context.publishEvent(new AuthenticationFailureCredentialsExpiredEvent(
authentication, user));
}
throw new CredentialsExpiredException(
"User credentials have expired");
}
if (!isPasswordCorrect(authentication, user)) {
// Password incorrect, so ensure we're using most current password
if (cacheWasUsed) {

View File

@ -15,16 +15,20 @@
package net.sf.acegisecurity.providers.dao;
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.AuthenticationServiceException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.CredentialsExpiredException;
import net.sf.acegisecurity.DisabledException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.AuthenticationProvider;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureAccountExpiredEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureCredentialsExpiredEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureDisabledEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureUsernameOrPasswordEvent;
import net.sf.acegisecurity.providers.dao.event.AuthenticationSuccessEvent;
@ -179,7 +183,7 @@ public class PasswordDaoAuthenticationProvider implements AuthenticationProvider
context.publishEvent(new AuthenticationFailureUsernameOrPasswordEvent(
authentication,
new User(username, "*****", false,
new User(username, "*****", false, false, false,
new GrantedAuthority[0])));
}
@ -196,6 +200,25 @@ public class PasswordDaoAuthenticationProvider implements AuthenticationProvider
throw new DisabledException("User is disabled");
}
if (!user.isAccountNonExpired()) {
if (this.context != null) {
context.publishEvent(new AuthenticationFailureAccountExpiredEvent(
authentication, user));
}
throw new AccountExpiredException("User account has expired");
}
if (!user.isCredentialsNonExpired()) {
if (this.context != null) {
context.publishEvent(new AuthenticationFailureCredentialsExpiredEvent(
authentication, user));
}
throw new CredentialsExpiredException(
"User credentials have expired");
}
if (!cacheWasUsed) {
// Put into cache
this.userCache.putUserInCache(user);

View File

@ -0,0 +1,37 @@
/* Copyright 2004 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.dao.event;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.UserDetails;
/**
* Application event which indicates authentication failure due to the user's
* account having expired.
*
* @author Ben Alex
* @version $Id$
*/
public class AuthenticationFailureAccountExpiredEvent
extends AuthenticationEvent {
//~ Constructors ===========================================================
public AuthenticationFailureAccountExpiredEvent(
Authentication authentication, UserDetails user) {
super(authentication, user);
}
}

View File

@ -0,0 +1,37 @@
/* Copyright 2004 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.dao.event;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.UserDetails;
/**
* Application event which indicates authentication failure due to the user's
* credentials having expired.
*
* @author Ben Alex
* @version $Id$
*/
public class AuthenticationFailureCredentialsExpiredEvent
extends AuthenticationEvent {
//~ Constructors ===========================================================
public AuthenticationFailureCredentialsExpiredEvent(
Authentication authentication, UserDetails user) {
super(authentication, user);
}
}

View File

@ -37,6 +37,8 @@ public class User implements UserDetails {
private String password;
private String username;
private GrantedAuthority[] authorities;
private boolean accountNonExpired;
private boolean credentialsNonExpired;
private boolean enabled;
//~ Constructors ===========================================================
@ -57,8 +59,38 @@ public class User implements UserDetails {
* @throws IllegalArgumentException if a <code>null</code> value was passed
* either as a parameter or as an element in the
* <code>GrantedAuthority[]</code> array
*
* @deprecated use new constructor with extended properties (this
* constructor will be removed from release 1.0.0)
*/
public User(String username, String password, boolean enabled,
GrantedAuthority[] authorities) throws IllegalArgumentException {
this(username, password, enabled, true, true, authorities);
}
/**
* Construct the <code>User</code> with the details required by {@link
* DaoAuthenticationProvider}.
*
* @param username the username presented to the
* <code>DaoAuthenticationProvider</code>
* @param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
* @param enabled set to <code>true</code> if the user is enabled
* @param accountNonExpired set to <code>true</code> if the account has not
* expired
* @param credentialsNonExpired set to <code>true</code> if the credentials
* have not expired
* @param authorities the authorities that should be granted to the caller
* if they presented the correct username and password and the user
* is enabled
*
* @throws IllegalArgumentException if a <code>null</code> value was passed
* either as a parameter or as an element in the
* <code>GrantedAuthority[]</code> array
*/
public User(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
GrantedAuthority[] authorities) throws IllegalArgumentException {
if (((username == null) || "".equals(username)) || (password == null)
|| "".equals(password) || (authorities == null)) {
@ -78,6 +110,8 @@ public class User implements UserDetails {
this.password = password;
this.enabled = enabled;
this.authorities = authorities;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
}
protected User() {
@ -86,10 +120,18 @@ public class User implements UserDetails {
//~ Methods ================================================================
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public GrantedAuthority[] getAuthorities() {
return authorities;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
}

View File

@ -43,6 +43,16 @@ import java.io.Serializable;
public interface UserDetails extends Serializable {
//~ Methods ================================================================
/**
* Indicates whether the user's account has expired. An expired account
* cannot be authenticated.
*
* @return <code>true</code> if the user's account is valid (ie
* non-expired), <code>false</code> if no longer valid (ie
* expired)
*/
public boolean isAccountNonExpired();
/**
* Returns the authorities granted to the user. Cannot return
* <code>null</code>.
@ -51,6 +61,16 @@ public interface UserDetails extends Serializable {
*/
public GrantedAuthority[] getAuthorities();
/**
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
*
* @return <code>true</code> if the user's credentials are valid (ie
* non-expired), <code>false</code> if no longer valid (ie
* expired)
*/
public boolean isCredentialsNonExpired();
/**
* Indicates whether the user is enabled or disabled. A disabled user
* cannot be authenticated.

View File

@ -58,6 +58,13 @@ import javax.sql.DataSource;
* the {@link MappingSqlQuery} instances used, via the {@link
* #initMappingSqlQueries()} extension point.
* </p>
*
* <p>
* In order to minimise backward compatibility issues, this DAO does not
* recognise the expiration of user accounts or the expiration of user
* credentials. However, it does recognise and honour the user
* enabled/disabled column.
* </p>
*
* @author Ben Alex
* @author colin sampaleanu
@ -185,14 +192,14 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements AuthenticationDao {
arrayAuths = (GrantedAuthority[]) dbAuths.toArray(arrayAuths);
return new User(user.getUsername(), user.getPassword(),
user.isEnabled(), arrayAuths);
user.isEnabled(), true, true, arrayAuths);
}
/**
* Allows subclasses to add their own granted authorities to the list to be
* returned in the <code>User</code>.
*
* @param username the username, for use by finder methods
* @param username the username, for use by finder methods
* @param authorities the current granted authorities, as populated from
* the <code>authoritiesByUsername</code> mapping
*/
@ -248,7 +255,8 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements AuthenticationDao {
String username = rs.getString(1);
String password = rs.getString(2);
boolean enabled = rs.getBoolean(3);
UserDetails user = new User(username, password, enabled,
UserDetails user = new User(username, password, enabled, true,
true,
new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
return user;

View File

@ -56,6 +56,12 @@ import java.util.Properties;
* If the above requirements are not met, the invalid entry will be silently
* ignored.
* </p>
*
* <p>
* This editor always assumes each entry has a non-expired account and
* non-expired credentials. However, it does honour the user enabled/disabled
* flag as described above.
* </p>
*
* @author Ben Alex
* @version $Id$
@ -91,7 +97,7 @@ public class UserMapEditor extends PropertyEditorSupport {
// Make a user object, assuming the properties were properly provided
if (attr != null) {
UserDetails user = new User(username, attr.getPassword(),
attr.isEnabled(), attr.getAuthorities());
attr.isEnabled(), true, true, attr.getAuthorities());
userMap.addUser(user);
}
}

View File

@ -55,7 +55,7 @@ public class GrantedAuthorityEffectiveAclsResolverTests extends TestCase {
.getPrincipal(), new NamedEntityObjectIdentity("OBJECT", "100"),
null, 2);
private UsernamePasswordAuthenticationToken scottWithUserDetails = new UsernamePasswordAuthenticationToken(new User(
"scott", "NOT_USED", true,
"scott", "NOT_USED", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl(
"ROLE_EVERYBODY")}), "not used",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_EVERYBODY"), new GrantedAuthorityImpl("ROLE_TWO")});

View File

@ -327,7 +327,7 @@ public class CasAuthenticationProviderTests extends TestCase {
}
private UserDetails makeUserDetails() {
return new User("user", "password", true,
return new User("user", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
}
@ -337,7 +337,7 @@ public class CasAuthenticationProviderTests extends TestCase {
private class MockAuthoritiesPopulator implements CasAuthoritiesPopulator {
public UserDetails getUserDetails(String casUserId)
throws AuthenticationException {
return new User("user", "password", true,
return new User("user", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl(
"ROLE_B")});
}

View File

@ -319,7 +319,7 @@ public class CasAuthenticationTokenTests extends TestCase {
}
private UserDetails makeUserDetails() {
return new User("user", "password", true,
return new User("user", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
}

View File

@ -102,7 +102,7 @@ public class EhCacheBasedTicketCacheTests extends TestCase {
List proxyList = new Vector();
proxyList.add("https://localhost/newPortal/j_acegi_cas_security_check");
User user = new User("marissa", "password", true,
User user = new User("marissa", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});

View File

@ -139,7 +139,7 @@ public class DaoCasAuthoritiesPopulatorTests extends TestCase {
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if ("marissa".equals(username)) {
return new User("marissa", "koala", true,
return new User("marissa", "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {

View File

@ -17,9 +17,11 @@ package net.sf.acegisecurity.providers.dao;
import junit.framework.TestCase;
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationServiceException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.CredentialsExpiredException;
import net.sf.acegisecurity.DisabledException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
@ -73,6 +75,38 @@ public class DaoAuthenticationProviderTests extends TestCase {
}
}
public void testAuthenticateFailsIfAccountExpired() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter",
"opal");
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setAuthenticationDao(new MockAuthenticationDaoUserPeterAccountExpired());
provider.setUserCache(new MockUserCache());
try {
provider.authenticate(token);
fail("Should have thrown AccountExpiredException");
} catch (AccountExpiredException expected) {
assertTrue(true);
}
}
public void testAuthenticateFailsIfCredentialsExpired() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter",
"opal");
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setAuthenticationDao(new MockAuthenticationDaoUserPeterCredentialsExpired());
provider.setUserCache(new MockUserCache());
try {
provider.authenticate(token);
fail("Should have thrown CredentialsExpiredException");
} catch (CredentialsExpiredException expected) {
assertTrue(true);
}
}
public void testAuthenticateFailsIfUserDisabled() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter",
"opal");
@ -426,7 +460,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if ("marissa".equals(username)) {
return new User("marissa", password, true,
return new User("marissa", password, true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
@ -442,6 +476,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
throws UsernameNotFoundException, DataAccessException {
if ("marissa".equals(username)) {
return new User("marissa", "koala{SYSTEM_SALT_VALUE}", true,
true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
@ -455,7 +490,37 @@ public class DaoAuthenticationProviderTests extends TestCase {
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if ("peter".equals(username)) {
return new User("peter", "opal", false,
return new User("peter", "opal", false, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
throw new UsernameNotFoundException("Could not find: "
+ username);
}
}
}
private class MockAuthenticationDaoUserPeterAccountExpired
implements AuthenticationDao {
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if ("peter".equals(username)) {
return new User("peter", "opal", true, false, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
throw new UsernameNotFoundException("Could not find: "
+ username);
}
}
}
private class MockAuthenticationDaoUserPeterCredentialsExpired
implements AuthenticationDao {
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if ("peter".equals(username)) {
return new User("peter", "opal", true, true, false,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {

View File

@ -17,9 +17,11 @@ package net.sf.acegisecurity.providers.dao;
import junit.framework.TestCase;
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationServiceException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.CredentialsExpiredException;
import net.sf.acegisecurity.DisabledException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
@ -68,6 +70,38 @@ public class PasswordDaoAuthenticationProviderTests extends TestCase {
}
}
public void testAuthenticateFailsIfAccountExpired() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter",
"opal");
PasswordDaoAuthenticationProvider provider = new PasswordDaoAuthenticationProvider();
provider.setPasswordAuthenticationDao(new MockAuthenticationDaoUserPeterAccountExpired());
provider.setUserCache(new MockUserCache());
try {
provider.authenticate(token);
fail("Should have thrown AccountExpiredException");
} catch (AccountExpiredException expected) {
assertTrue(true);
}
}
public void testAuthenticateFailsIfCredentialsExpired() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter",
"opal");
PasswordDaoAuthenticationProvider provider = new PasswordDaoAuthenticationProvider();
provider.setPasswordAuthenticationDao(new MockAuthenticationDaoUserPeterCredentialsExpired());
provider.setUserCache(new MockUserCache());
try {
provider.authenticate(token);
fail("Should have thrown CredentialsExpiredException");
} catch (CredentialsExpiredException expected) {
assertTrue(true);
}
}
public void testAuthenticateFailsIfUserDisabled() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter",
"opal");
@ -290,7 +324,7 @@ public class PasswordDaoAuthenticationProviderTests extends TestCase {
String password)
throws BadCredentialsException, DataAccessException {
if ("marissa".equals(username) && "koala".equals(password)) {
return new User("marissa", "koala", true,
return new User("marissa", "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
@ -305,7 +339,7 @@ public class PasswordDaoAuthenticationProviderTests extends TestCase {
String password)
throws BadCredentialsException, DataAccessException {
if ("peter".equals(username) && "opal".equals(password)) {
return new User("peter", "opal", false,
return new User("peter", "opal", false, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
@ -314,6 +348,38 @@ public class PasswordDaoAuthenticationProviderTests extends TestCase {
}
}
private class MockAuthenticationDaoUserPeterAccountExpired
implements PasswordAuthenticationDao {
public UserDetails loadUserByUsernameAndPassword(String username,
String password)
throws UsernameNotFoundException, DataAccessException {
if ("peter".equals(username)) {
return new User("peter", "opal", true, false, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
throw new UsernameNotFoundException("Could not find: "
+ username);
}
}
}
private class MockAuthenticationDaoUserPeterCredentialsExpired
implements PasswordAuthenticationDao {
public UserDetails loadUserByUsernameAndPassword(String username,
String password)
throws UsernameNotFoundException, DataAccessException {
if ("peter".equals(username)) {
return new User("peter", "opal", true, true, false,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
} else {
throw new UsernameNotFoundException("Could not find: "
+ username);
}
}
}
private class MockUserCache implements UserCache {
private Map cache = new HashMap();

View File

@ -60,7 +60,7 @@ public class UserTests extends TestCase {
public void testNullValuesRejected() throws Exception {
try {
UserDetails user = new User(null, "koala", true,
UserDetails user = new User(null, "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
fail("Should have thrown IllegalArgumentException");
@ -69,7 +69,7 @@ public class UserTests extends TestCase {
}
try {
UserDetails user = new User("marissa", null, true,
UserDetails user = new User("marissa", null, true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
fail("Should have thrown IllegalArgumentException");
@ -78,14 +78,15 @@ public class UserTests extends TestCase {
}
try {
UserDetails user = new User("marissa", "koala", true, null);
UserDetails user = new User("marissa", "koala", true, true, true,
null);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
UserDetails user = new User("marissa", "koala", true,
UserDetails user = new User("marissa", "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), null});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
@ -96,7 +97,7 @@ public class UserTests extends TestCase {
public void testNullWithinGrantedAuthorityElementIsRejected()
throws Exception {
try {
UserDetails user = new User(null, "koala", true,
UserDetails user = new User(null, "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO"), null, new GrantedAuthorityImpl(
"ROLE_THREE")});
@ -107,7 +108,7 @@ public class UserTests extends TestCase {
}
public void testUserGettersSetter() throws Exception {
UserDetails user = new User("marissa", "koala", true,
UserDetails user = new User("marissa", "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertEquals("marissa", user.getUsername());
@ -120,7 +121,7 @@ public class UserTests extends TestCase {
}
public void testUserIsEnabled() throws Exception {
UserDetails user = new User("marissa", "koala", false,
UserDetails user = new User("marissa", "koala", false, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertTrue(!user.isEnabled());

View File

@ -95,7 +95,7 @@ public class EhCacheBasedUserCacheTests extends TestCase {
}
private User getUser() {
return new User("john", "password", true,
return new User("john", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
}

View File

@ -57,7 +57,7 @@ public class NullUserCacheTests extends TestCase {
}
private User getUser() {
return new User("john", "password", true,
return new User("john", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
}

View File

@ -97,7 +97,7 @@ public class AuthenticationEventTests extends TestCase {
}
private User getUser() {
User user = new User("foo", "bar", true,
User user = new User("foo", "bar", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_FOOBAR")});
return user;

View File

@ -90,7 +90,7 @@ public class LoggerListenerTests extends TestCase {
}
private User getUser() {
User user = new User("foo", "bar", true,
User user = new User("foo", "bar", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_FOOBAR")});
return user;

View File

@ -52,13 +52,13 @@ public class UserMapTests extends TestCase {
}
public void testAddAndRetrieveUser() {
UserDetails marissa = new User("marissa", "koala", true,
UserDetails marissa = new User("marissa", "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
UserDetails scott = new User("scott", "wombat", true,
UserDetails scott = new User("scott", "wombat", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_THREE")});
UserDetails peter = new User("peter", "opal", true,
UserDetails peter = new User("peter", "opal", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_FOUR")});
UserMap map = new UserMap();
@ -85,7 +85,7 @@ public class UserMapTests extends TestCase {
}
public void testUnknownUserIsNotRetrieved() {
UserDetails marissa = new User("marissa", "koala", true,
UserDetails marissa = new User("marissa", "koala", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
UserMap map = new UserMap();

View File

@ -67,7 +67,7 @@ public class ReflectionSaltSourceTests extends TestCase {
ReflectionSaltSource saltSource = new ReflectionSaltSource();
saltSource.setUserPropertyToUse("getDoesNotExist");
UserDetails user = new User("scott", "wombat", true,
UserDetails user = new User("scott", "wombat", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
try {
@ -89,7 +89,7 @@ public class ReflectionSaltSourceTests extends TestCase {
saltSource.setUserPropertyToUse("getUsername");
saltSource.afterPropertiesSet();
UserDetails user = new User("scott", "wombat", true,
UserDetails user = new User("scott", "wombat", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
assertEquals("scott", saltSource.getSalt(user));
}

View File

@ -77,7 +77,7 @@ public class AuthenticationTagTests extends TestCase {
public void testOperationWhenPrincipalIsAUserDetailsInstance()
throws JspException {
Authentication auth = new TestingAuthenticationToken(new User(
"marissaUserDetails", "koala", true,
"marissaUserDetails", "koala", true, true, true,
new GrantedAuthority[] {}), "koala",
new GrantedAuthority[] {});
SecureContext sc = new SecureContextImpl();

View File

@ -78,7 +78,7 @@ public class ContextHolderAwareRequestWrapperTests extends TestCase {
throws Exception {
SecureContext sc = new SecureContextImpl();
Authentication auth = new TestingAuthenticationToken(new User(
"marissaAsUserDetails", "koala", true,
"marissaAsUserDetails", "koala", true, true, true,
new GrantedAuthority[] {}), "koala",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_HELLO"), new GrantedAuthorityImpl(
"ROLE_FOOBAR")});

View File

@ -1042,7 +1042,11 @@ public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
authentication is denied. An
<literal>AuthenticationServiceException</literal> is also provided,
which indicates the authentication system could not process the
request (eg a database was unavailable).</para>
request (eg a database was unavailable).
<literal>AuthenticationException</literal> also has a
<literal>CredentialsExpiredException</literal> and
<literal>AccoungtExpiredException</literal> subclass, although these
are less commonly used.</para>
</sect2>
<sect2 id="security-authentication-provider">
@ -1295,6 +1299,23 @@ public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
normally the case when an account is locked.</para>
</listitem>
<listitem>
<para><literal>AuthenticationFailureAccountExpiredEvent</literal>
is published when an authentication request is unsuccessful
because the returned <literal>UserDetails</literal> indicates the
account has expired. Some applications may wish to distinguish
between an account being disabled and expired.</para>
</listitem>
<listitem>
<para><literal>AuthenticationFailureCredentialsExpiredEvent</literal>
is published when an authentication request is unsuccessful
because the returned <literal>UserDetails</literal> indicates the
account's credentials have expired. Some applications may wish to
expire the credentials if, for example, a password is not changed
with sufficient regularity.</para>
</listitem>
<listitem>
<para><literal>AuthenticationFailureUsernameNotFoundEvent</literal>
is published when an authentication request is unsuccessful
@ -3184,8 +3205,8 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
<para>As the <literal>Authentication</literal> object is now in
the well-known location, it is handled like any other
authentication approach. Usually the
<literal>AutoIntegrationFilter</literal> will be used to associate
the <literal>Authentication</literal> object with the
<literal>HttpSessionIntegrationFilter</literal> will be used to
associate the <literal>Authentication</literal> object with the
<literal>ContextHolder</literal> for the duration of each
request.</para>
</listitem>
@ -4169,8 +4190,8 @@ INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);</programlisting></para>
</listitem>
<listitem>
<para>Acegi Security System for Spring Auto Integration Filter
(<literal>AutoIntegrationFilter</literal>)</para>
<para>Acegi Security System for Spring HTTP Session Integration
Filter (<literal>HttpSessionIntegrationFilter</literal>)</para>
</listitem>
<listitem>

View File

@ -45,6 +45,8 @@
<action dev="benalex" type="add">Added AbstractProcessingFilter property to always use defaultTargetUrl</action>
<action dev="benalex" type="add">Added ContextHolderAwareRequestWrapper to integrate with getRemoteUser()</action>
<action dev="benalex" type="add">Added attempted username to view if processed by AuthenticationProcessingFilter</action>
<action dev="benalex" type="add">Added UserDetails account and credentials expiration methods</action>
<action dev="benalex" type="add">Added exceptions and events to support new UserDetails methods</action>
<action dev="benalex" type="update">Improved BasicAclProvider to only respond to specified ACL object requests</action>
<action dev="benalex" type="update">Refactored MethodDefinitionSource to work with Method, not MethodInvocation</action>
<action dev="benalex" type="update">Refactored AbstractFilterInvocationDefinitionSource to work with URL Strings alone</action>
@ -55,6 +57,7 @@
<action dev="benalex" type="update">Refactored EH-CACHE implementations to use Spring IoC defined caches instead</action>
<action dev="benalex" type="update">AbstractProcessingFilter now has various hook methods to assist subclasses</action>
<action dev="benalex" type="update">DaoAuthenticationProvider better detects AuthenticationDao interface violations</action>
<action dev="benalex" type="update">The User class has a new constructor (the old constructor is deprecated)</action>
<action dev="benalex" type="fix">Fixed ambiguous column references in JdbcDaoImpl default query</action>
<action dev="benalex" type="fix">Fixed AbstractProcessingFilter to use removeAttribute (JRun compatibility)</action>
<action dev="benalex" type="fix">Fixed GrantedAuthorityEffectiveAclResolver support of UserDetails principals</action>

View File

@ -9,8 +9,15 @@
The following should help most casual users of the project update their
applications:
<ul>
<li>UserDetails now has two extra methods. Most people who have extended
Acegi Security's default User implementation of UserDetails will be fine, as
the constructor sets sensible defaults for the extra methods. People who
have written their own UserDetails implementation from scratch will need to
add the additional two methods. Returning true to both methods will normally
be correct.
</li>
<li>MethodDefinitionMap, which is usually used by MethodSecurityInterceptor
for its objectDefinitionSource property, has been changed. From 0.7, when
for its objectDefinitionSource property, has been changed. From 0.7.0, when
MethodDefinitionMap is queried for configuration attributes associated with
secure MethodInvocations, it will use any method matching in the method
invocation class (as it always has) plus any method matching any interface

View File

@ -164,7 +164,7 @@ public class LdapPasswordAuthenticationDao implements PasswordAuthenticationDao
String[] ldapRoles = (String[]) roles.toArray(new String[] {});
return new User(username, password, true,
return new User(username, password, true, true, true,
getGrantedAuthorities(ldapRoles));
} catch (AuthenticationException ex) {
throw new BadCredentialsException(BAD_CREDENTIALS_EXCEPTION_MESSAGE,