diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java index de50e7563a..07349531b4 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java @@ -17,10 +17,13 @@ package org.springframework.security.acls.domain; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Set; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; @@ -59,6 +62,8 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); + private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); + /** * Constructor. The only mandatory parameter relates to the system-wide * {@link GrantedAuthority} instances that can be held to always permit ACL changes. @@ -100,7 +105,9 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { } // Iterate this principal's authorities to determine right - Set authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities()); + Collection reachableGrantedAuthorities = this.roleHierarchy + .getReachableGrantedAuthorities(authentication.getAuthorities()); + Set authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities); if (acl.getOwner() instanceof GrantedAuthoritySid && authorities.contains(((GrantedAuthoritySid) acl.getOwner()).getGrantedAuthority())) { return; @@ -162,4 +169,14 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { this.securityContextHolderStrategy = securityContextHolderStrategy; } + /** + * Sets the {@link RoleHierarchy} to use. The default is to use a + * {@link NullRoleHierarchy} + * @since 6.4 + */ + public void setRoleHierarchy(RoleHierarchy roleHierarchy) { + Assert.notNull(roleHierarchy, "roleHierarchy cannot be null"); + this.roleHierarchy = roleHierarchy; + } + } diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java index 9a61e6d250..a68f41735c 100644 --- a/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java +++ b/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java @@ -18,6 +18,7 @@ package org.springframework.security.acls.domain; import java.util.Arrays; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,6 +26,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.acls.model.Acl; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.GrantedAuthority; @@ -34,6 +36,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @@ -86,6 +89,15 @@ public class AclAuthorizationStrategyImplTests { this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); } + @Test + public void securityCheckWhenRoleReachableByHierarchyThenAuthorized() { + given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH_B")); + this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); + this.strategy.setRoleHierarchy(RoleHierarchyImpl.fromHierarchy("ROLE_AUTH > ROLE_AUTH_B")); + assertThatNoException() + .isThrownBy(() -> this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL)); + } + @Test public void securityCheckWhenCustomSecurityContextHolderStrategyThenUses() { given(this.securityContextHolderStrategy.getContext()).willReturn(this.context); diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index cf4247b92a..50a475bee2 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -3,3 +3,5 @@ Spring Security 6.4 provides a number of new features. Below are the highlights of the release, or you can view https://github.com/spring-projects/spring-security/releases[the release notes] for a detailed listing of each feature and bug fix. + +- https://github.com/spring-projects/spring-security/issues/4186[gh-4186] - Support `RoleHierarchy` in `AclAuthorizationStrategyImpl`