Skip to content

Commit 096dfd4

Browse files
committed
Add AllAuthoritiesAuthorizationManager
Closes gh-17916
1 parent fdd2a91 commit 096dfd4

File tree

3 files changed

+279
-0
lines changed

3 files changed

+279
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authorization;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.function.Supplier;
23+
24+
import org.jspecify.annotations.Nullable;
25+
26+
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
27+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
28+
import org.springframework.security.core.Authentication;
29+
import org.springframework.security.core.GrantedAuthority;
30+
import org.springframework.security.core.authority.AuthorityUtils;
31+
import org.springframework.util.Assert;
32+
33+
/**
34+
* An {@link AuthorizationManager} that determines if the current user is authorized by
35+
* evaluating if the {@link Authentication} contains all the specified authorities.
36+
*
37+
* @author Rob Winch
38+
* @since 7.0
39+
* @see AuthoritiesAuthorizationManager
40+
*/
41+
public final class AllAuthoritiesAuthorizationManager<T> implements AuthorizationManager<T> {
42+
43+
private static final String ROLE_PREFIX = "ROLE_";
44+
45+
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
46+
47+
private final List<String> requiredAuthorities;
48+
49+
/**
50+
* Creates a new instance.
51+
* @param requiredAuthorities the authorities that are required.
52+
*/
53+
private AllAuthoritiesAuthorizationManager(String... requiredAuthorities) {
54+
Assert.notEmpty(requiredAuthorities, "requiredAuthorities cannot be empty");
55+
this.requiredAuthorities = Arrays.asList(requiredAuthorities);
56+
}
57+
58+
/**
59+
* Sets the {@link RoleHierarchy} to be used. Default is {@link NullRoleHierarchy}.
60+
* Cannot be null.
61+
* @param roleHierarchy the {@link RoleHierarchy} to use
62+
*/
63+
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
64+
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
65+
this.roleHierarchy = roleHierarchy;
66+
}
67+
68+
/**
69+
* Determines if the current user is authorized by evaluating if the
70+
* {@link Authentication} contains any of specified authorities.
71+
* @param authentication the {@link Supplier} of the {@link Authentication} to check
72+
* @param object the object to check authorization on (not used).
73+
* @return an {@link AuthorityAuthorizationDecision}
74+
*/
75+
@Override
76+
public AuthorityAuthorizationDecision authorize(Supplier<? extends @Nullable Authentication> authentication,
77+
T object) {
78+
List<String> authenticatedAuthorities = getGrantedAuthorities(authentication.get());
79+
List<String> missingAuthorities = new ArrayList<>(this.requiredAuthorities);
80+
missingAuthorities.removeIf(authenticatedAuthorities::contains);
81+
return new AuthorityAuthorizationDecision(missingAuthorities.isEmpty(),
82+
AuthorityUtils.createAuthorityList(missingAuthorities));
83+
}
84+
85+
private List<String> getGrantedAuthorities(Authentication authentication) {
86+
return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities())
87+
.stream()
88+
.map(GrantedAuthority::getAuthority)
89+
.toList();
90+
}
91+
92+
/**
93+
* Creates an instance of {@link AllAuthoritiesAuthorizationManager} with the provided
94+
* authorities.
95+
* @param roles the authorities to check for prefixed with "ROLE_". Each role should
96+
* not start with "ROLE_" since it is automatically prepended already.
97+
* @param <T> the type of object being authorized
98+
* @return the new instance
99+
*/
100+
public static <T> AllAuthoritiesAuthorizationManager<T> hasAllRoles(String... roles) {
101+
return hasAllPrefixedAuthorities(ROLE_PREFIX, roles);
102+
}
103+
104+
/**
105+
* Creates an instance of {@link AllAuthoritiesAuthorizationManager} with the provided
106+
* authorities.
107+
* @param prefix the prefix for <code>authorities</code>
108+
* @param authorities the authorities to check for prefixed with <code>prefix</code>
109+
* @param <T> the type of object being authorized
110+
* @return the new instance
111+
*/
112+
public static <T> AllAuthoritiesAuthorizationManager<T> hasAllPrefixedAuthorities(String prefix,
113+
String... authorities) {
114+
Assert.notNull(prefix, "rolePrefix cannot be null");
115+
Assert.notEmpty(authorities, "roles cannot be empty");
116+
Assert.noNullElements(authorities, "roles cannot contain null values");
117+
return hasAllAuthorities(toNamedRolesArray(prefix, authorities));
118+
}
119+
120+
/**
121+
* Creates an instance of {@link AllAuthoritiesAuthorizationManager} with the provided
122+
* authorities.
123+
* @param authorities the authorities to check for
124+
* @param <T> the type of object being authorized
125+
* @return the new instance
126+
*/
127+
public static <T> AllAuthoritiesAuthorizationManager<T> hasAllAuthorities(String... authorities) {
128+
Assert.notEmpty(authorities, "authorities cannot be empty");
129+
Assert.noNullElements(authorities, "authorities cannot contain null values");
130+
return new AllAuthoritiesAuthorizationManager<>(authorities);
131+
}
132+
133+
private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
134+
String[] result = new String[roles.length];
135+
for (int i = 0; i < roles.length; i++) {
136+
String role = roles[i];
137+
Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> role + " should not start with "
138+
+ rolePrefix + " since " + rolePrefix
139+
+ " is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead.");
140+
result[i] = rolePrefix + role;
141+
}
142+
return result;
143+
}
144+
145+
}

core/src/main/java/org/springframework/security/authorization/AuthoritiesAuthorizationManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
*
3535
* @author Evgeniy Cheban
3636
* @since 6.1
37+
* @see AllAuthoritiesAuthorizationManager
3738
*/
3839
public final class AuthoritiesAuthorizationManager implements AuthorizationManager<Collection<String>> {
3940

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authorization;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.mockito.ArgumentCaptor;
25+
import org.mockito.Captor;
26+
import org.mockito.Mock;
27+
import org.mockito.junit.jupiter.MockitoExtension;
28+
29+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
30+
import org.springframework.security.authentication.TestingAuthenticationToken;
31+
import org.springframework.security.core.Authentication;
32+
import org.springframework.security.core.GrantedAuthority;
33+
import org.springframework.security.core.authority.AuthorityUtils;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
37+
import static org.mockito.ArgumentMatchers.any;
38+
import static org.mockito.BDDMockito.given;
39+
import static org.mockito.Mockito.verify;
40+
41+
@ExtendWith(MockitoExtension.class)
42+
class AllAuthoritiesAuthorizationManagerTests {
43+
44+
public static final String ROLE_USER = "ROLE_USER";
45+
46+
public static final String ROLE_ADMIN = "ROLE_ADMIN";
47+
48+
@Mock
49+
private RoleHierarchy roleHierarchy;
50+
51+
@Captor
52+
private ArgumentCaptor<Collection<? extends GrantedAuthority>> authoritiesCaptor;
53+
54+
@Test
55+
void hasAllAuthoritiesWhenNullAuthoritiesThenIllegalArgumentException() {
56+
String[] requiredAuthorities = null;
57+
assertThatIllegalArgumentException()
58+
.isThrownBy(() -> AllAuthoritiesAuthorizationManager.hasAllAuthorities(requiredAuthorities));
59+
}
60+
61+
@Test
62+
void hasAllAuthortiesWhenEmptyAuthoritiesThenIllegalArgumentException() {
63+
assertThatIllegalArgumentException()
64+
.isThrownBy(() -> AllAuthoritiesAuthorizationManager.hasAllAuthorities((new String[0])));
65+
}
66+
67+
@Test
68+
void authorizeWhenGranted() {
69+
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
70+
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_USER);
71+
assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
72+
}
73+
74+
@Test
75+
void hasAllRolesAuthorizeWhenGranted() {
76+
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
77+
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllRoles("USER");
78+
assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
79+
}
80+
81+
@Test
82+
void hasAllPrefixedAuthoritiesAuthorizeWhenGranted() {
83+
String prefix = "PREFIX_";
84+
String authority1 = "AUTHORITY1";
85+
String authority2 = "AUTHORITY2";
86+
Authentication authentication = new TestingAuthenticationToken("user", "password", prefix + authority1,
87+
prefix + authority2);
88+
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager
89+
.hasAllPrefixedAuthorities(prefix, authority1, authority2);
90+
assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
91+
}
92+
93+
@Test
94+
void authorizeWhenSingleMissingThenDenied() {
95+
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
96+
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_ADMIN);
97+
assertThat(manager.authorize(() -> authentication, "").isGranted()).isFalse();
98+
}
99+
100+
@Test
101+
void authorizeWhenMultipleMissingOneThenDenied() {
102+
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
103+
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_ADMIN,
104+
ROLE_USER);
105+
AuthorityAuthorizationDecision result = manager.authorize(() -> authentication, "");
106+
assertThat(result.isGranted()).isFalse();
107+
assertThat(result.getAuthorities()).hasSize(1);
108+
assertThat(new ArrayList<>(result.getAuthorities()).get(0).getAuthority()).isEqualTo(ROLE_ADMIN);
109+
}
110+
111+
@Test
112+
void setRoleHierarchyWhenNullThenIllegalArgumentException() {
113+
AllAuthoritiesAuthorizationManager<?> manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_USER);
114+
assertThatIllegalArgumentException().isThrownBy(() -> manager.setRoleHierarchy(null));
115+
}
116+
117+
@Test
118+
void setRoleHierarchyThenUsesResult() {
119+
Collection result = AuthorityUtils.createAuthorityList(ROLE_USER, ROLE_ADMIN);
120+
given(this.roleHierarchy.getReachableGrantedAuthorities(any())).willReturn(result);
121+
AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager
122+
.hasAllAuthorities(ROLE_USER);
123+
manager.setRoleHierarchy(this.roleHierarchy);
124+
125+
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
126+
127+
AuthorityAuthorizationDecision authz = manager.authorize(() -> authentication, "");
128+
assertThat(authz.isGranted()).isTrue();
129+
verify(this.roleHierarchy).getReachableGrantedAuthorities(this.authoritiesCaptor.capture());
130+
assertThat(this.authoritiesCaptor.getValue()).map(GrantedAuthority::getAuthority).contains(ROLE_USER);
131+
}
132+
133+
}

0 commit comments

Comments
 (0)