diff --git a/security/src/main/java/org/springframework/ws/soap/security/support/AcegiUtils.java b/security/src/main/java/org/springframework/ws/soap/security/support/AcegiUtils.java new file mode 100644 index 00000000..5f57979f --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/support/AcegiUtils.java @@ -0,0 +1,58 @@ +/* Copyright 2004, 2005, 2006 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 org.springframework.ws.soap.security.support; + +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.LockedException; +import org.acegisecurity.DisabledException; +import org.acegisecurity.AccountExpiredException; +import org.acegisecurity.CredentialsExpiredException; + +/** + * Generic utility methods for Spring Security + * + * @author Tareq Abedrabbo + * @since 1.5.8 + */ +public abstract class AcegiUtils { + + /** + * Checks the validity of a user's account and credentials. + * @param user the user to check + * @throws org.springframework.security.AccountExpiredException if the account has expired + * @throws org.springframework.security.CredentialsExpiredException if the credentials have expired + * @throws org.springframework.security.DisabledException if the account is disabled + * @throws org.springframework.security.LockedException if the account is locked + */ + public static void checkUserValidity(UserDetails user) + throws AccountExpiredException, CredentialsExpiredException, DisabledException, LockedException { + if (!user.isAccountNonLocked()) { + throw new LockedException("User account is locked"); + } + + if (!user.isEnabled()) { + throw new DisabledException("User is disabled"); + } + + if (!user.isAccountNonExpired()) { + throw new AccountExpiredException("User account has expired"); + } + + if (!user.isCredentialsNonExpired()) { + throw new CredentialsExpiredException("User credentials have expired"); + } + } +} \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/support/SpringSecurityUtils.java b/security/src/main/java/org/springframework/ws/soap/security/support/SpringSecurityUtils.java new file mode 100644 index 00000000..2501d60d --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/support/SpringSecurityUtils.java @@ -0,0 +1,58 @@ +/* Copyright 2004, 2005, 2006 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 org.springframework.ws.soap.security.support; + +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.LockedException; +import org.springframework.security.DisabledException; +import org.springframework.security.AccountExpiredException; +import org.springframework.security.CredentialsExpiredException; + +/** + * Generic utility methods for Spring Security + * + * @author Tareq Abedrabbo + * @since 1.5.8 + */ +public abstract class SpringSecurityUtils { + + /** + * Checks the validity of a user's account and credentials. + * @param user the user to check + * @throws AccountExpiredException if the account has expired + * @throws CredentialsExpiredException if the credentials have expired + * @throws DisabledException if the account is disabled + * @throws LockedException if the account is locked + */ + public static void checkUserValidity(UserDetails user) + throws AccountExpiredException, CredentialsExpiredException, DisabledException, LockedException { + if (!user.isAccountNonLocked()) { + throw new LockedException("User account is locked", user); + } + + if (!user.isEnabled()) { + throw new DisabledException("User is disabled", user); + } + + if (!user.isAccountNonExpired()) { + throw new AccountExpiredException("User account has expired", user); + } + + if (!user.isCredentialsNonExpired()) { + throw new CredentialsExpiredException("User credentials have expired", user); + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/SpringDigestPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/SpringDigestPasswordValidationCallbackHandler.java index 586a20cd..8f2b0fe3 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/SpringDigestPasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/SpringDigestPasswordValidationCallbackHandler.java @@ -33,6 +33,7 @@ import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.userdetails.UsernameNotFoundException; import org.springframework.util.Assert; import org.springframework.ws.soap.security.callback.CleanupCallback; +import org.springframework.ws.soap.security.support.SpringSecurityUtils; /** * Callback handler that validates a password digest using an Spring Security UserDetailsService. Logic @@ -71,6 +72,7 @@ public class SpringDigestPasswordValidationCallbackHandler extends AbstractWsPas String identifier = callback.getIdentifier(); UserDetails user = loadUserDetails(identifier); if (user != null) { + SpringSecurityUtils.checkUserValidity(user); callback.setPassword(user.getPassword()); } } diff --git a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java index 02c8655c..edbc95f7 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java @@ -35,6 +35,7 @@ import org.springframework.util.Assert; import org.springframework.ws.soap.security.callback.CleanupCallback; import org.springframework.ws.soap.security.wss4j.callback.AbstractWsPasswordCallbackHandler; import org.springframework.ws.soap.security.wss4j.callback.UsernameTokenPrincipalCallback; +import org.springframework.ws.soap.security.support.AcegiUtils; /** * Callback handler that validates a password digest using an Acegi UserDetailsService. Logic based on @@ -73,6 +74,7 @@ public class AcegiDigestPasswordValidationCallbackHandler extends AbstractWsPass String identifier = callback.getIdentifier(); UserDetails user = loadUserDetails(identifier); if (user != null) { + AcegiUtils.checkUserValidity(user); callback.setPassword(user.getPassword()); } } diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandler.java index 3f6af7d4..702cd77b 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandler.java @@ -35,6 +35,7 @@ import org.springframework.security.userdetails.UsernameNotFoundException; import org.springframework.util.Assert; import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; import org.springframework.ws.soap.security.callback.CleanupCallback; +import org.springframework.ws.soap.security.support.SpringSecurityUtils; /** * Callback handler that validates a password digest using an Spring Security UserDetailsService. Logic @@ -89,6 +90,7 @@ public class SpringDigestPasswordValidationCallbackHandler extends AbstractCallb String username = request.getUsername(); UserDetails user = loadUserDetails(username); if (user != null) { + SpringSecurityUtils.checkUserValidity(user); request.setPassword(user.getPassword()); } SpringSecurityDigestPasswordValidator validator = new SpringSecurityDigestPasswordValidator(user); diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java index 3a41b7f2..8874618b 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java @@ -36,6 +36,7 @@ import org.springframework.util.Assert; import org.springframework.ws.soap.security.callback.AbstractCallbackHandler; import org.springframework.ws.soap.security.callback.CleanupCallback; import org.springframework.ws.soap.security.xwss.callback.DefaultTimestampValidator; +import org.springframework.ws.soap.security.support.AcegiUtils; /** * Callback handler that validates a password digest using an Acegi UserDetailsService. Logic based on @@ -89,6 +90,7 @@ public class AcegiDigestPasswordValidationCallbackHandler extends AbstractCallba String username = request.getUsername(); UserDetails user = loadUserDetails(username); if (user != null) { + AcegiUtils.checkUserValidity(user); request.setPassword(user.getPassword()); } AcegiDigestPasswordValidator validator = new AcegiDigestPasswordValidator(user); diff --git a/security/src/test/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java index 8b37a6fa..44506518 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/wss4j/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java @@ -20,12 +20,14 @@ import junit.framework.TestCase; import org.acegisecurity.Authentication; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.GrantedAuthorityImpl; +import org.acegisecurity.DisabledException; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.acegisecurity.userdetails.User; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UserDetailsService; import org.apache.ws.security.WSUsernameTokenPrincipal; +import org.apache.ws.security.WSPasswordCallback; import org.easymock.MockControl; import org.springframework.ws.soap.security.wss4j.callback.UsernameTokenPrincipalCallback; @@ -41,31 +43,29 @@ public class AcegiDigestPasswordValidationCallbackHandlerTest extends TestCase { private MockControl control; - private WSUsernameTokenPrincipal principal; - - private UsernameTokenPrincipalCallback callback; - private UserDetails user; protected void setUp() throws Exception { callbackHandler = new AcegiDigestPasswordValidationCallbackHandler(); grantedAuthority = new GrantedAuthorityImpl("ROLE_1"); - user = new User("Ernie", "Bert", true, true, true, true, new GrantedAuthority[]{grantedAuthority}); control = MockControl.createControl(UserDetailsService.class); userDetailsService = (UserDetailsService) control.getMock(); userDetailsService.loadUserByUsername("Ernie"); - control.setDefaultReturnValue(user); - control.replay(); callbackHandler.setUserDetailsService(userDetailsService); + } - principal = new WSUsernameTokenPrincipal("Ernie", true); - callback = new UsernameTokenPrincipalCallback(principal); - + protected void tearDown() throws Exception { + control.reset(); } public void testHandleUsernameTokenPrincipal() throws Exception { + user = new User("Ernie", "Bert", true, true, true, true, new GrantedAuthority[]{grantedAuthority}); + WSUsernameTokenPrincipal principal = new WSUsernameTokenPrincipal("Ernie", true); + UsernameTokenPrincipalCallback callback = new UsernameTokenPrincipalCallback(principal); + control.setDefaultReturnValue(user); + control.replay(); callbackHandler.handleUsernameTokenPrincipal(callback); SecurityContext context = SecurityContextHolder.getContext(); assertNotNull("SecurityContext must not be null", context); @@ -75,4 +75,16 @@ public class AcegiDigestPasswordValidationCallbackHandlerTest extends TestCase { assertTrue("GrantedAuthority[] must not be null or empty", (authorities != null && authorities.length > 0)); assertEquals("Unexpected authority", grantedAuthority, authorities[0]); } + + public void testHandleUsernameTokenWithDisabledUser() throws Exception { + user = new User("Ernie", "Bert", false, true, true, true, new GrantedAuthority[]{grantedAuthority}); + WSPasswordCallback callback = new WSPasswordCallback("ID", WSPasswordCallback.USERNAME_TOKEN); + control.setDefaultReturnValue(user); + control.replay(); + try { + callbackHandler.handleUsernameToken(callback); + fail("disabled user authenticated"); + } catch (DisabledException expected) { + } + } } diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandlerTest.java index 2f24cca8..d386920f 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandlerTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SpringDigestPasswordValidationCallbackHandlerTest.java @@ -21,6 +21,7 @@ import junit.framework.TestCase; import org.easymock.MockControl; import org.springframework.security.GrantedAuthority; +import org.springframework.security.DisabledException; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.providers.TestingAuthenticationToken; import org.springframework.security.userdetails.User; @@ -93,6 +94,18 @@ public class SpringDigestPasswordValidationCallbackHandlerTest extends TestCase control.verify(); } + public void testAuthenticateUserDigestDisbaled() throws Exception { + User user = new User(username, "Ernie", false, true, true, true, new GrantedAuthority[0]); + control.expectAndReturn(mock.loadUserByUsername(username), user); + control.replay(); + try { + callbackHandler.handleInternal(callback); + fail("disabled user authenticated"); + } catch ( + DisabledException expected) { + } + } + public void testCleanUp() throws Exception { TestingAuthenticationToken authentication = new TestingAuthenticationToken(new Object(), new Object(), new GrantedAuthority[0]); diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java index 0d355e60..ac2b56db 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java @@ -19,6 +19,7 @@ package org.springframework.ws.soap.security.xwss.callback.acegi; import com.sun.xml.wss.impl.callback.PasswordValidationCallback; import junit.framework.TestCase; import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.DisabledException; import org.acegisecurity.context.SecurityContextHolder; import org.acegisecurity.providers.TestingAuthenticationToken; import org.acegisecurity.userdetails.User; @@ -93,6 +94,18 @@ public class AcegiDigestPasswordValidationCallbackHandlerTest extends TestCase { control.verify(); } + public void testAuthenticateUserDigestDisbaled() throws Exception { + User user = new User(username, "Ernie", false, true, true, true, new GrantedAuthority[0]); + control.expectAndReturn(mock.loadUserByUsername(username), user); + control.replay(); + try { + callbackHandler.handleInternal(callback); + fail("disabled user authenticated"); + } catch ( + DisabledException expected) { + } + } + public void testCleanUp() throws Exception { TestingAuthenticationToken authentication = new TestingAuthenticationToken(new Object(), new Object(), new GrantedAuthority[0]);