diff --git a/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/CycleInRoleHierarchyException.java b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/CycleInRoleHierarchyException.java
new file mode 100755
index 0000000000..409326235f
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/CycleInRoleHierarchyException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.acegisecurity.userdetails.hierarchicalroles;
+
+/**
+ * Exception that is thrown because of a cycle in the role hierarchy definition
+ *
+ * @author Michael Mayr
+ */
+public class CycleInRoleHierarchyException extends RuntimeException {
+
+ private static final long serialVersionUID = -4970510612118296707L;
+
+ public CycleInRoleHierarchyException() {
+ super("Exception thrown because of a cycle in the role hierarchy definition!");
+ }
+
+}
diff --git a/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchy.java b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchy.java
new file mode 100755
index 0000000000..27bc20618f
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchy.java
@@ -0,0 +1,42 @@
+/*
+ * 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.acegisecurity.userdetails.hierarchicalroles;
+
+import org.acegisecurity.GrantedAuthority;
+
+/**
+ * The simple interface of a role hierarchy.
+ *
+ * @author Michael Mayr
+ *
+ */
+public interface RoleHierarchy {
+
+ /**
+ * This method returns an array of all reachable authorities.
+ * Reachable authorities are the directly assigned authorities plus all
+ * authorities that are (transitively) reachable from them in the role
+ * hierarchy.
+ * Example:
+ * Role hierarchy: ROLE_A > ROLE_B and ROLE_B > ROLE_C.
+ * Directly assigned authority: ROLE_A.
+ * Reachable authorities: ROLE_A, ROLE_B, ROLE_C.
+ *
+ * @param authorities - Array of the directly assigned authorities.
+ * @return Array of all reachable authorities given the assigned authorities.
+ */
+ public GrantedAuthority[] getReachableGrantedAuthorities(GrantedAuthority[] authorities);
+
+}
diff --git a/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchyImpl.java b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchyImpl.java
new file mode 100755
index 0000000000..3d1e846cf0
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchyImpl.java
@@ -0,0 +1,203 @@
+/*
+ * 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.acegisecurity.userdetails.hierarchicalroles;
+
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ *
+ * This class defines a role hierarchy for use with the UserDetailsServiceWrapper.
+ *
+ *
+ * Here is an example configuration of a role hierarchy (hint: read the ">" sign as "includes"):
+
+ <property name="hierarchy">
+ <value>
+ ROLE_A > ROLE_B
+ ROLE_B > ROLE_AUTHENTICATED
+ ROLE_AUTHENTICATED > ROLE_UNAUTHENTICATED
+ </value>
+ </property>
+
+
+ *
+ * Explanation of the above:
+ * In effect every user with ROLE_A also has ROLE_B, ROLE_AUTHENTICATED and ROLE_UNAUTHENTICATED;
+ * every user with ROLE_B also has ROLE_AUTHENTICATED and ROLE_UNAUTHENTICATED;
+ * every user with ROLE_AUTHENTICATED also has ROLE_UNAUTHENTICATED.
+ *
+ *
+ * Hierarchical Roles will dramatically shorten your access rules (and also make the access rules much more elegant).
+ *
+ *
+ * Consider this access rule for Acegi's RoleVoter (background: every user that is authenticated should be
+ * able to log out):
+ * /logout.html=ROLE_A,ROLE_B,ROLE_AUTHENTICATED
+ * With hierarchical roles this can now be shortened to:
+ * /logout.html=ROLE_AUTHENTICATED
+ * In addition to shorter rules this will also make your access rules more readable and your intentions clearer.
+ *
+ *
+ * @author Michael Mayr
+ *
+ */
+public class RoleHierarchyImpl implements RoleHierarchy {
+
+ private static final Log logger = LogFactory.getLog(RoleHierarchyImpl.class);
+
+ private String roleHierarchyStringRepresentation = null;
+
+ /**
+ * rolesReachableInOneStepMap is a Map that under the key of a specific role name contains a set of all roles
+ * reachable from this role in 1 step
+ */
+ private Map rolesReachableInOneStepMap = null;
+
+ /**
+ * rolesReachableInOneOrMoreStepsMap is a Map that under the key of a specific role name contains a set of all
+ * roles reachable from this role in 1 or more steps
+ */
+ private Map rolesReachableInOneOrMoreStepsMap = null;
+
+ /**
+ * Set the role hierarchy and precalculate for every role the set of all reachable roles, i. e. all roles lower in
+ * the hierarchy of every given role. Precalculation is done for performance reasons (reachable roles can then be
+ * calculated in O(1) time).
+ * During precalculation cycles in role hierarchy are detected and will cause a
+ * CycleInRoleHierarchyException to be thrown.
+ *
+ * @param roleHierarchyStringRepresentation - String definition of the role hierarchy.
+ */
+ public void setHierarchy(String roleHierarchyStringRepresentation) {
+ this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation;
+
+ logger.debug("setHierarchy() - The following role hierarchy was set: " + roleHierarchyStringRepresentation);
+
+ buildRolesReachableInOneStepMap();
+ buildRolesReachableInOneOrMoreStepsMap();
+ }
+
+ public GrantedAuthority[] getReachableGrantedAuthorities(GrantedAuthority[] authorities) {
+ if (authorities == null || authorities.length == 0) {
+ return null;
+ }
+
+ Set reachableRoles = new HashSet();
+
+ for (int i = 0; i < authorities.length; i++) {
+ reachableRoles.add(authorities[i]);
+ Set additionalReachableRoles = (Set) rolesReachableInOneOrMoreStepsMap.get(authorities[i]);
+ if (additionalReachableRoles != null) {
+ reachableRoles.addAll(additionalReachableRoles);
+ }
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("getReachableGrantedAuthorities() - From the roles " + ArrayUtils.toString(authorities)
+ + " one can reach " + reachableRoles + " in zero or more steps.");
+ }
+
+ return (GrantedAuthority[]) reachableRoles.toArray(new GrantedAuthority[reachableRoles.size()]);
+ }
+
+ /**
+ * Parse input and build the map for the roles reachable in one step: the higher role will become a key that
+ * references a set of the reachable lower roles.
+ */
+ private void buildRolesReachableInOneStepMap() {
+ String parsingRegex = "(\\s*(\\w+)\\s*\\>\\s*(\\w+))";
+ Pattern pattern = Pattern.compile(parsingRegex);
+
+ Matcher roleHierarchyMatcher = pattern.matcher(roleHierarchyStringRepresentation);
+ rolesReachableInOneStepMap = new HashMap();
+
+ while (roleHierarchyMatcher.find()) {
+ GrantedAuthority higherRole = new GrantedAuthorityImpl(roleHierarchyMatcher.group(2));
+ GrantedAuthority lowerRole = new GrantedAuthorityImpl(roleHierarchyMatcher.group(3));
+ Set rolesReachableInOneStepSet = null;
+
+ if (!rolesReachableInOneStepMap.containsKey(higherRole)) {
+ rolesReachableInOneStepSet = new HashSet();
+ rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet);
+ } else {
+ rolesReachableInOneStepSet = (Set) rolesReachableInOneStepMap.get(higherRole);
+ }
+ rolesReachableInOneStepSet.add(lowerRole);
+
+ logger.debug("buildRolesReachableInOneStepMap() - From role "
+ + higherRole + " one can reach role " + lowerRole + " in one step.");
+ }
+ }
+
+ /**
+ * For every higher role from rolesReachableInOneStepMap store all roles that are reachable from it in the map of
+ * roles reachable in one or more steps. (Or throw a CycleInRoleHierarchyException if a cycle in the role
+ * hierarchy definition is detected)
+ */
+ private void buildRolesReachableInOneOrMoreStepsMap() {
+ rolesReachableInOneOrMoreStepsMap = new HashMap();
+ // iterate over all higher roles from rolesReachableInOneStepMap
+ Iterator roleIterator = rolesReachableInOneStepMap.keySet().iterator();
+
+ while (roleIterator.hasNext()) {
+ GrantedAuthority role = (GrantedAuthority) roleIterator.next();
+ Set rolesToVisitSet = new HashSet();
+
+ if (rolesReachableInOneStepMap.containsKey(role)) {
+ rolesToVisitSet.addAll((Set) rolesReachableInOneStepMap.get(role));
+ }
+
+ Set visitedRolesSet = new HashSet();
+
+ while (!rolesToVisitSet.isEmpty()) {
+ // take a role from the rolesToVisit set
+ GrantedAuthority aRole = (GrantedAuthority) rolesToVisitSet.iterator().next();
+ rolesToVisitSet.remove(aRole);
+ visitedRolesSet.add(aRole);
+ if (rolesReachableInOneStepMap.containsKey(aRole)) {
+ Set newReachableRoles = (Set) rolesReachableInOneStepMap.get(aRole);
+
+ if (CollectionUtils.containsAny(rolesToVisitSet, newReachableRoles)
+ || CollectionUtils.containsAny(visitedRolesSet, newReachableRoles)) {
+ throw new CycleInRoleHierarchyException();
+ } else {
+ // no cycle
+ rolesToVisitSet.addAll(newReachableRoles);
+ }
+ }
+ }
+ rolesReachableInOneOrMoreStepsMap.put(role, visitedRolesSet);
+
+ logger.debug("buildRolesReachableInOneOrMoreStepsMap() - From role "
+ + role + " one can reach " + visitedRolesSet + " in one or more steps.");
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsServiceWrapper.java b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsServiceWrapper.java
new file mode 100755
index 0000000000..0953accea9
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsServiceWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * 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.acegisecurity.userdetails.hierarchicalroles;
+
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UserDetailsService;
+import org.acegisecurity.userdetails.UsernameNotFoundException;
+import org.springframework.dao.DataAccessException;
+
+/**
+ * This class wraps Acegi's UserDetailsService in a way that its loadUserByUsername()
+ * method returns wrapped UserDetails that return all hierachically reachable authorities
+ * instead of only the directly assigned authorities.
+ *
+ * @author Michael Mayr
+ */
+public class UserDetailsServiceWrapper implements UserDetailsService {
+
+ private UserDetailsService userDetailsService = null;
+
+ private RoleHierarchy roleHierarchy = null;
+
+ public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
+ this.roleHierarchy = roleHierarchy;
+ }
+
+ public void setUserDetailsService(UserDetailsService userDetailsService) {
+ this.userDetailsService = userDetailsService;
+ }
+
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+ UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+ // wrapped UserDetailsService might throw UsernameNotFoundException or DataAccessException which will then bubble up
+ return new UserDetailsWrapper(userDetails, roleHierarchy);
+ }
+
+ public UserDetailsService getWrappedUserDetailsService() {
+ return userDetailsService;
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsWrapper.java b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsWrapper.java
new file mode 100755
index 0000000000..c017e7bda5
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * 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.acegisecurity.userdetails.hierarchicalroles;
+
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.userdetails.UserDetails;
+
+/**
+ * This class wraps Acegi's UserDetails in a way that its getAuthorities()-Method is
+ * delegated to RoleHierarchy.getReachableGrantedAuthorities. All other methods are
+ * delegated to the UserDetails implementation.
+ *
+ * @author Michael Mayr
+ */
+public class UserDetailsWrapper implements UserDetails {
+
+ private static final long serialVersionUID = 1532428778390085311L;
+
+ private UserDetails userDetails = null;
+
+ private RoleHierarchy roleHierarchy = null;
+
+ public UserDetailsWrapper(UserDetails userDetails, RoleHierarchy roleHierarchy) {
+ this.userDetails = userDetails;
+ this.roleHierarchy = roleHierarchy;
+ }
+
+ public boolean isAccountNonExpired() {
+ return userDetails.isAccountNonExpired();
+ }
+
+ public boolean isAccountNonLocked() {
+ return userDetails.isAccountNonLocked();
+ }
+
+ public GrantedAuthority[] getAuthorities() {
+ return roleHierarchy.getReachableGrantedAuthorities(userDetails.getAuthorities());
+ }
+
+ public boolean isCredentialsNonExpired() {
+ return userDetails.isCredentialsNonExpired();
+ }
+
+ public boolean isEnabled() {
+ return userDetails.isEnabled();
+ }
+
+ public String getPassword() {
+ return userDetails.getPassword();
+ }
+
+ public String getUsername() {
+ return userDetails.getUsername();
+ }
+
+ public UserDetails getUnwrappedUserDetails() {
+ return userDetails;
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/HierarchicalRolesTestHelper.java b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/HierarchicalRolesTestHelper.java
new file mode 100755
index 0000000000..e53e300d13
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/HierarchicalRolesTestHelper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.acegisecurity.userdetails.hierarchicalroles;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.acegisecurity.GrantedAuthority;
+import org.apache.commons.collections.CollectionUtils;
+
+/**
+ * Test helper class for the hierarchical roles tests.
+ *
+ * @author Michael Mayr
+ */
+public abstract class HierarchicalRolesTestHelper {
+
+ public static boolean containTheSameGrantedAuthorities(GrantedAuthority[] authorities1, GrantedAuthority[] authorities2) {
+ if (authorities1 == null && authorities2 == null) {
+ return true;
+ } else if (authorities1 == null || authorities2 == null) {
+ return false;
+ }
+ List authoritiesList1 = new ArrayList();
+ CollectionUtils.addAll(authoritiesList1, authorities1);
+ List authoritiesList2 = new ArrayList();
+ CollectionUtils.addAll(authoritiesList2, authorities2);
+ return CollectionUtils.isEqualCollection(authoritiesList1, authoritiesList2);
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchyImplTests.java b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchyImplTests.java
new file mode 100755
index 0000000000..59c8c10594
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/RoleHierarchyImplTests.java
@@ -0,0 +1,88 @@
+/*
+* 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.acegisecurity.userdetails.hierarchicalroles;
+
+import junit.framework.TestCase;
+import junit.textui.TestRunner;
+
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+
+/**
+ * Tests for {@link RoleHierarchyImpl}.
+ *
+ * @author Michael Mayr
+ */
+public class RoleHierarchyImplTests extends TestCase {
+
+ public RoleHierarchyImplTests() {
+ }
+
+ public RoleHierarchyImplTests(String testCaseName) {
+ super(testCaseName);
+ }
+
+ public static void main(String[] args) {
+ TestRunner.run(RoleHierarchyImplTests.class);
+ }
+
+ public void testSimpleRoleHierarchy() {
+ GrantedAuthority[] authorities0 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_0") };
+ GrantedAuthority[] authorities1 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A") };
+ GrantedAuthority[] authorities2 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_B") };
+
+ RoleHierarchyImpl roleHierarchyImpl = new RoleHierarchyImpl();
+ roleHierarchyImpl.setHierarchy("ROLE_A > ROLE_B");
+
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(roleHierarchyImpl.getReachableGrantedAuthorities(authorities0), authorities0));
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(roleHierarchyImpl.getReachableGrantedAuthorities(authorities1), authorities2));
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(roleHierarchyImpl.getReachableGrantedAuthorities(authorities2), authorities2));
+ }
+
+ public void testTransitiveRoleHierarchies() {
+ GrantedAuthority[] authorities1 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A") };
+ GrantedAuthority[] authorities2 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_B"), new GrantedAuthorityImpl("ROLE_C") };
+ GrantedAuthority[] authorities3 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_B"), new GrantedAuthorityImpl("ROLE_C"),
+ new GrantedAuthorityImpl("ROLE_D") };
+
+ RoleHierarchyImpl roleHierarchyImpl = new RoleHierarchyImpl();
+
+ roleHierarchyImpl.setHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C");
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(roleHierarchyImpl.getReachableGrantedAuthorities(authorities1), authorities2));
+
+ roleHierarchyImpl.setHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C\nROLE_C > ROLE_D");
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(roleHierarchyImpl.getReachableGrantedAuthorities(authorities1), authorities3));
+ }
+
+ public void testCyclesInRoleHierarchy() {
+ RoleHierarchyImpl roleHierarchyImpl = new RoleHierarchyImpl();
+
+ try {
+ roleHierarchyImpl.setHierarchy("ROLE_A > ROLE_A");
+ fail("Cycle in role hierarchy was not detected!");
+ } catch (CycleInRoleHierarchyException e) {}
+
+ try {
+ roleHierarchyImpl.setHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_A");
+ fail("Cycle in role hierarchy was not detected!");
+ } catch (CycleInRoleHierarchyException e) {}
+
+ try {
+ roleHierarchyImpl.setHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C\nROLE_C > ROLE_A");
+ fail("Cycle in role hierarchy was not detected!");
+ } catch (CycleInRoleHierarchyException e) {}
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/TestHelperTests.java b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/TestHelperTests.java
new file mode 100755
index 0000000000..74c8f85fc9
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/TestHelperTests.java
@@ -0,0 +1,58 @@
+/*
+ * 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.acegisecurity.userdetails.hierarchicalroles;
+
+import junit.framework.TestCase;
+import junit.textui.TestRunner;
+
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+
+/**
+ * Tests for {@link HierarchicalRolesTestHelper}.
+ *
+ * @author Michael Mayr
+ */
+public class TestHelperTests extends TestCase {
+
+ public TestHelperTests() {
+ }
+
+ public TestHelperTests(String testCaseName) {
+ super(testCaseName);
+ }
+
+ public void testContainTheSameGrantedAuthorities() {
+ GrantedAuthority[] authorities1 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_B") };
+ GrantedAuthority[] authorities2 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_B"), new GrantedAuthorityImpl("ROLE_A") };
+ GrantedAuthority[] authorities3 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_C") };
+ GrantedAuthority[] authorities4 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A") };
+ GrantedAuthority[] authorities5 = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_A") };
+
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(null, null));
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities1, authorities1));
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities1, authorities2));
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities2, authorities1));
+
+ assertFalse(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(null, authorities1));
+ assertFalse(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities1, null));
+ assertFalse(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities1, authorities3));
+ assertFalse(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities3, authorities1));
+ assertFalse(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities1, authorities4));
+ assertFalse(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities4, authorities1));
+ assertFalse(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(authorities4, authorities5));
+ }
+
+}
diff --git a/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsServiceWrapperTests.java b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsServiceWrapperTests.java
new file mode 100755
index 0000000000..b88ca785d0
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsServiceWrapperTests.java
@@ -0,0 +1,75 @@
+package org.acegisecurity.userdetails.hierarchicalroles;
+
+import junit.textui.TestRunner;
+
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UserDetailsService;
+import org.acegisecurity.userdetails.UsernameNotFoundException;
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+
+public class UserDetailsServiceWrapperTests extends MockObjectTestCase {
+
+ private UserDetailsService wrappedUserDetailsService = null;
+ private UserDetailsServiceWrapper userDetailsServiceWrapper = null;
+
+ public UserDetailsServiceWrapperTests() {
+ super();
+ }
+
+ public UserDetailsServiceWrapperTests(String testCaseName) {
+ super(testCaseName);
+ }
+
+ public static void main(String[] args) {
+ TestRunner.run(UserDetailsServiceWrapperTests.class);
+ }
+
+ protected void setUp() throws Exception {
+ RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
+ roleHierarchy.setHierarchy("ROLE_A > ROLE_B");
+ GrantedAuthority[] authorities = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A") };
+ UserDetails user = new User("EXISTING_USER", "PASSWORD", true, true, true, true, authorities);
+ Mock wrappedUserDetailsServiceMock = mock(UserDetailsService.class);
+ wrappedUserDetailsServiceMock.stubs().method("loadUserByUsername").with(eq("EXISTING_USER")).will(returnValue(user));
+ wrappedUserDetailsServiceMock.stubs().method("loadUserByUsername").with(eq("USERNAME_NOT_FOUND_EXCEPTION")).will(throwException(new UsernameNotFoundException("USERNAME_NOT_FOUND_EXCEPTION")));
+ wrappedUserDetailsServiceMock.stubs().method("loadUserByUsername").with(eq("DATA_ACCESS_EXCEPTION")).will(throwException(new EmptyResultDataAccessException(1234)));
+ wrappedUserDetailsService = (UserDetailsService) wrappedUserDetailsServiceMock.proxy();
+ userDetailsServiceWrapper = new UserDetailsServiceWrapper();
+ userDetailsServiceWrapper.setRoleHierarchy(roleHierarchy);
+ userDetailsServiceWrapper.setUserDetailsService(wrappedUserDetailsService);
+ }
+
+ public void testLoadUserByUsername() {
+ GrantedAuthority[] authorities = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_B") };
+ UserDetails expectedUserDetails = new User("EXISTING_USER", "PASSWORD", true, true, true, true, authorities);
+ UserDetails userDetails = userDetailsServiceWrapper.loadUserByUsername("EXISTING_USER");
+ assertEquals(expectedUserDetails.getPassword(), userDetails.getPassword());
+ assertEquals(expectedUserDetails.getUsername(), userDetails.getUsername());
+ assertEquals(expectedUserDetails.isAccountNonExpired(), userDetails.isAccountNonExpired());
+ assertEquals(expectedUserDetails.isAccountNonLocked(), userDetails.isAccountNonLocked());
+ assertEquals(expectedUserDetails.isCredentialsNonExpired(), expectedUserDetails.isCredentialsNonExpired());
+ assertEquals(expectedUserDetails.isEnabled(), userDetails.isEnabled());
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(expectedUserDetails.getAuthorities(), userDetails.getAuthorities()));
+
+ try {
+ userDetails = userDetailsServiceWrapper.loadUserByUsername("USERNAME_NOT_FOUND_EXCEPTION");
+ fail("testLoadUserByUsername() - UsernameNotFoundException did not bubble up!");
+ } catch (UsernameNotFoundException e) {}
+
+ try {
+ userDetails = userDetailsServiceWrapper.loadUserByUsername("DATA_ACCESS_EXCEPTION");
+ fail("testLoadUserByUsername() - DataAccessException did not bubble up!");
+ } catch (DataAccessException e) {}
+ }
+
+ public void testGetWrappedUserDetailsService() {
+ assertTrue(userDetailsServiceWrapper.getWrappedUserDetailsService() == wrappedUserDetailsService);
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsWrapperTests.java b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsWrapperTests.java
new file mode 100755
index 0000000000..7c3a9c33bb
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/userdetails/hierarchicalroles/UserDetailsWrapperTests.java
@@ -0,0 +1,82 @@
+package org.acegisecurity.userdetails.hierarchicalroles;
+
+import junit.framework.TestCase;
+import junit.textui.TestRunner;
+
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
+
+/**
+ * Tests for {@link UserDetailsWrapper}.
+ *
+ * @author Michael Mayr
+ */
+public class UserDetailsWrapperTests extends TestCase {
+
+ private GrantedAuthority[] authorities = null;
+ private UserDetails userDetails1 = null;
+ private UserDetails userDetails2 = null;
+ private UserDetailsWrapper userDetailsWrapper1 = null;
+ private UserDetailsWrapper userDetailsWrapper2 = null;
+
+ public UserDetailsWrapperTests() {
+ }
+
+ public UserDetailsWrapperTests(String testCaseName) {
+ super(testCaseName);
+ }
+
+ protected void setUp() throws Exception {
+ RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
+ roleHierarchy.setHierarchy("ROLE_A > ROLE_B");
+ authorities = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A") };
+ userDetails1 = new User("TestUser1", "TestPassword1", true, true, true, true, authorities);
+ userDetails2 = new User("TestUser2", "TestPassword2", false, false, false, false, authorities);
+ userDetailsWrapper1 = new UserDetailsWrapper(userDetails1, roleHierarchy);
+ userDetailsWrapper2 = new UserDetailsWrapper(userDetails2, roleHierarchy);
+ }
+
+ public void testIsAccountNonExpired() {
+ assertEquals(userDetails1.isAccountNonExpired(), userDetailsWrapper1.isAccountNonExpired());
+ assertEquals(userDetails2.isAccountNonExpired(), userDetailsWrapper2.isAccountNonExpired());
+ }
+
+ public void testIsAccountNonLocked() {
+ assertEquals(userDetails1.isAccountNonLocked(), userDetailsWrapper1.isAccountNonLocked());
+ assertEquals(userDetails2.isAccountNonLocked(), userDetailsWrapper2.isAccountNonLocked());
+ }
+
+ public void testGetAuthorities() {
+ GrantedAuthority[] expectedAuthorities = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_B") };
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(userDetailsWrapper1.getAuthorities(), expectedAuthorities));
+ assertTrue(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(userDetailsWrapper2.getAuthorities(), expectedAuthorities));
+ }
+
+ public void testIsCredentialsNonExpired() {
+ assertEquals(userDetails1.isCredentialsNonExpired(), userDetailsWrapper1.isCredentialsNonExpired());
+ assertEquals(userDetails2.isCredentialsNonExpired(), userDetailsWrapper2.isCredentialsNonExpired());
+ }
+
+ public void testIsEnabled() {
+ assertEquals(userDetails1.isEnabled(), userDetailsWrapper1.isEnabled());
+ assertEquals(userDetails2.isEnabled(), userDetailsWrapper2.isEnabled());
+ }
+
+ public void testGetPassword() {
+ assertEquals(userDetails1.getPassword(), userDetailsWrapper1.getPassword());
+ assertEquals(userDetails2.getPassword(), userDetailsWrapper2.getPassword());
+ }
+
+ public void testGetUsername() {
+ assertEquals(userDetails1.getUsername(), userDetailsWrapper1.getUsername());
+ assertEquals(userDetails2.getUsername(), userDetailsWrapper2.getUsername());
+ }
+
+ public void testGetUnwrappedUserDetails() {
+ assertTrue(userDetailsWrapper1.getUnwrappedUserDetails() == userDetails1);
+ assertTrue(userDetailsWrapper2.getUnwrappedUserDetails() == userDetails2);
+ }
+
+}
\ No newline at end of file