Support RoleHierarchy Bean in authorizeHttpRequests Kotlin DSL

Closes gh-15136
This commit is contained in:
Marcus Hert Da Coregio 2024-06-10 15:40:21 -03:00
parent ed2b654f71
commit 7c43fc111f
3 changed files with 106 additions and 10 deletions

View File

@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.web
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpMethod
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
import org.springframework.security.authorization.AuthenticatedAuthorizationManager
import org.springframework.security.authorization.AuthorityAuthorizationManager
import org.springframework.security.authorization.AuthorizationDecision
@ -65,6 +67,7 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
private val authorizationRules = mutableListOf<AuthorizationManagerRule>()
private val rolePrefix: String
private val roleHierarchy: RoleHierarchy
private val HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
@ -210,7 +213,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
* @return the [AuthorizationManager] with the provided authority
*/
fun hasAuthority(authority: String): AuthorizationManager<RequestAuthorizationContext> {
return AuthorityAuthorizationManager.hasAuthority(authority)
val manager = AuthorityAuthorizationManager.hasAuthority<RequestAuthorizationContext>(authority)
return withRoleHierarchy(manager)
}
/**
@ -220,7 +224,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
* @return the [AuthorizationManager] with the provided authorities
*/
fun hasAnyAuthority(vararg authorities: String): AuthorizationManager<RequestAuthorizationContext> {
return AuthorityAuthorizationManager.hasAnyAuthority(*authorities)
val manager = AuthorityAuthorizationManager.hasAnyAuthority<RequestAuthorizationContext>(*authorities)
return withRoleHierarchy(manager)
}
/**
@ -230,7 +235,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
* @return the [AuthorizationManager] with the provided role
*/
fun hasRole(role: String): AuthorizationManager<RequestAuthorizationContext> {
return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(role))
val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(role))
return withRoleHierarchy(manager)
}
/**
@ -240,7 +246,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
* @return the [AuthorizationManager] with the provided roles
*/
fun hasAnyRole(vararg roles: String): AuthorizationManager<RequestAuthorizationContext> {
return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(*roles))
val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(*roles))
return withRoleHierarchy(manager)
}
/**
@ -296,15 +303,34 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
constructor() {
this.rolePrefix = "ROLE_"
this.roleHierarchy = NullRoleHierarchy()
}
constructor(context: ApplicationContext) {
val rolePrefix = resolveRolePrefix(context)
this.rolePrefix = rolePrefix
val roleHierarchy = resolveRoleHierarchy(context)
this.roleHierarchy = roleHierarchy
}
private fun resolveRolePrefix(context: ApplicationContext): String {
val beanNames = context.getBeanNamesForType(GrantedAuthorityDefaults::class.java)
if (beanNames.size > 0) {
val grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults::class.java);
this.rolePrefix = grantedAuthorityDefaults.rolePrefix
} else {
this.rolePrefix = "ROLE_"
if (beanNames.isNotEmpty()) {
return context.getBean(GrantedAuthorityDefaults::class.java).rolePrefix
}
return "ROLE_";
}
private fun resolveRoleHierarchy(context: ApplicationContext): RoleHierarchy {
val beanNames = context.getBeanNamesForType(RoleHierarchy::class.java)
if (beanNames.isNotEmpty()) {
return context.getBean(RoleHierarchy::class.java)
}
return NullRoleHierarchy()
}
private fun withRoleHierarchy(manager: AuthorityAuthorizationManager<RequestAuthorizationContext>): AuthorityAuthorizationManager<RequestAuthorizationContext> {
manager.setRoleHierarchy(this.roleHierarchy)
return manager
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.config.annotation.web.builders.HttpSecurity
@ -892,4 +894,70 @@ class AuthorizeHttpRequestsDslTests {
return GrantedAuthorityDefaults("CUSTOM_")
}
}
@Test
fun `hasRole when role hierarchy configured then honor hierarchy`() {
this.spring.register(RoleHierarchyConfig::class.java).autowire()
this.mockMvc.get("/protected") {
with(httpBasic("admin", "password"))
}.andExpect {
status {
isOk()
}
}
this.mockMvc.get("/protected") {
with(httpBasic("user", "password"))
}.andExpect {
status {
isOk()
}
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
open class RoleHierarchyConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize("/protected", hasRole("USER"))
}
httpBasic { }
}
return http.build()
}
@Bean
open fun roleHierarchy(): RoleHierarchy {
return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_USER")
}
@Bean
open fun userDetailsService(): UserDetailsService {
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
val admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("ADMIN")
.build()
return InMemoryUserDetailsManager(user, admin)
}
@RestController
internal class PathController {
@RequestMapping("/protected")
fun path() {
}
}
}
}

View File

@ -5,3 +5,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`
- https://github.com/spring-projects/spring-security/issues/15136[gh-15136] - Support `RoleHierarchy` Bean in `authorizeHttpRequests` Kotlin DSL