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

Add AllAuthoritiesAuthorizationManager

Closes gh-17916
Rob Winch пре 2 недеља
родитељ
комит
096dfd4046

+ 145 - 0
core/src/main/java/org/springframework/security/authorization/AllAuthoritiesAuthorizationManager.java

@@ -0,0 +1,145 @@
+/*
+ * Copyright 2004-present 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
+ *
+ *      https://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.authorization;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthorizationManager} that determines if the current user is authorized by
+ * evaluating if the {@link Authentication} contains all the specified authorities.
+ *
+ * @author Rob Winch
+ * @since 7.0
+ * @see AuthoritiesAuthorizationManager
+ */
+public final class AllAuthoritiesAuthorizationManager<T> implements AuthorizationManager<T> {
+
+	private static final String ROLE_PREFIX = "ROLE_";
+
+	private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
+
+	private final List<String> requiredAuthorities;
+
+	/**
+	 * Creates a new instance.
+	 * @param requiredAuthorities the authorities that are required.
+	 */
+	private AllAuthoritiesAuthorizationManager(String... requiredAuthorities) {
+		Assert.notEmpty(requiredAuthorities, "requiredAuthorities cannot be empty");
+		this.requiredAuthorities = Arrays.asList(requiredAuthorities);
+	}
+
+	/**
+	 * Sets the {@link RoleHierarchy} to be used. Default is {@link NullRoleHierarchy}.
+	 * Cannot be null.
+	 * @param roleHierarchy the {@link RoleHierarchy} to use
+	 */
+	public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
+		Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
+		this.roleHierarchy = roleHierarchy;
+	}
+
+	/**
+	 * Determines if the current user is authorized by evaluating if the
+	 * {@link Authentication} contains any of specified authorities.
+	 * @param authentication the {@link Supplier} of the {@link Authentication} to check
+	 * @param object the object to check authorization on (not used).
+	 * @return an {@link AuthorityAuthorizationDecision}
+	 */
+	@Override
+	public AuthorityAuthorizationDecision authorize(Supplier<? extends @Nullable Authentication> authentication,
+			T object) {
+		List<String> authenticatedAuthorities = getGrantedAuthorities(authentication.get());
+		List<String> missingAuthorities = new ArrayList<>(this.requiredAuthorities);
+		missingAuthorities.removeIf(authenticatedAuthorities::contains);
+		return new AuthorityAuthorizationDecision(missingAuthorities.isEmpty(),
+				AuthorityUtils.createAuthorityList(missingAuthorities));
+	}
+
+	private List<String> getGrantedAuthorities(Authentication authentication) {
+		return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities())
+			.stream()
+			.map(GrantedAuthority::getAuthority)
+			.toList();
+	}
+
+	/**
+	 * Creates an instance of {@link AllAuthoritiesAuthorizationManager} with the provided
+	 * authorities.
+	 * @param roles the authorities to check for prefixed with "ROLE_". Each role should
+	 * not start with "ROLE_" since it is automatically prepended already.
+	 * @param <T> the type of object being authorized
+	 * @return the new instance
+	 */
+	public static <T> AllAuthoritiesAuthorizationManager<T> hasAllRoles(String... roles) {
+		return hasAllPrefixedAuthorities(ROLE_PREFIX, roles);
+	}
+
+	/**
+	 * Creates an instance of {@link AllAuthoritiesAuthorizationManager} with the provided
+	 * authorities.
+	 * @param prefix the prefix for <code>authorities</code>
+	 * @param authorities the authorities to check for prefixed with <code>prefix</code>
+	 * @param <T> the type of object being authorized
+	 * @return the new instance
+	 */
+	public static <T> AllAuthoritiesAuthorizationManager<T> hasAllPrefixedAuthorities(String prefix,
+			String... authorities) {
+		Assert.notNull(prefix, "rolePrefix cannot be null");
+		Assert.notEmpty(authorities, "roles cannot be empty");
+		Assert.noNullElements(authorities, "roles cannot contain null values");
+		return hasAllAuthorities(toNamedRolesArray(prefix, authorities));
+	}
+
+	/**
+	 * Creates an instance of {@link AllAuthoritiesAuthorizationManager} with the provided
+	 * authorities.
+	 * @param authorities the authorities to check for
+	 * @param <T> the type of object being authorized
+	 * @return the new instance
+	 */
+	public static <T> AllAuthoritiesAuthorizationManager<T> hasAllAuthorities(String... authorities) {
+		Assert.notEmpty(authorities, "authorities cannot be empty");
+		Assert.noNullElements(authorities, "authorities cannot contain null values");
+		return new AllAuthoritiesAuthorizationManager<>(authorities);
+	}
+
+	private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
+		String[] result = new String[roles.length];
+		for (int i = 0; i < roles.length; i++) {
+			String role = roles[i];
+			Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> role + " should not start with "
+					+ rolePrefix + " since " + rolePrefix
+					+ " is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead.");
+			result[i] = rolePrefix + role;
+		}
+		return result;
+	}
+
+}

+ 1 - 0
core/src/main/java/org/springframework/security/authorization/AuthoritiesAuthorizationManager.java

@@ -34,6 +34,7 @@ import org.springframework.util.Assert;
  *
  * @author Evgeniy Cheban
  * @since 6.1
+ * @see AllAuthoritiesAuthorizationManager
  */
 public final class AuthoritiesAuthorizationManager implements AuthorizationManager<Collection<String>> {
 

+ 133 - 0
core/src/test/java/org/springframework/security/authorization/AllAuthoritiesAuthorizationManagerTests.java

@@ -0,0 +1,133 @@
+/*
+ * Copyright 2004-present 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
+ *
+ *      https://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.authorization;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class AllAuthoritiesAuthorizationManagerTests {
+
+	public static final String ROLE_USER = "ROLE_USER";
+
+	public static final String ROLE_ADMIN = "ROLE_ADMIN";
+
+	@Mock
+	private RoleHierarchy roleHierarchy;
+
+	@Captor
+	private ArgumentCaptor<Collection<? extends GrantedAuthority>> authoritiesCaptor;
+
+	@Test
+	void hasAllAuthoritiesWhenNullAuthoritiesThenIllegalArgumentException() {
+		String[] requiredAuthorities = null;
+		assertThatIllegalArgumentException()
+			.isThrownBy(() -> AllAuthoritiesAuthorizationManager.hasAllAuthorities(requiredAuthorities));
+	}
+
+	@Test
+	void hasAllAuthortiesWhenEmptyAuthoritiesThenIllegalArgumentException() {
+		assertThatIllegalArgumentException()
+			.isThrownBy(() -> AllAuthoritiesAuthorizationManager.hasAllAuthorities((new String[0])));
+	}
+
+	@Test
+	void authorizeWhenGranted() {
+		Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
+		AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_USER);
+		assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
+	}
+
+	@Test
+	void hasAllRolesAuthorizeWhenGranted() {
+		Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
+		AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllRoles("USER");
+		assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
+	}
+
+	@Test
+	void hasAllPrefixedAuthoritiesAuthorizeWhenGranted() {
+		String prefix = "PREFIX_";
+		String authority1 = "AUTHORITY1";
+		String authority2 = "AUTHORITY2";
+		Authentication authentication = new TestingAuthenticationToken("user", "password", prefix + authority1,
+				prefix + authority2);
+		AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager
+			.hasAllPrefixedAuthorities(prefix, authority1, authority2);
+		assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
+	}
+
+	@Test
+	void authorizeWhenSingleMissingThenDenied() {
+		Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
+		AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_ADMIN);
+		assertThat(manager.authorize(() -> authentication, "").isGranted()).isFalse();
+	}
+
+	@Test
+	void authorizeWhenMultipleMissingOneThenDenied() {
+		Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
+		AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_ADMIN,
+				ROLE_USER);
+		AuthorityAuthorizationDecision result = manager.authorize(() -> authentication, "");
+		assertThat(result.isGranted()).isFalse();
+		assertThat(result.getAuthorities()).hasSize(1);
+		assertThat(new ArrayList<>(result.getAuthorities()).get(0).getAuthority()).isEqualTo(ROLE_ADMIN);
+	}
+
+	@Test
+	void setRoleHierarchyWhenNullThenIllegalArgumentException() {
+		AllAuthoritiesAuthorizationManager<?> manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_USER);
+		assertThatIllegalArgumentException().isThrownBy(() -> manager.setRoleHierarchy(null));
+	}
+
+	@Test
+	void setRoleHierarchyThenUsesResult() {
+		Collection result = AuthorityUtils.createAuthorityList(ROLE_USER, ROLE_ADMIN);
+		given(this.roleHierarchy.getReachableGrantedAuthorities(any())).willReturn(result);
+		AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager
+			.hasAllAuthorities(ROLE_USER);
+		manager.setRoleHierarchy(this.roleHierarchy);
+
+		Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
+
+		AuthorityAuthorizationDecision authz = manager.authorize(() -> authentication, "");
+		assertThat(authz.isGranted()).isTrue();
+		verify(this.roleHierarchy).getReachableGrantedAuthorities(this.authoritiesCaptor.capture());
+		assertThat(this.authoritiesCaptor.getValue()).map(GrantedAuthority::getAuthority).contains(ROLE_USER);
+	}
+
+}