7 changed files with 333 additions and 8 deletions
@ -0,0 +1,158 @@
@@ -0,0 +1,158 @@
|
||||
/* |
||||
* 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.Collections; |
||||
import java.util.List; |
||||
import java.util.function.Supplier; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
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; |
||||
|
||||
/** |
||||
* A {@link ReactiveAuthorizationManager} 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 AuthorityReactiveAuthorizationManager |
||||
*/ |
||||
public final class AllAuthoritiesReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> { |
||||
|
||||
private static final String ROLE_PREFIX = "ROLE_"; |
||||
|
||||
private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); |
||||
|
||||
private final List<String> requiredAuthorities; |
||||
|
||||
private final AuthorityAuthorizationDecision defaultDecision; |
||||
|
||||
/** |
||||
* Creates a new instance. |
||||
* @param requiredAuthorities the authorities that are required. |
||||
*/ |
||||
private AllAuthoritiesReactiveAuthorizationManager(String... requiredAuthorities) { |
||||
Assert.notEmpty(requiredAuthorities, "requiredAuthorities cannot be empty"); |
||||
this.requiredAuthorities = Arrays.asList(requiredAuthorities); |
||||
this.defaultDecision = new AuthorityAuthorizationDecision(false, |
||||
AuthorityUtils.createAuthorityList(this.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 Mono<AuthorizationResult> authorize(Mono<Authentication> authentication, T object) { |
||||
// @formatter:off
|
||||
return authentication |
||||
.filter(Authentication::isAuthenticated) |
||||
.map(this::getGrantedAuthorities) |
||||
.defaultIfEmpty(Collections.emptyList()) |
||||
.map((authenticatedAuthorities) -> { |
||||
List<String> missingAuthorities = new ArrayList<>(this.requiredAuthorities); |
||||
missingAuthorities.removeIf(authenticatedAuthorities::contains); |
||||
return new AuthorityAuthorizationDecision(missingAuthorities.isEmpty(), |
||||
AuthorityUtils.createAuthorityList(missingAuthorities)); |
||||
}); |
||||
// @formatter:on
|
||||
|
||||
} |
||||
|
||||
private List<String> getGrantedAuthorities(Authentication authentication) { |
||||
return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()) |
||||
.stream() |
||||
.map(GrantedAuthority::getAuthority) |
||||
.toList(); |
||||
} |
||||
|
||||
/** |
||||
* Creates an instance of {@link AllAuthoritiesReactiveAuthorizationManager} 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> AllAuthoritiesReactiveAuthorizationManager<T> hasAllRoles(String... roles) { |
||||
return hasAllPrefixedAuthorities(ROLE_PREFIX, roles); |
||||
} |
||||
|
||||
/** |
||||
* Creates an instance of {@link AllAuthoritiesReactiveAuthorizationManager} 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> AllAuthoritiesReactiveAuthorizationManager<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 AllAuthoritiesReactiveAuthorizationManager} 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> AllAuthoritiesReactiveAuthorizationManager<T> hasAllAuthorities(String... authorities) { |
||||
Assert.notEmpty(authorities, "authorities cannot be empty"); |
||||
Assert.noNullElements(authorities, "authorities cannot contain null values"); |
||||
return new AllAuthoritiesReactiveAuthorizationManager<>(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; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,151 @@
@@ -0,0 +1,151 @@
|
||||
/* |
||||
* 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 reactor.core.publisher.Mono; |
||||
|
||||
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 AllAuthoritiesReactiveAuthorizationManagerTests { |
||||
|
||||
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(() -> AllAuthoritiesReactiveAuthorizationManager.hasAllAuthorities(requiredAuthorities)); |
||||
} |
||||
|
||||
@Test |
||||
void hasAllAuthortiesWhenEmptyAuthoritiesThenIllegalArgumentException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> AllAuthoritiesReactiveAuthorizationManager.hasAllAuthorities((new String[0]))); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenGranted() { |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER); |
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.hasAllAuthorities(ROLE_USER); |
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenNotAuthenticated() { |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER); |
||||
authentication.setAuthenticated(false); |
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.hasAllAuthorities(ROLE_USER); |
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void hasAllRolesAuthorizeWhenGranted() { |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER); |
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.hasAllRoles("USER"); |
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void hasAllPrefixedAuthoritiesAuthorizeWhenGranted() { |
||||
String prefix = "PREFIX_"; |
||||
String authority1 = "AUTHORITY1"; |
||||
String authority2 = "AUTHORITY2"; |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", prefix + authority1, |
||||
prefix + authority2); |
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.hasAllPrefixedAuthorities(prefix, authority1, authority2); |
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenSingleMissingThenDenied() { |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER); |
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.hasAllAuthorities(ROLE_ADMIN); |
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenMultipleMissingOneThenDenied() { |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER); |
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.hasAllAuthorities(ROLE_ADMIN, ROLE_USER); |
||||
AuthorityAuthorizationDecision result = manager.authorize(Mono.just(authentication), "") |
||||
.cast(AuthorityAuthorizationDecision.class) |
||||
.block(); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getAuthorities()).hasSize(1); |
||||
assertThat(new ArrayList<>(result.getAuthorities()).get(0).getAuthority()).isEqualTo(ROLE_ADMIN); |
||||
} |
||||
|
||||
@Test |
||||
void setRoleHierarchyWhenNullThenIllegalArgumentException() { |
||||
AllAuthoritiesReactiveAuthorizationManager<?> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.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); |
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager |
||||
.hasAllAuthorities(ROLE_USER); |
||||
manager.setRoleHierarchy(this.roleHierarchy); |
||||
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER); |
||||
|
||||
AuthorityAuthorizationDecision authz = manager.authorize(Mono.just(authentication), "") |
||||
.cast(AuthorityAuthorizationDecision.class) |
||||
.block(); |
||||
assertThat(authz.isGranted()).isTrue(); |
||||
verify(this.roleHierarchy).getReachableGrantedAuthorities(this.authoritiesCaptor.capture()); |
||||
assertThat(this.authoritiesCaptor.getValue()).map(GrantedAuthority::getAuthority).contains(ROLE_USER); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue