From 855282ac3be1dc510d25b45c65925344b0da8e31 Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Fri, 2 Dec 2022 02:40:27 +0100 Subject: [PATCH] Add Authority String AuthorizationManager Closes gh-12231 --- .../AuthoritiesAuthorizationManager.java | 81 +++++++++++++++++ .../AuthorityAuthorizationManager.java | 36 ++------ .../core/authority/AuthorityUtils.java | 17 +++- .../AuthoritiesAuthorizationManagerTests.java | 87 +++++++++++++++++++ .../AuthorityAuthorizationManagerTests.java | 4 +- .../core/authority/AuthorityUtilsTests.java | 14 ++- 6 files changed, 206 insertions(+), 33 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/authorization/AuthoritiesAuthorizationManager.java create mode 100644 core/src/test/java/org/springframework/security/authorization/AuthoritiesAuthorizationManagerTests.java diff --git a/core/src/main/java/org/springframework/security/authorization/AuthoritiesAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/AuthoritiesAuthorizationManager.java new file mode 100644 index 0000000000..eedb9d8671 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/AuthoritiesAuthorizationManager.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2022 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.Collection; +import java.util.function.Supplier; + +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 any of the specified authorities. + * + * @author Evgeniy Cheban + * @since 6.1 + */ +public final class AuthoritiesAuthorizationManager implements AuthorizationManager> { + + private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); + + /** + * 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 authorities the collection of authority strings to check + * @return an {@link AuthorityAuthorizationDecision} + */ + @Override + public AuthorityAuthorizationDecision check(Supplier authentication, + Collection authorities) { + boolean granted = isGranted(authentication.get(), authorities); + return new AuthorityAuthorizationDecision(granted, AuthorityUtils.createAuthorityList(authorities)); + } + + private boolean isGranted(Authentication authentication, Collection authorities) { + return authentication != null && isAuthorized(authentication, authorities); + } + + private boolean isAuthorized(Authentication authentication, Collection authorities) { + for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) { + if (authorities.contains(grantedAuthority.getAuthority())) { + return true; + } + } + return false; + } + + private Collection getGrantedAuthorities(Authentication authentication) { + return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()); + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java index 4a180cf1aa..f266fa3663 100644 --- a/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java @@ -16,16 +16,14 @@ package org.springframework.security.authorization; -import java.util.Collection; -import java.util.List; +import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; 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; /** @@ -40,12 +38,12 @@ public final class AuthorityAuthorizationManager implements AuthorizationMana private static final String ROLE_PREFIX = "ROLE_"; - private final List authorities; + private final AuthoritiesAuthorizationManager delegate = new AuthoritiesAuthorizationManager(); - private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); + private final Set authorities; private AuthorityAuthorizationManager(String... authorities) { - this.authorities = AuthorityUtils.createAuthorityList(authorities); + this.authorities = new HashSet<>(Arrays.asList(authorities)); } /** @@ -55,8 +53,7 @@ public final class AuthorityAuthorizationManager implements AuthorizationMana * @since 5.8 */ public void setRoleHierarchy(RoleHierarchy roleHierarchy) { - Assert.notNull(roleHierarchy, "roleHierarchy cannot be null"); - this.roleHierarchy = roleHierarchy; + this.delegate.setRoleHierarchy(roleHierarchy); } /** @@ -139,26 +136,7 @@ public final class AuthorityAuthorizationManager implements AuthorizationMana */ @Override public AuthorizationDecision check(Supplier authentication, T object) { - boolean granted = isGranted(authentication.get()); - return new AuthorityAuthorizationDecision(granted, this.authorities); - } - - private boolean isGranted(Authentication authentication) { - return authentication != null && authentication.isAuthenticated() && isAuthorized(authentication); - } - - private boolean isAuthorized(Authentication authentication) { - Set authorities = AuthorityUtils.authorityListToSet(this.authorities); - for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) { - if (authorities.contains(grantedAuthority.getAuthority())) { - return true; - } - } - return false; - } - - private Collection getGrantedAuthorities(Authentication authentication) { - return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()); + return this.delegate.check(authentication, this.authorities); } @Override diff --git a/core/src/main/java/org/springframework/security/core/authority/AuthorityUtils.java b/core/src/main/java/org/springframework/security/core/authority/AuthorityUtils.java index babadbca1d..03a3dcd21a 100644 --- a/core/src/main/java/org/springframework/security/core/authority/AuthorityUtils.java +++ b/core/src/main/java/org/springframework/security/core/authority/AuthorityUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -33,6 +33,7 @@ import org.springframework.util.StringUtils; * Mainly intended for internal use. * * @author Luke Taylor + * @author Evgeniy Cheban */ public final class AuthorityUtils { @@ -78,4 +79,18 @@ public final class AuthorityUtils { return grantedAuthorities; } + /** + * Converts authorities into a List of GrantedAuthority objects. + * @param authorities the authorities to convert + * @return a List of GrantedAuthority objects + * @since 6.1 + */ + public static List createAuthorityList(Collection authorities) { + List grantedAuthorities = new ArrayList<>(authorities.size()); + for (String authority : authorities) { + grantedAuthorities.add(new SimpleGrantedAuthority(authority)); + } + return grantedAuthorities; + } + } diff --git a/core/src/test/java/org/springframework/security/authorization/AuthoritiesAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/AuthoritiesAuthorizationManagerTests.java new file mode 100644 index 0000000000..92359a64b8 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/AuthoritiesAuthorizationManagerTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2022 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.Arrays; +import java.util.Collections; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.Authentication; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link AuthoritiesAuthorizationManager}. + * + * @author Evgeniy Cheban + */ +class AuthoritiesAuthorizationManagerTests { + + @Test + void setRoleHierarchyWhenNullThenIllegalArgumentException() { + AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager(); + assertThatIllegalArgumentException().isThrownBy(() -> manager.setRoleHierarchy(null)) + .withMessage("roleHierarchy cannot be null"); + } + + @Test + void setRoleHierarchyWhenNotNullThenVerifyRoleHierarchy() { + AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager(); + RoleHierarchy roleHierarchy = new RoleHierarchyImpl(); + manager.setRoleHierarchy(roleHierarchy); + assertThat(manager).extracting("roleHierarchy").isEqualTo(roleHierarchy); + } + + @Test + void getRoleHierarchyWhenNotSetThenDefaultsToNullRoleHierarchy() { + AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager(); + assertThat(manager).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class); + } + + @Test + void checkWhenUserHasAnyAuthorityThenGrantedDecision() { + AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager(); + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "USER"); + assertThat(manager.check(authentication, Arrays.asList("ADMIN", "USER")).isGranted()).isTrue(); + } + + @Test + void checkWhenUserHasNotAnyAuthorityThenDeniedDecision() { + AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager(); + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ANONYMOUS"); + assertThat(manager.check(authentication, Arrays.asList("ADMIN", "USER")).isGranted()).isFalse(); + } + + @Test + void hasRoleWhenRoleHierarchySetThenGreaterRoleTakesPrecedence() { + AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager(); + RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); + roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER"); + manager.setRoleHierarchy(roleHierarchy); + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", + "ROLE_ADMIN"); + assertThat(manager.check(authentication, Collections.singleton("ROLE_ADMIN")).isGranted()).isTrue(); + } + +} diff --git a/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java index 7054910334..24b7c6235f 100644 --- a/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java @@ -226,13 +226,13 @@ public class AuthorityAuthorizationManagerTests { AuthorityAuthorizationManager manager = AuthorityAuthorizationManager.hasRole("USER"); RoleHierarchy roleHierarchy = new RoleHierarchyImpl(); manager.setRoleHierarchy(roleHierarchy); - assertThat(manager).extracting("roleHierarchy").isEqualTo(roleHierarchy); + assertThat(manager).extracting("delegate").extracting("roleHierarchy").isEqualTo(roleHierarchy); } @Test public void getRoleHierarchyWhenNotSetThenDefaultsToNullRoleHierarchy() { AuthorityAuthorizationManager manager = AuthorityAuthorizationManager.hasRole("USER"); - assertThat(manager).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class); + assertThat(manager).extracting("delegate").extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class); } @Test diff --git a/core/src/test/java/org/springframework/security/core/authority/AuthorityUtilsTests.java b/core/src/test/java/org/springframework/security/core/authority/AuthorityUtilsTests.java index 38a4a0a19b..c285e8f6eb 100644 --- a/core/src/test/java/org/springframework/security/core/authority/AuthorityUtilsTests.java +++ b/core/src/test/java/org/springframework/security/core/authority/AuthorityUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -16,6 +16,7 @@ package org.springframework.security.core.authority; +import java.util.Arrays; import java.util.List; import java.util.Set; @@ -27,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor + * @author Evgeniy Cheban */ public class AuthorityUtilsTests { @@ -42,4 +44,14 @@ public class AuthorityUtilsTests { assertThat(authorities.contains("ROLE_D")).isTrue(); } + @Test + public void createAuthorityList() { + List authorities = AuthorityUtils + .createAuthorityList(Arrays.asList("ROLE_A", "ROLE_B", "ROLE_C")); + assertThat(authorities).hasSize(3); + assertThat(authorities).element(0).extracting(GrantedAuthority::getAuthority).isEqualTo("ROLE_A"); + assertThat(authorities).element(1).extracting(GrantedAuthority::getAuthority).isEqualTo("ROLE_B"); + assertThat(authorities).element(2).extracting(GrantedAuthority::getAuthority).isEqualTo("ROLE_C"); + } + }