Skip to content

Commit 9eaadcc

Browse files
committed
Add hasAll(Roles|Authorities) to SecurityExpressionRoot
This adds support for hasAllRoles and hasAllAuthorities to method security expressions. Issue spring-projectsgh-17932
1 parent bce8049 commit 9eaadcc

File tree

7 files changed

+89
-2
lines changed

7 files changed

+89
-2
lines changed

config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ public interface MethodSecurityService {
9393
@PreAuthorize("hasRole('USER')")
9494
void preAuthorizeUser();
9595

96+
@PreAuthorize("hasAllRoles('USER', 'ADMIN')")
97+
void hasAllRolesUserAdmin();
98+
99+
@PreAuthorize("hasAllAuthorities('ROLE_USER', 'ROLE_ADMIN')")
100+
void hasAllAuthoritiesRoleUserRoleAdmin();
101+
96102
@PreAuthorize("hasPermission(#object,'read')")
97103
String hasPermission(String object);
98104

config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,12 @@ public String checkCustomResult(boolean result) {
203203
return "ok";
204204
}
205205

206+
@Override
207+
public void hasAllRolesUserAdmin() {
208+
}
209+
210+
@Override
211+
public void hasAllAuthoritiesRoleUserRoleAdmin() {
212+
}
213+
206214
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,52 @@ public void preAuthorizeAdminWhenSecurityContextHolderStrategyThenUses() {
282282
verify(strategy, atLeastOnce()).getContext();
283283
}
284284

285+
@WithMockUser(roles = { "ADMIN", "USER" })
286+
@Test
287+
public void hasAllAuthoritiesRoleUserRoleAdminWhenGranted() {
288+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
289+
this.methodSecurityService.hasAllAuthoritiesRoleUserRoleAdmin();
290+
}
291+
292+
@WithMockUser(roles = { "USER" })
293+
@Test
294+
public void hasAllAuthoritiesRoleUserRoleAdminWhenMissingOneThenDenied() {
295+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
296+
assertThatExceptionOfType(AccessDeniedException.class)
297+
.isThrownBy(this.methodSecurityService::hasAllAuthoritiesRoleUserRoleAdmin);
298+
}
299+
300+
@WithMockUser(roles = { "OTHER" })
301+
@Test
302+
public void hasAllAuthoritiesRoleUserRoleAdminWhenAllThenDenied() {
303+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
304+
assertThatExceptionOfType(AccessDeniedException.class)
305+
.isThrownBy(this.methodSecurityService::hasAllAuthoritiesRoleUserRoleAdmin);
306+
}
307+
308+
@WithMockUser(roles = { "ADMIN", "USER" })
309+
@Test
310+
public void hasAllRolesRoleUserRoleAdminWhenGranted() {
311+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
312+
this.methodSecurityService.hasAllRolesUserAdmin();
313+
}
314+
315+
@WithMockUser(roles = { "USER" })
316+
@Test
317+
public void hasAllRolesRoleUserRoleAdminWhenMissingOneThenDenied() {
318+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
319+
assertThatExceptionOfType(AccessDeniedException.class)
320+
.isThrownBy(this.methodSecurityService::hasAllRolesUserAdmin);
321+
}
322+
323+
@WithMockUser(roles = { "OTHER" })
324+
@Test
325+
public void hasAllRolesRoleUserRoleAdminWhenAllThenDenied() {
326+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
327+
assertThatExceptionOfType(AccessDeniedException.class)
328+
.isThrownBy(this.methodSecurityService::hasAllRolesUserAdmin);
329+
}
330+
285331
@WithMockUser(authorities = "PREFIX_ADMIN")
286332
@Test
287333
public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {

core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ public final boolean hasAnyAuthority(String... authorities) {
124124
return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities));
125125
}
126126

127+
public final boolean hasAllAuthorities(String... authorities) {
128+
AuthorizationManager<T> manager = this.authorizationManagerFactory.hasAllAuthorities(authorities);
129+
return isGranted(manager);
130+
}
131+
127132
@Override
128133
public final boolean hasRole(String role) {
129134
if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T>) {
@@ -155,6 +160,11 @@ public final boolean hasAnyRole(String... roles) {
155160
return isGranted(this.authorizationManagerFactory.hasAnyRole(roles));
156161
}
157162

163+
public final boolean hasAllRoles(String... roles) {
164+
AuthorizationManager<T> manager = this.authorizationManagerFactory.hasAllRoles(roles);
165+
return isGranted(manager);
166+
}
167+
158168
@Override
159169
public final Authentication getAuthentication() {
160170
return this.authentication.get();

core/src/test/java/org/springframework/security/access/expression/SecurityExpressionRootTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,29 @@ public void hasAnyRoleNullPrefixDoesNotAddsDefaultPrefix() {
128128
assertThat(this.root.hasAnyRole("ROLE_A")).isTrue();
129129
}
130130

131+
@Test
132+
public void hasAllRoles() {
133+
assertThat(this.root.hasAllRoles("A")).isTrue();
134+
assertThat(this.root.hasAllRoles("A", "B")).isTrue();
135+
assertThat(this.root.hasAllRoles("NO")).isFalse();
136+
assertThat(this.root.hasAllRoles("A", "NO")).isFalse();
137+
}
138+
131139
@Test
132140
public void hasAuthorityDoesNotAddDefaultPrefix() {
133141
assertThat(this.root.hasAuthority("A")).isFalse();
134142
assertThat(this.root.hasAnyAuthority("NO", "A")).isFalse();
135143
assertThat(this.root.hasAnyAuthority("ROLE_A", "NOT")).isTrue();
136144
}
137145

146+
@Test
147+
public void hasAllAuthorities() {
148+
assertThat(this.root.hasAllAuthorities("ROLE_A")).isTrue();
149+
assertThat(this.root.hasAllAuthorities("ROLE_A", "ROLE_B")).isTrue();
150+
assertThat(this.root.hasAllAuthorities("ROLE_NO")).isFalse();
151+
assertThat(this.root.hasAllAuthorities("ROLE_A", "ROLE_NO")).isFalse();
152+
}
153+
138154
@Test
139155
void isAuthenticatedWhenAuthenticatedNullThenException() {
140156
this.root = new SecurityExpressionRoot((Authentication) null) {

docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
[[jc-method]]
32
= Method Security
43
:figures: servlet/authorization
@@ -1828,6 +1827,8 @@ What follows is a quick overview of the most common methods:
18281827
* `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
18291828
* `hasAnyAuthority` - The method requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
18301829
* `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
1830+
* `hasAllAuthorities` - The method requires that the `Authentication` have ``GrantedAuthority``s that matches all of the given values
1831+
* `hasAllRoles` - A shortcut for `hasAllAuthorities` that prefixes `ROLE_` or whatever is configured as the default prefix
18311832
* `hasPermission` - A hook into your `PermissionEvaluator` instance for doing object-level authorization
18321833

18331834
And here is a brief look at the most common fields:

docs/modules/ROOT/pages/whats-new.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Each section that follows will indicate the more notable removals as well as the
1616
== Core
1717

1818
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
19-
* Added javadoc:org.springframework.security.authorization.AllAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.AllAuthoritiesReactiveAuthorizationManager[] along with corresponding methods for xref:servlet/authorization/authorize-http-requests.adoc#authorize-requests[Authorizing `HttpServletRequests`]
19+
* Added javadoc:org.springframework.security.authorization.AllAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.AllAuthoritiesReactiveAuthorizationManager[] along with corresponding methods for xref:servlet/authorization/authorize-http-requests.adoc#authorize-requests[Authorizing `HttpServletRequests`] and xref:servlet/authorization/method-security.adoc#using-authorization-expression-fields-and-methods[method security expressions].
2020
* Added xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`] for creating `AuthorizationManager` instances in xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] authorization components
2121
* Added `Authentication.Builder` for mutating and merging `Authentication` instances
2222
* Moved Access API (`AccessDecisionManager`, `AccessDecisionVoter`, etc.) to a new module, `spring-security-access`

0 commit comments

Comments
 (0)