From be6d2f117efb2b720f7c711c503c00c8164f550d Mon Sep 17 00:00:00 2001 From: koishikawa11 <64801497+koishikawa11@users.noreply.github.com> Date: Tue, 11 Aug 2020 20:59:22 +0900 Subject: [PATCH] Add hasAnyRole and hasAnyAuthority to authorizeRequests in Kotlin DSL Closes gh-8892 --- .../web/servlet/AuthorizeRequestsDsl.kt | 24 ++++ .../web/servlet/AuthorizeRequestsDslTests.kt | 136 ++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt index eb0887d7bf..663287a118 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt @@ -153,6 +153,18 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() { */ fun hasAuthority(authority: String) = "hasAuthority('$authority')" + /** + * Specify that URLs requires any of a number authorities. + * + * @param authorities the authorities to require (i.e. ROLE_USER, ROLE_ADMIN, etc). + * @return the SpEL expression "hasAnyAuthority" with the given authorities as a + * parameter + */ + fun hasAnyAuthority(vararg authorities: String): String { + val anyAuthorities = authorities.joinToString("','") + return "hasAnyAuthority('$anyAuthorities')" + } + /** * Specify that URLs require a particular role. * @@ -162,6 +174,18 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() { */ fun hasRole(role: String) = "hasRole('$role')" + /** + * Specify that URLs requires any of a number roles. + * + * @param roles the roles to require (i.e. USER, ADMIN, etc). + * @return the SpEL expression "hasAnyRole" with the given roles as a + * parameter + */ + fun hasAnyRole(vararg roles: String): String { + val anyRoles = roles.joinToString("','") + return "hasAnyRole('$anyRoles')" + } + /** * Specify that URLs are allowed by anyone. */ diff --git a/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt index 72c5ff57be..d0f27b1350 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt @@ -267,6 +267,142 @@ class AuthorizeRequestsDslTests { } } + @Test + fun `request when user has some allowed roles then responds with OK`() { + this.spring.register(HasAnyRoleConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("user", "password")) + }.andExpect { + status { isOk } + } + + this.mockMvc.get("/") { + with(httpBasic("admin", "password")) + }.andExpect { + status { isOk } + } + } + + @Test + fun `request when user does not have any allowed roles then responds with forbidden`() { + this.spring.register(HasAnyRoleConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("other", "password")) + }.andExpect { + status { isForbidden } + } + } + + @EnableWebSecurity + @EnableWebMvc + open class HasAnyRoleConfig : WebSecurityConfigurerAdapter() { + override fun configure(http: HttpSecurity) { + http { + authorizeRequests { + authorize("/**", hasAnyRole("ADMIN", "USER")) + } + httpBasic { } + } + } + + @RestController + internal class PathController { + @GetMapping("/") + fun index() { + } + } + + @Bean + override fun userDetailsService(): UserDetailsService { + val userDetails = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("USER") + .build() + val admin1Details = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .roles("ADMIN") + .build() + val admin2Details = User.withDefaultPasswordEncoder() + .username("other") + .password("password") + .roles("OTHER") + .build() + return InMemoryUserDetailsManager(userDetails, admin1Details, admin2Details) + } + } + + @Test + fun `request when user has some allowed authorities then responds with OK`() { + this.spring.register(HasAnyAuthorityConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("user", "password")) + }.andExpect { + status { isOk } + } + + this.mockMvc.get("/") { + with(httpBasic("admin", "password")) + }.andExpect { + status { isOk } + } + } + + @Test + fun `request when user does not have any allowed authorities then responds with forbidden`() { + this.spring.register(HasAnyAuthorityConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("other", "password")) + }.andExpect { + status { isForbidden } + } + } + + @EnableWebSecurity + @EnableWebMvc + open class HasAnyAuthorityConfig : WebSecurityConfigurerAdapter() { + override fun configure(http: HttpSecurity) { + http { + authorizeRequests { + authorize("/**", hasAnyAuthority("ROLE_ADMIN", "ROLE_USER")) + } + httpBasic { } + } + } + + @RestController + internal class PathController { + @GetMapping("/") + fun index() { + } + } + + @Bean + override fun userDetailsService(): UserDetailsService { + val userDetails = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .authorities("ROLE_USER") + .build() + val admin1Details = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .authorities("ROLE_ADMIN") + .build() + val admin2Details = User.withDefaultPasswordEncoder() + .username("other") + .password("password") + .authorities("ROLE_OTHER") + .build() + return InMemoryUserDetailsManager(userDetails, admin1Details, admin2Details) + } + } + @Test fun `request when secured by mvc with servlet path then responds based on servlet path`() { this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()