diff --git a/core/src/main/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyUtils.java b/core/src/main/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyUtils.java
new file mode 100644
index 0000000000..ff5ce22e5c
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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.security.access.hierarchicalroles;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility method for working with {@link RoleHierarchy}.
+ *
+ * @author Thomas Darimont
+ * @since
+ */
+public class RoleHierarchyUtils {
+
+ /**
+ * Builds a {@link RoleHierarchy} representation from the given {@link Map} of role name to implied roles.
+ * The map key is the role name and the map value is a {@link List} of implied role names.
+ *
+ *
+ * Here is an example configuration of a role hierarchy configured via yaml.
+ * wich follows the pattern:
+ * {@code ROLE_NAME: List of implied role names}
+ *
+ *
+ *
+ *
+ * security:
+ * roles:
+ * hierarchy:
+ * ROLE_ALL: ROLE_A, ROLE_C
+ * ROLE_A: ROLE_B
+ * ROLE_B: ROLE_AUTHENTICATED
+ * ROLE_C: ROLE_AUTHENTICATED
+ * ROLE_AUTHENTICATED: ROLE_UNAUTHENTICATED
+ *
+ *
+ * This yaml configuration could then be mapped by the following {@literal ConfigurationProperties}
+ *
+ *
+ * {@literal @}ConfigurationProperties("security.roles")
+ * class SecurityPropertiesExtension {
+ * Map> hierarchy = new LinkedHashMap<>();
+ *
+ * //getter | setter
+ * }
+ *
+ *
+ * To define the role hierarchy just declare a {@link org.springframework.context.annotation.Bean} of
+ * type {@link RoleHierarchy} as follows:
+ *
+ *
+ * {@literal @}Bean
+ * RoleHierarchy roleHierarchy(SecurityPropertiesExtension spe) {
+ * return RoleHierarchyUtils.roleHierarchyFromMap(spe.getHierarchy());
+ * }
+ *
+ *
+ *
+ * @param roleHierarchyMapping the role name to implied role names mapping
+ * @return
+ */
+ public static RoleHierarchy roleHierarchyFromMap(Map> roleHierarchyMapping) {
+
+ StringWriter roleHierachyDescriptionBuffer = new StringWriter();
+ PrintWriter roleHierarchyDescriptionWriter = new PrintWriter(roleHierachyDescriptionBuffer);
+
+ for (Map.Entry> entry : roleHierarchyMapping.entrySet()) {
+
+ String currentRole = entry.getKey();
+ List impliedRoles = entry.getValue();
+
+ for (String impliedRole : impliedRoles) {
+ String roleMapping = currentRole + " > " + impliedRole;
+ roleHierarchyDescriptionWriter.println(roleMapping);
+ }
+ }
+
+ RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
+ roleHierarchy.setHierarchy(roleHierachyDescriptionBuffer.toString());
+ return roleHierarchy;
+ }
+}
diff --git a/core/src/test/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyUtilsTests.java b/core/src/test/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyUtilsTests.java
new file mode 100644
index 0000000000..b90e58f03b
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyUtilsTests.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * 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.security.access.hierarchicalroles;
+
+import org.junit.Test;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.springframework.security.access.hierarchicalroles.RoleHierarchyUtils.roleHierarchyFromMap;
+
+/**
+ * Tests for {@link RoleHierarchyUtils}.
+ *
+ * Copied from {@link RoleHierarchyImplTests} with adaptations for {@link RoleHierarchyUtils}.
+ *
+ * @author Thomas Darimont
+ */
+public class RoleHierarchyUtilsTests {
+
+ @Test
+ public void testRoleHierarchyWithNullOrEmptyAuthorities() {
+
+ List authorities0 = null;
+ List authorities1 = new ArrayList();
+
+ RoleHierarchy roleHierarchy = roleHierarchyFromMap(singletonMap("ROLE_A", singletonList("ROLE_B")));
+
+ assertThat(roleHierarchy.getReachableGrantedAuthorities(
+ authorities0)).isNotNull();
+ assertThat(
+ roleHierarchy.getReachableGrantedAuthorities(authorities0)).isEmpty();
+ ;
+ assertThat(roleHierarchy.getReachableGrantedAuthorities(
+ authorities1)).isNotNull();
+ assertThat(
+ roleHierarchy.getReachableGrantedAuthorities(authorities1)).isEmpty();
+ ;
+ }
+
+ @Test
+ public void testSimpleRoleHierarchy() {
+
+ List authorities0 = AuthorityUtils.createAuthorityList(
+ "ROLE_0");
+ List authorities1 = AuthorityUtils.createAuthorityList(
+ "ROLE_A");
+ List authorities2 = AuthorityUtils.createAuthorityList("ROLE_A",
+ "ROLE_B");
+
+ RoleHierarchy roleHierarchy = roleHierarchyFromMap(singletonMap("ROLE_A", singletonList("ROLE_B")));
+
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy.getReachableGrantedAuthorities(authorities0),
+ authorities0)).isTrue();
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy.getReachableGrantedAuthorities(authorities1),
+ authorities2)).isTrue();
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy.getReachableGrantedAuthorities(authorities2),
+ authorities2)).isTrue();
+ }
+
+ @Test
+ public void testTransitiveRoleHierarchies() {
+ List authorities1 = AuthorityUtils.createAuthorityList(
+ "ROLE_A");
+ List authorities2 = AuthorityUtils.createAuthorityList("ROLE_A",
+ "ROLE_B", "ROLE_C");
+ List authorities3 = AuthorityUtils.createAuthorityList("ROLE_A",
+ "ROLE_B", "ROLE_C", "ROLE_D");
+
+ RoleHierarchy roleHierarchy2Levels = roleHierarchyFromMap(new HashMap>(){
+ {
+ put("ROLE_A", asList("ROLE_B"));
+ put("ROLE_B", asList("ROLE_C"));
+ }
+ });
+
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy2Levels.getReachableGrantedAuthorities(authorities1),
+ authorities2)).isTrue();
+
+ RoleHierarchy roleHierarchy3Levels = roleHierarchyFromMap(new HashMap>(){
+ {
+ put("ROLE_A", asList("ROLE_B"));
+ put("ROLE_B", asList("ROLE_C"));
+ put("ROLE_C", asList("ROLE_D"));
+ }
+ });
+
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy3Levels.getReachableGrantedAuthorities(authorities1),
+ authorities3)).isTrue();
+ }
+
+ @Test
+ public void testComplexRoleHierarchy() {
+ List authoritiesInput1 = AuthorityUtils.createAuthorityList(
+ "ROLE_A");
+ List authoritiesOutput1 = AuthorityUtils.createAuthorityList(
+ "ROLE_A", "ROLE_B", "ROLE_C", "ROLE_D");
+ List authoritiesInput2 = AuthorityUtils.createAuthorityList(
+ "ROLE_B");
+ List authoritiesOutput2 = AuthorityUtils.createAuthorityList(
+ "ROLE_B", "ROLE_D");
+ List authoritiesInput3 = AuthorityUtils.createAuthorityList(
+ "ROLE_C");
+ List authoritiesOutput3 = AuthorityUtils.createAuthorityList(
+ "ROLE_C", "ROLE_D");
+ List authoritiesInput4 = AuthorityUtils.createAuthorityList(
+ "ROLE_D");
+ List authoritiesOutput4 = AuthorityUtils.createAuthorityList(
+ "ROLE_D");
+
+
+ RoleHierarchy roleHierarchy3LevelsMultipleRoles = roleHierarchyFromMap(new HashMap>(){
+ {
+ put("ROLE_A", asList("ROLE_B","ROLE_C"));
+ put("ROLE_B", asList("ROLE_D"));
+ put("ROLE_C", asList("ROLE_D"));
+ }
+ });
+
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy3LevelsMultipleRoles.getReachableGrantedAuthorities(authoritiesInput1),
+ authoritiesOutput1)).isTrue();
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy3LevelsMultipleRoles.getReachableGrantedAuthorities(authoritiesInput2),
+ authoritiesOutput2)).isTrue();
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy3LevelsMultipleRoles.getReachableGrantedAuthorities(authoritiesInput3),
+ authoritiesOutput3)).isTrue();
+ assertThat(HierarchicalRolesTestHelper.containTheSameGrantedAuthorities(
+ roleHierarchy3LevelsMultipleRoles.getReachableGrantedAuthorities(authoritiesInput4),
+ authoritiesOutput4)).isTrue();
+ }
+
+ @Test
+ public void testCyclesInRoleHierarchy() {
+
+ try {
+ roleHierarchyFromMap(singletonMap("ROLE_A", singletonList("ROLE_A")));
+ fail("Cycle in role hierarchy was not detected!");
+ }
+ catch (CycleInRoleHierarchyException e) {
+ }
+
+ try {
+
+ roleHierarchyFromMap(new HashMap>(){
+ {
+ put("ROLE_A", asList("ROLE_B"));
+ put("ROLE_B", asList("ROLE_A"));
+ }
+ });
+
+ fail("Cycle in role hierarchy was not detected!");
+ }
+ catch (CycleInRoleHierarchyException e) {
+ }
+
+ try {
+
+ roleHierarchyFromMap(new HashMap>(){
+ {
+ put("ROLE_A", asList("ROLE_B"));
+ put("ROLE_B", asList("ROLE_C"));
+ put("ROLE_C", asList("ROLE_A"));
+ }
+ });
+
+ fail("Cycle in role hierarchy was not detected!");
+ }
+ catch (CycleInRoleHierarchyException e) {
+ }
+
+ try {
+
+ roleHierarchyFromMap(new HashMap>(){
+ {
+ put("ROLE_A", asList("ROLE_B"));
+ put("ROLE_B", asList("ROLE_C"));
+ put("ROLE_C", asList("ROLE_E"));
+ put("ROLE_E", asList("ROLE_D"));
+ put("ROLE_D", asList("ROLE_B"));
+ }
+ });
+
+
+ fail("Cycle in role hierarchy was not detected!");
+ }
+ catch (CycleInRoleHierarchyException e) {
+ }
+ }
+
+ @Test
+ public void testNoCyclesInRoleHierarchy() {
+ RoleHierarchyImpl roleHierarchyImpl = new RoleHierarchyImpl();
+
+ try {
+ roleHierarchyImpl.setHierarchy(
+ "ROLE_A > ROLE_B\nROLE_A > ROLE_C\nROLE_C > ROLE_D\nROLE_B > ROLE_D");
+
+ roleHierarchyFromMap(new HashMap>(){
+ {
+ put("ROLE_A", asList("ROLE_B"));
+ put("ROLE_A", asList("ROLE_C"));
+ put("ROLE_C", asList("ROLE_D"));
+ put("ROLE_B", asList("ROLE_D"));
+ }
+ });
+
+ }
+ catch (CycleInRoleHierarchyException e) {
+ fail("A cycle in role hierarchy was incorrectly detected!");
+ }
+ }
+
+ @Test
+ public void testSimpleRoleHierarchyWithCustomGrantedAuthorityImplementation() {
+
+ List authorities0 = HierarchicalRolesTestHelper.createAuthorityList(
+ "ROLE_0");
+ List authorities1 = HierarchicalRolesTestHelper.createAuthorityList(
+ "ROLE_A");
+ List authorities2 = HierarchicalRolesTestHelper.createAuthorityList(
+ "ROLE_A", "ROLE_B");
+
+ RoleHierarchy roleHierarchy = roleHierarchyFromMap(singletonMap("ROLE_A", singletonList("ROLE_B")));
+
+ assertThat(
+ HierarchicalRolesTestHelper.containTheSameGrantedAuthoritiesCompareByAuthorityString(
+ roleHierarchy.getReachableGrantedAuthorities(authorities0),
+ authorities0)).isTrue();
+ assertThat(
+ HierarchicalRolesTestHelper.containTheSameGrantedAuthoritiesCompareByAuthorityString(
+ roleHierarchy.getReachableGrantedAuthorities(authorities1),
+ authorities2)).isTrue();
+ assertThat(
+ HierarchicalRolesTestHelper.containTheSameGrantedAuthoritiesCompareByAuthorityString(
+ roleHierarchy.getReachableGrantedAuthorities(authorities2),
+ authorities2)).isTrue();
+ }
+}