Преглед изворни кода

Add convenience method for constructing RoleHierarchy from Map.

Introduced `RoleHierarchyUtils` which enables convenient
construction of `RoleHierarchy` from map based representation.
Where the map key is the role name and the map value is a list
of implied role names.

Here is a small example for that in action:
https://gist.github.com/thomasdarimont/ee9fffdef1adb9243b12ad247478aad4

Fixes #3990.

Signed-off-by: Thomas Darimont <thomas.darimont@gmail.com>

Signed-off-by: Thomas Darimont <thomas.darimont@gmail.com>
Thomas Darimont пре 9 година
родитељ
комит
06c67070a6

+ 98 - 0
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.
+	 *
+	 * <p>
+	 *     Here is an example configuration of a role hierarchy configured via yaml.
+	 *     wich follows the pattern:
+	 *     {@code ROLE_NAME: List of implied role names}
+	 * </p>
+	 * <pre>
+	 * <code>
+	 *
+	 * 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
+	 * </code>
+	 * </pre>
+	 * <p>This yaml configuration could then be mapped by the following {@literal ConfigurationProperties}</p>
+	 * <pre>
+	 * <code>
+	 *   {@literal @}ConfigurationProperties("security.roles")
+	 *   class SecurityPropertiesExtension {
+	 *     Map<String, List<String>> hierarchy = new LinkedHashMap<>();
+	 *
+	 *     //getter | setter
+	 *   }
+	 * </code>
+	 * </pre>
+	 * <p>To define the role hierarchy just declare a {@link org.springframework.context.annotation.Bean} of
+	 * type {@link RoleHierarchy} as follows:</p>
+	 * <pre>
+	 * <code>
+	 *   {@literal @}Bean
+	 *   RoleHierarchy roleHierarchy(SecurityPropertiesExtension spe) {
+	 *     return RoleHierarchyUtils.roleHierarchyFromMap(spe.getHierarchy());
+	 *   }
+	 * </code>
+	 * </pre>
+	 *
+	 * @param roleHierarchyMapping the role name to implied role names mapping
+	 * @return
+	 */
+	public static RoleHierarchy roleHierarchyFromMap(Map<String, List<String>> roleHierarchyMapping) {
+
+		StringWriter roleHierachyDescriptionBuffer = new StringWriter();
+		PrintWriter roleHierarchyDescriptionWriter = new PrintWriter(roleHierachyDescriptionBuffer);
+
+		for (Map.Entry<String, List<String>> entry : roleHierarchyMapping.entrySet()) {
+
+			String currentRole = entry.getKey();
+			List<String> impliedRoles = entry.getValue();
+
+			for (String impliedRole : impliedRoles) {
+				String roleMapping = currentRole + " > " + impliedRole;
+				roleHierarchyDescriptionWriter.println(roleMapping);
+			}
+		}
+
+		RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
+		roleHierarchy.setHierarchy(roleHierachyDescriptionBuffer.toString());
+		return roleHierarchy;
+	}
+}

+ 266 - 0
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<GrantedAuthority> authorities0 = null;
+		List<GrantedAuthority> authorities1 = new ArrayList<GrantedAuthority>();
+
+		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<GrantedAuthority> authorities0 = AuthorityUtils.createAuthorityList(
+				"ROLE_0");
+		List<GrantedAuthority> authorities1 = AuthorityUtils.createAuthorityList(
+				"ROLE_A");
+		List<GrantedAuthority> 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<GrantedAuthority> authorities1 = AuthorityUtils.createAuthorityList(
+				"ROLE_A");
+		List<GrantedAuthority> authorities2 = AuthorityUtils.createAuthorityList("ROLE_A",
+				"ROLE_B", "ROLE_C");
+		List<GrantedAuthority> authorities3 = AuthorityUtils.createAuthorityList("ROLE_A",
+				"ROLE_B", "ROLE_C", "ROLE_D");
+
+		RoleHierarchy roleHierarchy2Levels = roleHierarchyFromMap(new HashMap<String, List<String>>(){
+			{
+				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<String, List<String>>(){
+			{
+				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<GrantedAuthority> authoritiesInput1 = AuthorityUtils.createAuthorityList(
+				"ROLE_A");
+		List<GrantedAuthority> authoritiesOutput1 = AuthorityUtils.createAuthorityList(
+				"ROLE_A", "ROLE_B", "ROLE_C", "ROLE_D");
+		List<GrantedAuthority> authoritiesInput2 = AuthorityUtils.createAuthorityList(
+				"ROLE_B");
+		List<GrantedAuthority> authoritiesOutput2 = AuthorityUtils.createAuthorityList(
+				"ROLE_B", "ROLE_D");
+		List<GrantedAuthority> authoritiesInput3 = AuthorityUtils.createAuthorityList(
+				"ROLE_C");
+		List<GrantedAuthority> authoritiesOutput3 = AuthorityUtils.createAuthorityList(
+				"ROLE_C", "ROLE_D");
+		List<GrantedAuthority> authoritiesInput4 = AuthorityUtils.createAuthorityList(
+				"ROLE_D");
+		List<GrantedAuthority> authoritiesOutput4 = AuthorityUtils.createAuthorityList(
+				"ROLE_D");
+
+
+		RoleHierarchy roleHierarchy3LevelsMultipleRoles = roleHierarchyFromMap(new HashMap<String, List<String>>(){
+			{
+				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<String, List<String>>(){
+				{
+					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<String, List<String>>(){
+				{
+					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<String, List<String>>(){
+				{
+					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<String, List<String>>(){
+				{
+					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<GrantedAuthority> authorities0 = HierarchicalRolesTestHelper.createAuthorityList(
+				"ROLE_0");
+		List<GrantedAuthority> authorities1 = HierarchicalRolesTestHelper.createAuthorityList(
+				"ROLE_A");
+		List<GrantedAuthority> 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();
+	}
+}