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