diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy index c904fc04020..523905e4c22 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy @@ -25,6 +25,9 @@ import org.springframework.beans.factory.config.BeanPostProcessor import org.springframework.context.ApplicationListener import org.springframework.context.annotation.Bean import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.PermissionEvaluator +import org.springframework.security.access.hierarchicalroles.RoleHierarchy +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl import org.springframework.security.access.event.AuthorizedEvent import org.springframework.security.access.vote.AffirmativeBased import org.springframework.security.authentication.RememberMeAuthenticationToken @@ -35,6 +38,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurerConfigs.CustomExpressionRootConfig +import org.springframework.security.core.Authentication import org.springframework.security.core.authority.AuthorityUtils import org.springframework.security.web.access.intercept.FilterSecurityInterceptor @@ -574,4 +578,119 @@ public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec { } } + + def "permissionEvaluator autowired"() { + setup: + loadConfig(PermissionEvaluatorConfig) + when: "invoke hasPermission expression that allows access" + super.setup() + login() + request.servletPath = "/allow/1" + springSecurityFilterChain.doFilter(request, response, chain) + then: "permissionEvaluator with id and type works - allows access" + response.status == HttpServletResponse.SC_OK + when: "invoke hasPermission expression that denies access" + super.setup() + login() + request.servletPath = "/deny/1" + springSecurityFilterChain.doFilter(request, response, chain) + then: "permissionEvaluator with id and type works - denies access" + response.status == HttpServletResponse.SC_FORBIDDEN + when: "invoke hasPermission expression that allows access" + super.setup() + login() + request.servletPath = "/allowObject/1" + springSecurityFilterChain.doFilter(request, response, chain) + then: "permissionEvaluator with object works - allows access" + response.status == HttpServletResponse.SC_OK + when: "invoke hasPermission expression that denies access" + super.setup() + login() + request.servletPath = "/denyObject/1" + springSecurityFilterChain.doFilter(request, response, chain) + then: "permissionEvaluator with object works - denies access" + response.status == HttpServletResponse.SC_FORBIDDEN + } + + @EnableWebSecurity + static class PermissionEvaluatorConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/allow/**").access("hasPermission('ID', 'TYPE', 'PERMISSION')") + .antMatchers("/allowObject/**").access("hasPermission('TESTOBJ', 'PERMISSION')") + .antMatchers("/deny/**").access("hasPermission('ID', 'TYPE', 'NO PERMISSION')") + .antMatchers("/denyObject/**").access("hasPermission('TESTOBJ', 'NO PERMISSION')") + .anyRequest().permitAll(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER") + } + + @Bean + public PermissionEvaluator permissionEvaluator(){ + return new PermissionEvaluator(){ + @Override + public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + return "TESTOBJ".equals(targetDomainObject) && "PERMISSION".equals(permission); + } + @Override + public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, + Object permission) { + return "ID".equals(targetId) && "TYPE".equals(targetType) && "PERMISSION".equals(permission); + } + }; + } + + } + + @EnableWebSecurity + static class RoleHierarchyConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/allow/**").access("hasRole('XXX')") + .antMatchers("/deny/**").access("hasRole('NOPE')") + .anyRequest().permitAll(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER") + } + + @Bean + public RoleHierarchy roleHierarchy(){ + return new RoleHierarchyImpl("USER > XXX"); + } + + } + + def "roleHierarchy autowired"() { + setup: + loadConfig(PermissionEvaluatorConfig) + when: "invoke roleHierarchy expression that allows access" + super.setup() + login() + request.servletPath = "/allow/1" + springSecurityFilterChain.doFilter(request, response, chain) + then: "permissionEvaluator with id and type works - allows access" + response.status == HttpServletResponse.SC_OK + when: "invoke roleHierarchy expression that denies access" + super.setup() + login() + request.servletPath = "/deny/1" + springSecurityFilterChain.doFilter(request, response, chain) + then: "permissionEvaluator with id and type works - denies access" + response.status == HttpServletResponse.SC_FORBIDDEN + } + } diff --git a/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java index 15d1f5b744f..7438aed7314 100644 --- a/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java @@ -40,8 +40,12 @@ public abstract class AbstractSecurityExpressionHandler implements SecurityExpressionHandler, ApplicationContextAware { private ExpressionParser expressionParser = new SpelExpressionParser(); private BeanResolver br; + private ApplicationContext context; private RoleHierarchy roleHierarchy; private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator(); + private boolean roleHierarchySet = false; + private boolean permissionEvaluatorSet = false; + public final ExpressionParser getExpressionParser() { return expressionParser; @@ -101,23 +105,52 @@ protected StandardEvaluationContext createEvaluationContextInternal( protected abstract SecurityExpressionOperations createSecurityExpressionRoot( Authentication authentication, T invocation); + private boolean roleHerarchyNotSetForValidContext() { + return ! roleHierarchySet && context != null; + } + protected RoleHierarchy getRoleHierarchy() { + if(roleHerarchyNotSetForValidContext()) { + RoleHierarchy contextRoleHierarchy = getSingleBeanOrNull(RoleHierarchy.class); + if(contextRoleHierarchy != null){ + roleHierarchy = contextRoleHierarchy; + } + roleHierarchySet = true; + } return roleHierarchy; } public void setRoleHierarchy(RoleHierarchy roleHierarchy) { + roleHierarchySet = true; this.roleHierarchy = roleHierarchy; } protected PermissionEvaluator getPermissionEvaluator() { + if(! permissionEvaluatorSet && context != null) { + PermissionEvaluator contextPermissionEvaluator = getSingleBeanOrNull(PermissionEvaluator.class); + if(contextPermissionEvaluator != null){ + permissionEvaluator = contextPermissionEvaluator; + } + permissionEvaluatorSet = true; + } return permissionEvaluator; } public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) { + permissionEvaluatorSet = true; this.permissionEvaluator = permissionEvaluator; } public void setApplicationContext(ApplicationContext applicationContext) { br = new BeanFactoryResolver(applicationContext); + this.context = applicationContext; + } + + private T getSingleBeanOrNull(Class type) { + String[] beanNamesForType = context.getBeanNamesForType(type); + if (beanNamesForType == null || beanNamesForType.length != 1) { + return null; + } + return context.getBean(beanNamesForType[0], type); } }