Skip to content

Commit

Permalink
Support RoleHierarchy Bean in authorizeHttpRequests Kotlin DSL
Browse files Browse the repository at this point in the history
Closes gh-15136
  • Loading branch information
marcusdacoregio committed Jun 10, 2024
1 parent ed2b654 commit 7c43fc1
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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() {
}

}

}
}
2 changes: 2 additions & 0 deletions docs/modules/ROOT/pages/whats-new.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 7c43fc1

Please sign in to comment.