From 4aa32f7d064a6f2545a04b90362db141a37d1d8e Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Fri, 24 Oct 2008 00:38:36 +0000 Subject: [PATCH] SEC-999: First commit of expression-based authorization implementation --- core/pom.xml | 11 +- .../security/ConfigAttributeDefinition.java | 33 +--- .../afterinvocation/ArrayFilterer.java | 3 +- .../security/afterinvocation/Filterer.java | 2 +- .../Jsr250MethodDefinitionSource.java | 67 ++++--- .../SecuredMethodDefinitionSource.java | 44 +++-- .../security/config/BeanIds.java | 83 +++++---- .../security/config/ConfigUtils.java | 176 +++++++++--------- ...balMethodSecurityBeanDefinitionParser.java | 34 +++- .../HttpSecurityBeanDefinitionParser.java | 8 +- ...terceptMethodsBeanDefinitionDecorator.java | 32 ++-- .../security/expression/ExpressionUtils.java | 67 +++++++ ...ethodInvocationSecurityExpressionRoot.java | 14 ++ .../expression/SecurityExpressionRoot.java | 67 +++++++ .../expression/annotation/PostAuthorize.java | 26 +++ .../expression/annotation/PostFilter.java | 26 +++ .../expression/annotation/PreAuthorize.java | 27 +++ .../expression/annotation/PreFilter.java | 31 +++ ...tExpressionBasedMethodConfigAttribute.java | 42 +++++ ...ssionAnnotationMethodDefinitionSource.java | 111 +++++++++++ ...thodExpressionAfterInvocationProvider.java | 110 +++++++++++ .../support/MethodExpressionVoter.java | 109 +++++++++++ ...nExpressionBasedMethodConfigAttribute.java | 12 ++ ...nExpressionBasedMethodConfigAttribute.java | 18 ++ ...bstractFallbackMethodDefinitionSource.java | 20 +- .../MapBasedMethodDefinitionSource.java | 31 +-- .../method/MethodDefinitionAttributes.java | 71 ------- .../method/MethodDefinitionSourceEditor.java | 12 +- .../method/ProtectPointcutPostProcessor.java | 15 +- .../util/InMemoryXmlApplicationContext.java | 6 +- .../main/resources/META-INF/spring.schemas | 3 +- .../security/annotation/BusinessService.java | 12 ++ .../annotation/BusinessServiceImpl.java | 16 ++ .../annotation/Jsr250BusinessServiceImpl.java | 16 ++ .../Jsr250MethodDefinitionSourceTests.java | 45 ++--- .../SecuredMethodDefinitionSourceTests.java | 43 ++--- ...nProviderBeanDefinitionDecoratorTests.java | 4 +- ...thodSecurityBeanDefinitionParserTests.java | 47 +++++ .../support/MethodExpressionVoterTests.java | 71 +++++++ .../AbstractMethodDefinitionSourceTests.java | 94 ---------- .../MapBasedMethodDefinitionSourceTests.java | 15 +- .../MethodDefinitionAttributesTests.java | 57 ------ .../method/MockMethodDefinitionSource.java | 17 +- pom.xml | 25 ++- samples/pom.xml | 9 - 45 files changed, 1213 insertions(+), 569 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/expression/ExpressionUtils.java create mode 100644 core/src/main/java/org/springframework/security/expression/MethodInvocationSecurityExpressionRoot.java create mode 100644 core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java create mode 100644 core/src/main/java/org/springframework/security/expression/annotation/PostAuthorize.java create mode 100644 core/src/main/java/org/springframework/security/expression/annotation/PostFilter.java create mode 100644 core/src/main/java/org/springframework/security/expression/annotation/PreAuthorize.java create mode 100644 core/src/main/java/org/springframework/security/expression/annotation/PreFilter.java create mode 100644 core/src/main/java/org/springframework/security/expression/support/AbstractExpressionBasedMethodConfigAttribute.java create mode 100644 core/src/main/java/org/springframework/security/expression/support/ExpressionAnnotationMethodDefinitionSource.java create mode 100644 core/src/main/java/org/springframework/security/expression/support/MethodExpressionAfterInvocationProvider.java create mode 100644 core/src/main/java/org/springframework/security/expression/support/MethodExpressionVoter.java create mode 100644 core/src/main/java/org/springframework/security/expression/support/PostInvocationExpressionBasedMethodConfigAttribute.java create mode 100644 core/src/main/java/org/springframework/security/expression/support/PreInvocationExpressionBasedMethodConfigAttribute.java delete mode 100644 core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java create mode 100644 core/src/test/java/org/springframework/security/expression/support/MethodExpressionVoterTests.java delete mode 100644 core/src/test/java/org/springframework/security/intercept/method/AbstractMethodDefinitionSourceTests.java delete mode 100644 core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java diff --git a/core/pom.xml b/core/pom.xml index 76d3d255d0..b8e88cad0a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -10,6 +10,11 @@ Spring Security - Core + + org.springframework.security + spring-el-security + ${project.version} + org.springframework spring-core @@ -39,13 +44,11 @@ org.aspectj - aspectjrt - true + com.springsource.org.aspectj.runtime org.aspectj - aspectjweaver - true + com.springsource.org.aspectj.weaver org.springframework.ldap diff --git a/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java b/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java index 43f1e6f018..b92f5c4710 100644 --- a/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java +++ b/core/src/main/java/org/springframework/security/ConfigAttributeDefinition.java @@ -67,7 +67,7 @@ public class ConfigAttributeDefinition implements Serializable { public ConfigAttributeDefinition(ConfigAttribute attribute) { configAttributes = new ArrayList(1); configAttributes.add(attribute); - configAttributes = Collections.unmodifiableList(configAttributes); + configAttributes = Collections.unmodifiableList(configAttributes); } /** @@ -78,7 +78,7 @@ public class ConfigAttributeDefinition implements Serializable { */ public ConfigAttributeDefinition(String[] attributeTokens) { configAttributes = new ArrayList(attributeTokens.length); - + for (int i = 0; i < attributeTokens.length; i++) { configAttributes.add(new SecurityConfig(attributeTokens[i].trim())); } @@ -98,33 +98,6 @@ public class ConfigAttributeDefinition implements Serializable { this.configAttributes = Collections.unmodifiableList(new ArrayList(configAttributes)); } - - /** - * Creates a ConfigAttributeDefinition by including only those attributes which implement ConfigAttribute. - * - * @param unfilteredInput a collection of various elements, zero or more which implement ConfigAttribute (can also be null) - * @return a ConfigAttributeDefinition if at least one ConfigAttribute was present, or null if none implemented it - */ - public static ConfigAttributeDefinition createFiltered(Collection unfilteredInput) { - if (unfilteredInput == null) { - return null; - } - - List configAttributes = new ArrayList(); - Iterator i = unfilteredInput.iterator(); - while (i.hasNext()) { - Object element = i.next(); - if (element instanceof ConfigAttribute) { - configAttributes.add(element); - } - } - - if (configAttributes.size() == 0) { - return null; - } - - return new ConfigAttributeDefinition(configAttributes); - } //~ Methods ======================================================================================================== @@ -160,7 +133,7 @@ public class ConfigAttributeDefinition implements Serializable { * * @return the configuration attributes stored in this instance. */ - public Collection getConfigAttributes() { + public Collection getConfigAttributes() { return this.configAttributes; } diff --git a/core/src/main/java/org/springframework/security/afterinvocation/ArrayFilterer.java b/core/src/main/java/org/springframework/security/afterinvocation/ArrayFilterer.java index 774d13224c..928d53184d 100644 --- a/core/src/main/java/org/springframework/security/afterinvocation/ArrayFilterer.java +++ b/core/src/main/java/org/springframework/security/afterinvocation/ArrayFilterer.java @@ -36,8 +36,7 @@ import java.util.Set; class ArrayFilterer implements Filterer { //~ Static fields/initializers ===================================================================================== - protected static final Log logger = - LogFactory.getLog(BasicAclEntryAfterInvocationCollectionFilteringProvider.class); + protected static final Log logger = LogFactory.getLog(ArrayFilterer.class); //~ Instance fields ================================================================================================ diff --git a/core/src/main/java/org/springframework/security/afterinvocation/Filterer.java b/core/src/main/java/org/springframework/security/afterinvocation/Filterer.java index 3e26be8c10..0dcca73695 100644 --- a/core/src/main/java/org/springframework/security/afterinvocation/Filterer.java +++ b/core/src/main/java/org/springframework/security/afterinvocation/Filterer.java @@ -19,7 +19,7 @@ import java.util.Iterator; /** - * Filter strategy interface. + * Filterer strategy interface. * * @author Ben Alex * @author Paulo Neves diff --git a/core/src/main/java/org/springframework/security/annotation/Jsr250MethodDefinitionSource.java b/core/src/main/java/org/springframework/security/annotation/Jsr250MethodDefinitionSource.java index d30425910b..1d6ceda30a 100644 --- a/core/src/main/java/org/springframework/security/annotation/Jsr250MethodDefinitionSource.java +++ b/core/src/main/java/org/springframework/security/annotation/Jsr250MethodDefinitionSource.java @@ -26,12 +26,13 @@ import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource; /** - * Sources method security metadata from major JSR 250 security annotations. + * Sources method security metadata from major JSR 250 security annotations. * * @author Ben Alex * @version $Id$ @@ -39,38 +40,42 @@ import org.springframework.security.intercept.method.AbstractFallbackMethodDefin */ public class Jsr250MethodDefinitionSource extends AbstractFallbackMethodDefinitionSource { - protected ConfigAttributeDefinition findAttributes(Class clazz) { - return processAnnotations(clazz.getAnnotations()); - } + protected List findAttributes(Class clazz) { + return processAnnotations(clazz.getAnnotations()); + } + + protected List findAttributes(Method method, Class targetClass) { + return processAnnotations(AnnotationUtils.getAnnotations(method)); + } - protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { - return processAnnotations(AnnotationUtils.getAnnotations(method)); - } - public Collection getConfigAttributeDefinitions() { return null; } - - private ConfigAttributeDefinition processAnnotations(Annotation[] annotations) { - if (annotations == null || annotations.length == 0) { - return null; - } - for (Annotation a: annotations) { - if (a instanceof DenyAll) { - return new ConfigAttributeDefinition(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE); - } - if (a instanceof PermitAll) { - return new ConfigAttributeDefinition(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE); - } - if (a instanceof RolesAllowed) { - RolesAllowed ra = (RolesAllowed) a; - List attributes = new ArrayList(); - for (String allowed : ra.value()) { - attributes.add(new Jsr250SecurityConfig(allowed)); - } - return new ConfigAttributeDefinition(attributes); - } - } - return null; - } + + private List processAnnotations(Annotation[] annotations) { + if (annotations == null || annotations.length == 0) { + return null; + } + List attributes = new ArrayList(); + + for (Annotation a: annotations) { + if (a instanceof DenyAll) { + attributes.add(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE); + return attributes; + } + if (a instanceof PermitAll) { + attributes.add(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE); + return attributes; + } + if (a instanceof RolesAllowed) { + RolesAllowed ra = (RolesAllowed) a; + + for (String allowed : ra.value()) { + attributes.add(new Jsr250SecurityConfig(allowed)); + } + return attributes; + } + } + return null; + } } diff --git a/core/src/main/java/org/springframework/security/annotation/SecuredMethodDefinitionSource.java b/core/src/main/java/org/springframework/security/annotation/SecuredMethodDefinitionSource.java index f3fa4915d1..7e785c298e 100644 --- a/core/src/main/java/org/springframework/security/annotation/SecuredMethodDefinitionSource.java +++ b/core/src/main/java/org/springframework/security/annotation/SecuredMethodDefinitionSource.java @@ -17,38 +17,48 @@ package org.springframework.security.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.SecurityConfig; import org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource; /** - * Sources method security metadata from Spring Security's {@link Secured} annotation. + * Sources method security metadata from Spring Security's {@link Secured} annotation. * * @author Ben Alex * @version $Id$ */ public class SecuredMethodDefinitionSource extends AbstractFallbackMethodDefinitionSource { - protected ConfigAttributeDefinition findAttributes(Class clazz) { - return processAnnotation(clazz.getAnnotation(Secured.class)); - } + protected List findAttributes(Class clazz) { + return processAnnotation(clazz.getAnnotation(Secured.class)); + } + + protected List findAttributes(Method method, Class targetClass) { + return processAnnotation(AnnotationUtils.findAnnotation(method, Secured.class)); + } - protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { - return processAnnotation(AnnotationUtils.findAnnotation(method, Secured.class)); - } - public Collection getConfigAttributeDefinitions() { return null; } - - private ConfigAttributeDefinition processAnnotation(Annotation a) { - if (a == null || !(a instanceof Secured)) { - return null; - } - Secured secured = (Secured) a; - return new ConfigAttributeDefinition(secured.value()); - } + + private List processAnnotation(Annotation a) { + if (a == null || !(a instanceof Secured)) { + return null; + } + + String[] attributeTokens = ((Secured) a).value(); + List attributes = new ArrayList(attributeTokens.length); + + for(String token : attributeTokens) { + attributes.add(new SecurityConfig(token)); + } + + return attributes; + } } diff --git a/core/src/main/java/org/springframework/security/config/BeanIds.java b/core/src/main/java/org/springframework/security/config/BeanIds.java index 11783abe90..b9c88d3d95 100644 --- a/core/src/main/java/org/springframework/security/config/BeanIds.java +++ b/core/src/main/java/org/springframework/security/config/BeanIds.java @@ -11,56 +11,57 @@ package org.springframework.security.config; public abstract class BeanIds { /** External alias for FilterChainProxy bean, for use in web.xml files */ - public static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain"; + public static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain"; /** Package protected as end users shouldn't really be using this BFPP directly */ - static final String INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR = "_interceptMethodsBeanfactoryPP"; + static final String INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR = "_interceptMethodsBeanfactoryPP"; static final String CONTEXT_SOURCE_SETTING_POST_PROCESSOR = "_contextSettingPostProcessor"; static final String ENTRY_POINT_INJECTION_POST_PROCESSOR = "_entryPointInjectionBeanPostProcessor"; static final String USER_DETAILS_SERVICE_INJECTION_POST_PROCESSOR = "_userServiceInjectionPostProcessor"; - static final String SESSION_REGISTRY_INJECTION_POST_PROCESSOR = "_sessionRegistryInjectionPostProcessor"; + static final String SESSION_REGISTRY_INJECTION_POST_PROCESSOR = "_sessionRegistryInjectionPostProcessor"; static final String FILTER_CHAIN_POST_PROCESSOR = "_filterChainProxyPostProcessor"; static final String FILTER_LIST = "_filterChainList"; public static final String JDBC_USER_DETAILS_MANAGER = "_jdbcUserDetailsManager"; - public static final String USER_DETAILS_SERVICE = "_userDetailsService"; - public static final String ANONYMOUS_PROCESSING_FILTER = "_anonymousProcessingFilter"; - public static final String ANONYMOUS_AUTHENTICATION_PROVIDER = "_anonymousAuthenticationProvider"; - public static final String BASIC_AUTHENTICATION_FILTER = "_basicAuthenticationFilter"; - public static final String BASIC_AUTHENTICATION_ENTRY_POINT = "_basicAuthenticationEntryPoint"; - public static final String SESSION_REGISTRY = "_sessionRegistry"; - public static final String CONCURRENT_SESSION_FILTER = "_concurrentSessionFilter"; - public static final String CONCURRENT_SESSION_CONTROLLER = "_concurrentSessionController"; - public static final String ACCESS_MANAGER = "_accessManager"; - public static final String AUTHENTICATION_MANAGER = "_authenticationManager"; - public static final String AFTER_INVOCATION_MANAGER = "_afterInvocationManager"; - public static final String FORM_LOGIN_FILTER = "_formLoginFilter"; - public static final String FORM_LOGIN_ENTRY_POINT = "_formLoginEntryPoint"; - public static final String OPEN_ID_FILTER = "_openIDFilter"; - public static final String OPEN_ID_ENTRY_POINT = "_openIDFilterEntryPoint"; - public static final String OPEN_ID_PROVIDER = "_openIDAuthenticationProvider"; - public static final String MAIN_ENTRY_POINT = "_mainEntryPoint"; - public static final String FILTER_CHAIN_PROXY = "_filterChainProxy"; - public static final String HTTP_SESSION_CONTEXT_INTEGRATION_FILTER = "_httpSessionContextIntegrationFilter"; - public static final String LDAP_AUTHENTICATION_PROVIDER = "_ldapAuthenticationProvider"; - public static final String LOGOUT_FILTER = "_logoutFilter"; - public static final String EXCEPTION_TRANSLATION_FILTER = "_exceptionTranslationFilter"; - public static final String FILTER_SECURITY_INTERCEPTOR = "_filterSecurityInterceptor"; - public static final String CHANNEL_PROCESSING_FILTER = "_channelProcessingFilter"; - public static final String CHANNEL_DECISION_MANAGER = "_channelDecisionManager"; - public static final String REMEMBER_ME_FILTER = "_rememberMeFilter"; - public static final String REMEMBER_ME_SERVICES = "_rememberMeServices"; - public static final String REMEMBER_ME_AUTHENTICATION_PROVIDER = "_rememberMeAuthenticationProvider"; - public static final String DEFAULT_LOGIN_PAGE_GENERATING_FILTER = "_defaultLoginPageFilter"; - public static final String SECURITY_CONTEXT_HOLDER_AWARE_REQUEST_FILTER = "_securityContextHolderAwareRequestFilter"; - public static final String SESSION_FIXATION_PROTECTION_FILTER = "_sessionFixationProtectionFilter"; - public static final String METHOD_SECURITY_INTERCEPTOR = "_methodSecurityInterceptor"; - public static final String METHOD_SECURITY_INTERCEPTOR_POST_PROCESSOR = "_methodSecurityInterceptorPostProcessor"; - public static final String METHOD_DEFINITION_SOURCE_ADVISOR = "_methodDefinitionSourceAdvisor"; - public static final String PROTECT_POINTCUT_POST_PROCESSOR = "_protectPointcutPostProcessor"; - public static final String DELEGATING_METHOD_DEFINITION_SOURCE = "_delegatingMethodDefinitionSource"; - public static final String SECURED_METHOD_DEFINITION_SOURCE = "_securedMethodDefinitionSource"; - public static final String JSR_250_METHOD_DEFINITION_SOURCE = "_jsr250MethodDefinitionSource"; + public static final String USER_DETAILS_SERVICE = "_userDetailsService"; + public static final String ANONYMOUS_PROCESSING_FILTER = "_anonymousProcessingFilter"; + public static final String ANONYMOUS_AUTHENTICATION_PROVIDER = "_anonymousAuthenticationProvider"; + public static final String BASIC_AUTHENTICATION_FILTER = "_basicAuthenticationFilter"; + public static final String BASIC_AUTHENTICATION_ENTRY_POINT = "_basicAuthenticationEntryPoint"; + public static final String SESSION_REGISTRY = "_sessionRegistry"; + public static final String CONCURRENT_SESSION_FILTER = "_concurrentSessionFilter"; + public static final String CONCURRENT_SESSION_CONTROLLER = "_concurrentSessionController"; + public static final String METHOD_ACCESS_MANAGER = "_methodAccessManager"; + public static final String WEB_ACCESS_MANAGER = "_webAccessManager"; + public static final String AUTHENTICATION_MANAGER = "_authenticationManager"; + public static final String AFTER_INVOCATION_MANAGER = "_afterInvocationManager"; + public static final String FORM_LOGIN_FILTER = "_formLoginFilter"; + public static final String FORM_LOGIN_ENTRY_POINT = "_formLoginEntryPoint"; + public static final String OPEN_ID_FILTER = "_openIDFilter"; + public static final String OPEN_ID_ENTRY_POINT = "_openIDFilterEntryPoint"; + public static final String OPEN_ID_PROVIDER = "_openIDAuthenticationProvider"; + public static final String MAIN_ENTRY_POINT = "_mainEntryPoint"; + public static final String FILTER_CHAIN_PROXY = "_filterChainProxy"; + public static final String HTTP_SESSION_CONTEXT_INTEGRATION_FILTER = "_httpSessionContextIntegrationFilter"; + public static final String LDAP_AUTHENTICATION_PROVIDER = "_ldapAuthenticationProvider"; + public static final String LOGOUT_FILTER = "_logoutFilter"; + public static final String EXCEPTION_TRANSLATION_FILTER = "_exceptionTranslationFilter"; + public static final String FILTER_SECURITY_INTERCEPTOR = "_filterSecurityInterceptor"; + public static final String CHANNEL_PROCESSING_FILTER = "_channelProcessingFilter"; + public static final String CHANNEL_DECISION_MANAGER = "_channelDecisionManager"; + public static final String REMEMBER_ME_FILTER = "_rememberMeFilter"; + public static final String REMEMBER_ME_SERVICES = "_rememberMeServices"; + public static final String REMEMBER_ME_AUTHENTICATION_PROVIDER = "_rememberMeAuthenticationProvider"; + public static final String DEFAULT_LOGIN_PAGE_GENERATING_FILTER = "_defaultLoginPageFilter"; + public static final String SECURITY_CONTEXT_HOLDER_AWARE_REQUEST_FILTER = "_securityContextHolderAwareRequestFilter"; + public static final String SESSION_FIXATION_PROTECTION_FILTER = "_sessionFixationProtectionFilter"; + public static final String METHOD_SECURITY_INTERCEPTOR = "_methodSecurityInterceptor"; + public static final String METHOD_SECURITY_INTERCEPTOR_POST_PROCESSOR = "_methodSecurityInterceptorPostProcessor"; + public static final String METHOD_DEFINITION_SOURCE_ADVISOR = "_methodDefinitionSourceAdvisor"; + public static final String PROTECT_POINTCUT_POST_PROCESSOR = "_protectPointcutPostProcessor"; + public static final String DELEGATING_METHOD_DEFINITION_SOURCE = "_delegatingMethodDefinitionSource"; + public static final String SECURED_METHOD_DEFINITION_SOURCE = "_securedMethodDefinitionSource"; + public static final String JSR_250_METHOD_DEFINITION_SOURCE = "_jsr250MethodDefinitionSource"; public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer"; public static final String CONTEXT_SOURCE = "_securityContextSource"; public static final String PORT_MAPPER = "_portMapper"; diff --git a/core/src/main/java/org/springframework/security/config/ConfigUtils.java b/core/src/main/java/org/springframework/security/config/ConfigUtils.java index 402247a565..2d4ba085ab 100644 --- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java +++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java @@ -3,8 +3,6 @@ package org.springframework.security.config; import java.util.ArrayList; import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValue; @@ -15,6 +13,7 @@ import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.afterinvocation.AfterInvocationProviderManager; +import org.springframework.security.expression.support.MethodExpressionVoter; import org.springframework.security.util.UrlUtils; import org.springframework.security.vote.AffirmativeBased; import org.springframework.security.vote.AuthenticatedVoter; @@ -29,45 +28,54 @@ import org.w3c.dom.Element; * @author Ben Alex * @version $Id$ */ -public abstract class ConfigUtils { - private static final Log logger = LogFactory.getLog(ConfigUtils.class); +abstract class ConfigUtils { - static void registerDefaultAccessManagerIfNecessary(ParserContext parserContext) { - - if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.ACCESS_MANAGER)) { - ManagedList defaultVoters = new ManagedList(2); - - defaultVoters.add(new RootBeanDefinition(RoleVoter.class)); - defaultVoters.add(new RootBeanDefinition(AuthenticatedVoter.class)); - - BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class); - accessMgrBuilder.addPropertyValue("decisionVoters", defaultVoters); - BeanDefinition accessMgr = accessMgrBuilder.getBeanDefinition(); - - parserContext.getRegistry().registerBeanDefinition(BeanIds.ACCESS_MANAGER, accessMgr); + static void registerDefaultWebAccessManagerIfNecessary(ParserContext parserContext) { + if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.WEB_ACCESS_MANAGER)) { + parserContext.getRegistry().registerBeanDefinition(BeanIds.WEB_ACCESS_MANAGER, + createAccessManagerBean(RoleVoter.class, AuthenticatedVoter.class)); } } - - public static int countNonEmpty(String[] objects) { - int nonNulls = 0; - - for (int i = 0; i < objects.length; i++) { - if (StringUtils.hasText(objects[i])) { - nonNulls++; - } - } - - return nonNulls; + + static void registerDefaultMethodAccessManagerIfNecessary(ParserContext parserContext) { + if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER)) { + parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER, + createAccessManagerBean(MethodExpressionVoter.class, RoleVoter.class, AuthenticatedVoter.class)); + } } - public static void addVoter(BeanDefinition voter, ParserContext parserContext) { - registerDefaultAccessManagerIfNecessary(parserContext); + private static BeanDefinition createAccessManagerBean(Class... voters) { + ManagedList defaultVoters = new ManagedList(voters.length); - BeanDefinition accessMgr = parserContext.getRegistry().getBeanDefinition(BeanIds.ACCESS_MANAGER); + for(Class voter : voters) { + defaultVoters.add(new RootBeanDefinition(voter)); + } + + BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class); + accessMgrBuilder.addPropertyValue("decisionVoters", defaultVoters); + return accessMgrBuilder.getBeanDefinition(); + } + + public static int countNonEmpty(String[] objects) { + int nonNulls = 0; + + for (int i = 0; i < objects.length; i++) { + if (StringUtils.hasText(objects[i])) { + nonNulls++; + } + } + + return nonNulls; + } + + static void addVoter(BeanDefinition voter, ParserContext parserContext) { + registerDefaultMethodAccessManagerIfNecessary(parserContext); + + BeanDefinition accessMgr = parserContext.getRegistry().getBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER); ManagedList voters = (ManagedList) accessMgr.getPropertyValues().getPropertyValue("decisionVoters").getValue(); voters.add(voter); - + accessMgr.getPropertyValues().addPropertyValue("decisionVoters", voters); } @@ -92,12 +100,12 @@ public abstract class ConfigUtils { BeanDefinition authManager = parserContext.getRegistry().getBeanDefinition(BeanIds.AUTHENTICATION_MANAGER); ((ArrayList) authManager.getPropertyValues().getPropertyValue("providerBeanNames").getValue()).add(beanName); } - - static ManagedList getRegisteredAfterInvocationProviders(ParserContext parserContext) { - BeanDefinition manager = registerAfterInvocationProviderManagerIfNecessary(parserContext); - return (ManagedList) manager.getPropertyValues().getPropertyValue("providers").getValue(); - } - + + static ManagedList getRegisteredAfterInvocationProviders(ParserContext parserContext) { + BeanDefinition manager = registerAfterInvocationProviderManagerIfNecessary(parserContext); + return (ManagedList) manager.getPropertyValues().getPropertyValue("providers").getValue(); + } + private static BeanDefinition registerAfterInvocationProviderManagerIfNecessary(ParserContext parserContext) { if(parserContext.getRegistry().containsBeanDefinition(BeanIds.AFTER_INVOCATION_MANAGER)) { return parserContext.getRegistry().getBeanDefinition(BeanIds.AFTER_INVOCATION_MANAGER); @@ -108,12 +116,12 @@ public abstract class ConfigUtils { parserContext.getRegistry().registerBeanDefinition(BeanIds.AFTER_INVOCATION_MANAGER, manager); return manager; - } + } - private static void registerFilterChainPostProcessorIfNecessary(ParserContext pc) { - if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_POST_PROCESSOR)) { - return; - } + private static void registerFilterChainPostProcessorIfNecessary(ParserContext pc) { + if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_POST_PROCESSOR)) { + return; + } // Post processor specifically to assemble and order the filter chain immediately before the FilterChainProxy is initialized. RootBeanDefinition filterChainPostProcessor = new RootBeanDefinition(FilterChainProxyPostProcessor.class); filterChainPostProcessor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); @@ -122,62 +130,62 @@ public abstract class ConfigUtils { filterList.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); pc.getRegistry().registerBeanDefinition(BeanIds.FILTER_LIST, filterList); } - + static void addHttpFilter(ParserContext pc, BeanMetadataElement filter) { - registerFilterChainPostProcessorIfNecessary(pc); - - RootBeanDefinition filterList = (RootBeanDefinition) pc.getRegistry().getBeanDefinition(BeanIds.FILTER_LIST); - - ManagedList filters; - MutablePropertyValues pvs = filterList.getPropertyValues(); - if (pvs.contains("filters")) { - filters = (ManagedList) pvs.getPropertyValue("filters").getValue(); - } else { - filters = new ManagedList(); - pvs.addPropertyValue("filters", filters); - } - - filters.add(filter); + registerFilterChainPostProcessorIfNecessary(pc); + + RootBeanDefinition filterList = (RootBeanDefinition) pc.getRegistry().getBeanDefinition(BeanIds.FILTER_LIST); + + ManagedList filters; + MutablePropertyValues pvs = filterList.getPropertyValues(); + if (pvs.contains("filters")) { + filters = (ManagedList) pvs.getPropertyValue("filters").getValue(); + } else { + filters = new ManagedList(); + pvs.addPropertyValue("filters", filters); + } + + filters.add(filter); } /** - * Bean which holds the list of filters which are maintained in the context and modified by calls to + * Bean which holds the list of filters which are maintained in the context and modified by calls to * addHttpFilter. The post processor retrieves these before injecting the list into the FilterChainProxy. */ public static class FilterChainList { - List filters; + List filters; - public List getFilters() { - return filters; - } + public List getFilters() { + return filters; + } - public void setFilters(List filters) { - this.filters = filters; - } + public void setFilters(List filters) { + this.filters = filters; + } } - + /** * Checks the value of an XML attribute which represents a redirect URL. - * If not empty or starting with "$" (potential placeholder), "/" or "http" it will raise an error. + * If not empty or starting with "$" (potential placeholder), "/" or "http" it will raise an error. */ static void validateHttpRedirect(String url, ParserContext pc, Object source) { - if (UrlUtils.isValidRedirectUrl(url) || url.startsWith("$")) { - return; - } - pc.getReaderContext().warning(url + " is not a valid redirect URL (must start with '/' or http(s))", source); - } - - static void setSessionControllerOnAuthenticationManager(ParserContext pc, String beanName, Element sourceElt) { - registerProviderManagerIfNecessary(pc); - BeanDefinition authManager = pc.getRegistry().getBeanDefinition(BeanIds.AUTHENTICATION_MANAGER); - PropertyValue pv = authManager.getPropertyValues().getPropertyValue("sessionController"); - - if (pv != null && pv.getValue() != null) { - pc.getReaderContext().error("A session controller has already been set on the authentication manager. " + - "The element isn't compatible with a custom session controller", - pc.extractSource(sourceElt)); + if (UrlUtils.isValidRedirectUrl(url) || url.startsWith("$")) { + return; } - + pc.getReaderContext().warning(url + " is not a valid redirect URL (must start with '/' or http(s))", source); + } + + static void setSessionControllerOnAuthenticationManager(ParserContext pc, String beanName, Element sourceElt) { + registerProviderManagerIfNecessary(pc); + BeanDefinition authManager = pc.getRegistry().getBeanDefinition(BeanIds.AUTHENTICATION_MANAGER); + PropertyValue pv = authManager.getPropertyValues().getPropertyValue("sessionController"); + + if (pv != null && pv.getValue() != null) { + pc.getReaderContext().error("A session controller has already been set on the authentication manager. " + + "The element isn't compatible with a custom session controller", + pc.extractSource(sourceElt)); + } + authManager.getPropertyValues().addPropertyValue("sessionController", new RuntimeBeanReference(beanName)); } } diff --git a/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java index bb388aabc8..10d0b65012 100644 --- a/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java @@ -1,5 +1,6 @@ package org.springframework.security.config; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -14,7 +15,9 @@ import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.SecurityConfig; +import org.springframework.security.expression.support.MethodExpressionAfterInvocationProvider; import org.springframework.security.intercept.method.DelegatingMethodDefinitionSource; import org.springframework.security.intercept.method.MapBasedMethodDefinitionSource; import org.springframework.security.intercept.method.ProtectPointcutPostProcessor; @@ -33,6 +36,7 @@ import org.w3c.dom.Element; class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { public static final String SECURED_DEPENDENCY_CLASS = "org.springframework.security.annotation.Secured"; public static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource"; + public static final String EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.expression.support.ExpressionAnnotationMethodDefinitionSource"; public static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource"; public static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter"; private static final String ATT_ACCESS = "access"; @@ -40,6 +44,7 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { private static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; private static final String ATT_USE_JSR250 = "jsr250-annotations"; private static final String ATT_USE_SECURED = "secured-annotations"; + private static final String ATT_USE_EXPRESSIONS = "spel-annotations"; public BeanDefinition parse(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); @@ -61,17 +66,21 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { registerDelegatingMethodDefinitionSource(parserContext, delegates, source); + // Add the expression-based after invocation provider + ConfigUtils.getRegisteredAfterInvocationProviders(parserContext).add( + new RootBeanDefinition(MethodExpressionAfterInvocationProvider.class)); + // Register the applicable AccessDecisionManager, handling the special JSR 250 voter if being used String accessManagerId = element.getAttribute(ATT_ACCESS_MGR); if (!StringUtils.hasText(accessManagerId)) { - ConfigUtils.registerDefaultAccessManagerIfNecessary(parserContext); + ConfigUtils.registerDefaultMethodAccessManagerIfNecessary(parserContext); if (jsr250Enabled) { ConfigUtils.addVoter(new RootBeanDefinition(JSR_250_VOTER_CLASS, null, null), parserContext); } - accessManagerId = BeanIds.ACCESS_MANAGER; + accessManagerId = BeanIds.METHOD_ACCESS_MANAGER; } registerMethodSecurityInterceptor(parserContext, accessManagerId, source); @@ -84,12 +93,17 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { } /** - * Checks whether JSR-250 and/or Secured annotations are enabled and adds the appropriate + * Checks whether el-based, JSR-250 and/or Secured annotations are enabled and adds the appropriate * MethodDefinitionSource delegates if required. */ private boolean registerAnnotationBasedMethodDefinitionSources(Element element, ParserContext pc, ManagedList delegates) { boolean useJsr250 = "enabled".equals(element.getAttribute(ATT_USE_JSR250)); boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED)); + boolean useExpressions = "enabled".equals(element.getAttribute(ATT_USE_EXPRESSIONS)); + + if (useExpressions) { + delegates.add(BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); + } if (useSecured) { delegates.add(BeanDefinitionBuilder.rootBeanDefinition(SECURED_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); @@ -139,8 +153,14 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { parserContext.getReaderContext().error("Pointcut expression required", parserContext.extractSource(childElt)); } - ConfigAttributeDefinition def = new ConfigAttributeDefinition(StringUtils.commaDelimitedListToStringArray(accessConfig)); - pointcutMap.put(expression, def); + String[] attributeTokens = StringUtils.commaDelimitedListToStringArray(accessConfig); + List attributes = new ArrayList(attributeTokens.length); + + for(String token : attributeTokens) { + attributes.add(new SecurityConfig(token)); + } + + pointcutMap.put(expression, attributes); } return pointcutMap; @@ -158,7 +178,7 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { parserContext.registerComponent(new BeanComponentDefinition(interceptor, BeanIds.METHOD_SECURITY_INTERCEPTOR)); parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR_POST_PROCESSOR, - new RootBeanDefinition(MethodSecurityInterceptorPostProcessor.class)); + new RootBeanDefinition(MethodSecurityInterceptorPostProcessor.class)); } private void registerAdvisor(ParserContext parserContext, Object source) { diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java index 2d45d7b91d..c5abdc1546 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -124,8 +124,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { String accessManagerId = element.getAttribute(ATT_ACCESS_MGR); if (!StringUtils.hasText(accessManagerId)) { - ConfigUtils.registerDefaultAccessManagerIfNecessary(parserContext); - accessManagerId = BeanIds.ACCESS_MANAGER; + ConfigUtils.registerDefaultWebAccessManagerIfNecessary(parserContext); + accessManagerId = BeanIds.WEB_ACCESS_MANAGER; } // Register the portMapper. A default will always be created, even if no element exists. @@ -273,8 +273,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { exceptionTranslationFilterBuilder.addPropertyValue("createSessionAllowed", new Boolean(allowSessionCreation)); if (StringUtils.hasText(accessDeniedPage)) { - BeanDefinition accessDeniedHandler = new RootBeanDefinition(AccessDeniedHandlerImpl.class); - accessDeniedHandler.getPropertyValues().addPropertyValue("errorPage", accessDeniedPage); + BeanDefinition accessDeniedHandler = new RootBeanDefinition(AccessDeniedHandlerImpl.class); + accessDeniedHandler.getPropertyValues().addPropertyValue("errorPage", accessDeniedPage); exceptionTranslationFilterBuilder.addPropertyValue("accessDeniedHandler", accessDeniedHandler); } diff --git a/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java b/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java index 97e5907cfe..58ad96b26b 100644 --- a/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java +++ b/core/src/main/java/org/springframework/security/config/InterceptMethodsBeanDefinitionDecorator.java @@ -30,8 +30,8 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { ConfigUtils.registerProviderManagerIfNecessary(parserContext); - ConfigUtils.registerDefaultAccessManagerIfNecessary(parserContext); - + ConfigUtils.registerDefaultMethodAccessManagerIfNecessary(parserContext); + return delegate.decorate(node, definition, parserContext); } } @@ -41,8 +41,8 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe * registration. */ class InternalInterceptMethodsBeanDefinitionDecorator extends AbstractInterceptorDrivenBeanDefinitionDecorator { - static final String ATT_METHOD = "method"; - static final String ATT_ACCESS = "access"; + static final String ATT_METHOD = "method"; + static final String ATT_ACCESS = "access"; private static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; private Log logger = LogFactory.getLog(getClass()); @@ -57,40 +57,40 @@ class InternalInterceptMethodsBeanDefinitionDecorator extends AbstractIntercepto String accessManagerId = interceptMethodsElt.getAttribute(ATT_ACCESS_MGR); if (!StringUtils.hasText(accessManagerId)) { - accessManagerId = BeanIds.ACCESS_MANAGER; + accessManagerId = BeanIds.METHOD_ACCESS_MANAGER; } interceptor.addPropertyValue("accessDecisionManager", new RuntimeBeanReference(accessManagerId)); interceptor.addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER)); // Lookup parent bean information - Element parent = (Element) node.getParentNode(); - String parentBeanClass = parent.getAttribute("class"); - String parentBeanId = parent.getAttribute("id"); + Element parent = (Element) node.getParentNode(); + String parentBeanClass = parent.getAttribute("class"); + String parentBeanId = parent.getAttribute("id"); parent = null; - + // Parse the included methods List methods = DomUtils.getChildElementsByTagName(interceptMethodsElt, Elements.PROTECT); StringBuffer sb = new StringBuffer(); - + for (Iterator i = methods.iterator(); i.hasNext();) { Element protectmethodElt = (Element) i.next(); String accessConfig = protectmethodElt.getAttribute(ATT_ACCESS); // Support inference of class names String methodName = protectmethodElt.getAttribute(ATT_METHOD); - + if (methodName.lastIndexOf(".") == -1) { - if (parentBeanClass != null && !"".equals(parentBeanClass)) { - methodName = parentBeanClass + "." + methodName; - } + if (parentBeanClass != null && !"".equals(parentBeanClass)) { + methodName = parentBeanClass + "." + methodName; + } } - + // Rely on the default property editor for MethodSecurityInterceptor.setObjectDefinitionSource to setup the MethodDefinitionSource sb.append(methodName + "=" + accessConfig).append("\r\n"); } - + interceptor.addPropertyValue("objectDefinitionSource", sb.toString()); return interceptor.getBeanDefinition(); diff --git a/core/src/main/java/org/springframework/security/expression/ExpressionUtils.java b/core/src/main/java/org/springframework/security/expression/ExpressionUtils.java new file mode 100644 index 0000000000..1492382785 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/ExpressionUtils.java @@ -0,0 +1,67 @@ +package org.springframework.security.expression; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.StandardEvaluationContext; + +public class ExpressionUtils { + public static Object doFilter(Object filterTarget, Expression filterExpression, StandardEvaluationContext ctx) { + SecurityExpressionRoot rootObject = (SecurityExpressionRoot) ctx.getRootContextObject(); + Set removeList = new HashSet(); + + if (filterTarget instanceof Collection) { + for (Object filterObject : (Collection)filterTarget) { + rootObject.setFilterObject(filterObject); + + if (!evaluateAsBoolean(filterExpression, ctx)) { + removeList.add(filterObject); + } + } + + for(Object toRemove : removeList) { + ((Collection)filterTarget).remove(toRemove); + } + + return filterTarget; + } + + if (filterTarget.getClass().isArray()) { + Object[] array = (Object[])filterTarget; + + for (int i = 0; i < array.length; i++) { + rootObject.setFilterObject(array[i]); + + if (!evaluateAsBoolean(filterExpression, ctx)) { + removeList.add(array[i]); + } + } + + Object[] filtered = (Object[]) Array.newInstance(filterTarget.getClass().getComponentType(), + array.length - removeList.size()); + for (int i = 0, j = 0; i < array.length; i++) { + if (!removeList.contains(array[i])) { + filtered[j++] = array[i]; + } + } + + return filtered; + } + + throw new IllegalArgumentException("Filter target must be a collection or array type, but was " + filterTarget); + } + + public static boolean evaluateAsBoolean(Expression expr, StandardEvaluationContext ctx) { + try { + return ((Boolean) expr.getValue(ctx, Boolean.class)).booleanValue(); + } catch (EvaluationException e) { + throw new IllegalArgumentException("Failed to evaluate expression", e); + } + } + + +} diff --git a/core/src/main/java/org/springframework/security/expression/MethodInvocationSecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/expression/MethodInvocationSecurityExpressionRoot.java new file mode 100644 index 0000000000..d246782362 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/MethodInvocationSecurityExpressionRoot.java @@ -0,0 +1,14 @@ +package org.springframework.security.expression; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.security.Authentication; + +public class MethodInvocationSecurityExpressionRoot extends SecurityExpressionRoot { + + MethodInvocationSecurityExpressionRoot(Authentication a, MethodInvocation mi) { + super(a); + + mi.getArguments(); + } + +} diff --git a/core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java new file mode 100644 index 0000000000..510702ae09 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java @@ -0,0 +1,67 @@ +package org.springframework.security.expression; + +import java.util.Set; + +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationTrustResolver; +import org.springframework.security.AuthenticationTrustResolverImpl; +import org.springframework.security.util.AuthorityUtils; + +public class SecurityExpressionRoot { + private Authentication authentication; + private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + private Object filterObject; + private Object returnObject; + + public SecurityExpressionRoot(Authentication a) { + this.authentication = a; + } + + public boolean hasRole(String role) { + return hasAnyRole(role); + } + + public boolean hasAnyRole(String... roles) { + Set roleSet = AuthorityUtils.authorityArrayToSet(authentication.getAuthorities()); + + for (String role : roles) { + if (roleSet.contains(role)) { + return true; + } + } + + return false; + } + + public boolean isAnonymous() { + return trustResolver.isAnonymous(authentication); + } + + public boolean isRememberMe() { + return trustResolver.isRememberMe(authentication); + } + + public String getName() { + return authentication.getName(); + } + + public boolean isFullyAuthenticated() { + return !trustResolver.isAnonymous(authentication) && !trustResolver.isRememberMe(authentication); + } + + public void setFilterObject(Object filterObject) { + this.filterObject = filterObject; + } + + public Object getFilterObject() { + return filterObject; + } + + public void setReturnObject(Object returnObject) { + this.returnObject = returnObject; + } + + public Object getReturnObject() { + return returnObject; + } +} diff --git a/core/src/main/java/org/springframework/security/expression/annotation/PostAuthorize.java b/core/src/main/java/org/springframework/security/expression/annotation/PostAuthorize.java new file mode 100644 index 0000000000..3b044b1d6b --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/annotation/PostAuthorize.java @@ -0,0 +1,26 @@ +package org.springframework.security.expression.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for specifying a method access-control expression which will be evaluated after a method has been invoked. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface PostAuthorize { + /** + * @return the Spring-EL expression to be evaluated after invoking the protected method + */ + public String value(); +} diff --git a/core/src/main/java/org/springframework/security/expression/annotation/PostFilter.java b/core/src/main/java/org/springframework/security/expression/annotation/PostFilter.java new file mode 100644 index 0000000000..6672174302 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/annotation/PostFilter.java @@ -0,0 +1,26 @@ +package org.springframework.security.expression.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for specifying a method filtering expression which will be evaluated after a method has been invoked. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface PostFilter { + /** + * @return the Spring-EL expression to be evaluated after invoking the protected method + */ + public String value(); +} diff --git a/core/src/main/java/org/springframework/security/expression/annotation/PreAuthorize.java b/core/src/main/java/org/springframework/security/expression/annotation/PreAuthorize.java new file mode 100644 index 0000000000..97c78aef9c --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/annotation/PreAuthorize.java @@ -0,0 +1,27 @@ +package org.springframework.security.expression.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for specifying a method access-control expression which will be evaluated to decide whether a + * method invocation is allowed or not. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface PreAuthorize { + /** + * @return the Spring-EL expression to be evaluated before invoking the protected method + */ + String value(); +} diff --git a/core/src/main/java/org/springframework/security/expression/annotation/PreFilter.java b/core/src/main/java/org/springframework/security/expression/annotation/PreFilter.java new file mode 100644 index 0000000000..7f13cf8894 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/annotation/PreFilter.java @@ -0,0 +1,31 @@ +package org.springframework.security.expression.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for specifying a method filtering expression which will be evaluated after a method has been invoked. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface PreFilter { + /** + * @return the Spring-EL expression to be evaluated before invoking the protected method + */ + public String value(); + + /** + * @return the name of the parameter which should be filtered (must be an array or collection) + */ + public String filterTarget(); +} diff --git a/core/src/main/java/org/springframework/security/expression/support/AbstractExpressionBasedMethodConfigAttribute.java b/core/src/main/java/org/springframework/security/expression/support/AbstractExpressionBasedMethodConfigAttribute.java new file mode 100644 index 0000000000..658f538847 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/support/AbstractExpressionBasedMethodConfigAttribute.java @@ -0,0 +1,42 @@ +package org.springframework.security.expression.support; + +import org.springframework.expression.Expression; +import org.springframework.expression.ParseException; +import org.springframework.expression.spel.SpelExpressionParser; +import org.springframework.security.ConfigAttribute; +import org.springframework.util.Assert; + +/** + * Contains both filtering and authorization expression meta-data for Spring-EL based access control. + *

+ * Base class for pre or post-invocation phases of a method invocation. + *

+ * Either filter or authorization expressions may be null, but not both. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +abstract class AbstractExpressionBasedMethodConfigAttribute implements ConfigAttribute { + private final Expression filterExpression; + private final Expression authorizeExpression; + + AbstractExpressionBasedMethodConfigAttribute(String filterExpression, String authorizeExpression) throws ParseException { + Assert.isTrue(filterExpression != null || authorizeExpression != null, "Filter and authorization Expressions cannot both be null"); + SpelExpressionParser parser = new SpelExpressionParser(); + this.filterExpression = filterExpression == null ? null : parser.parseExpression(filterExpression); + this.authorizeExpression = authorizeExpression == null ? null : parser.parseExpression(authorizeExpression); + } + + Expression getFilterExpression() { + return filterExpression; + } + + Expression getAuthorizeExpression() { + return authorizeExpression; + } + + public String getAttribute() { + return null; + } +} diff --git a/core/src/main/java/org/springframework/security/expression/support/ExpressionAnnotationMethodDefinitionSource.java b/core/src/main/java/org/springframework/security/expression/support/ExpressionAnnotationMethodDefinitionSource.java new file mode 100644 index 0000000000..871cbc0236 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/support/ExpressionAnnotationMethodDefinitionSource.java @@ -0,0 +1,111 @@ +package org.springframework.security.expression.support; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.expression.ParseException; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.config.SecurityConfigurationException; +import org.springframework.security.expression.annotation.PostAuthorize; +import org.springframework.security.expression.annotation.PostFilter; +import org.springframework.security.expression.annotation.PreAuthorize; +import org.springframework.security.expression.annotation.PreFilter; +import org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource; + +/** + * MethodDefinitionSource which extracts metadata from the @PreFilter and @PreAuthorize annotations + * placed on a method. The metadata is encapsulated in a {@link AbstractExpressionBasedMethodConfigAttribute} instance. + * + * @see MethodExpressionVoter + * + * @author Luke Taylor + * @since 2.5 + * @version $Id$ + */ +public class ExpressionAnnotationMethodDefinitionSource extends AbstractFallbackMethodDefinitionSource { + + @Override + protected List findAttributes(Method method, Class targetClass) { + ConfigAttribute pre = processPreInvocationAnnotations(AnnotationUtils.findAnnotation(method, PreFilter.class), + AnnotationUtils.findAnnotation(method, PreAuthorize.class)); + ConfigAttribute post = processPostInvocationAnnotations(AnnotationUtils.findAnnotation(method, PostFilter.class), + AnnotationUtils.findAnnotation(method, PostAuthorize.class)); + + if (pre == null && post == null) { + return null; + } + + List attrs = new ArrayList(2); + if (pre != null) { + attrs.add(pre); + } + + if (post != null) { + attrs.add(post); + } + + return attrs; + } + + @Override + protected List findAttributes(Class targetClass) { + ConfigAttribute pre = processPreInvocationAnnotations((PreFilter)targetClass.getAnnotation(PreFilter.class), + (PreAuthorize)targetClass.getAnnotation(PreAuthorize.class)); + ConfigAttribute post = processPostInvocationAnnotations((PostFilter)targetClass.getAnnotation(PostFilter.class), + (PostAuthorize)targetClass.getAnnotation(PostAuthorize.class)); + + if (pre == null && post == null) { + return null; + } + + List attrs = new ArrayList(2); + if (pre != null) { + attrs.add(pre); + } + + if (post != null) { + attrs.add(post); + } + + return attrs; + } + + public Collection getConfigAttributeDefinitions() { + return null; + } + + private ConfigAttribute processPreInvocationAnnotations(PreFilter preFilter, PreAuthorize preAuthz) { + if (preFilter == null && preAuthz == null) { + return null; + } + + String preAuthorizeExpression = preAuthz == null ? null : preAuthz.value(); + String preFilterExpression = preFilter == null ? null : preFilter.value(); + String filterObject = preFilter == null ? null : preFilter.filterTarget(); + + try { + return new PreInvocationExpressionBasedMethodConfigAttribute(preFilterExpression, filterObject, preAuthorizeExpression); + } catch (ParseException e) { + throw new SecurityConfigurationException("Failed to parse expression '" + e.getExpressionString() + "'", e); + } + } + + private ConfigAttribute processPostInvocationAnnotations(PostFilter postFilter, PostAuthorize postAuthz) { + if (postFilter == null && postAuthz == null) { + return null; + } + + String postAuthorizeExpression = postAuthz == null ? null : postAuthz.value(); + String postFilterExpression = postFilter == null ? null : postFilter.value(); + + try { + return new PostInvocationExpressionBasedMethodConfigAttribute(postFilterExpression, postAuthorizeExpression); + } catch (ParseException e) { + throw new SecurityConfigurationException("Failed to parse expression '" + e.getExpressionString() + "'", e); + } + } +} diff --git a/core/src/main/java/org/springframework/security/expression/support/MethodExpressionAfterInvocationProvider.java b/core/src/main/java/org/springframework/security/expression/support/MethodExpressionAfterInvocationProvider.java new file mode 100644 index 0000000000..919a978eff --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/support/MethodExpressionAfterInvocationProvider.java @@ -0,0 +1,110 @@ +package org.springframework.security.expression.support; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.StandardEvaluationContext; +import org.springframework.security.AccessDeniedException; +import org.springframework.security.Authentication; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.afterinvocation.AfterInvocationProvider; +import org.springframework.security.expression.ExpressionUtils; +import org.springframework.security.expression.SecurityExpressionRoot; +import org.springframework.util.ClassUtils; + +/** + * AfterInvocationProvider which handles the @PostAuthorize and @PostFilter annotation expressions. + * + * @author Luke Taylor + * @verson $Id$ + * @since 2.5 + */ +public class MethodExpressionAfterInvocationProvider implements AfterInvocationProvider { + + protected final Log logger = LogFactory.getLog(getClass()); + + private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + + public Object decide(Authentication authentication, Object object, ConfigAttributeDefinition config, Object returnedObject) + throws AccessDeniedException { + + PostInvocationExpressionBasedMethodConfigAttribute mca = findMethodAccessControlExpression(config); + + if (mca == null) { + return returnedObject; + } + + StandardEvaluationContext ctx = new StandardEvaluationContext(); + populateContextVariables(ctx, (MethodInvocation) object); + SecurityExpressionRoot expressionRoot = new SecurityExpressionRoot(authentication); + ctx.setRootObject(expressionRoot); + + Expression postFilter = mca.getFilterExpression(); + Expression postAuthorize = mca.getAuthorizeExpression(); + + if (postFilter != null) { + if (logger.isDebugEnabled()) { + logger.debug("Applying PostFilter expression " + postFilter); + } + + if (returnedObject != null) { + returnedObject = ExpressionUtils.doFilter(returnedObject, postFilter, ctx); + } else { + if (logger.isDebugEnabled()) { + logger.debug("Return object is null, filtering will be skipped"); + } + } + } + + expressionRoot.setReturnObject(returnedObject); + + if (postAuthorize != null && !ExpressionUtils.evaluateAsBoolean(postAuthorize, ctx)) { + if (logger.isDebugEnabled()) { + logger.debug("PostAuthorize expression rejected access"); + } + throw new AccessDeniedException("Access is denied"); + } + + return returnedObject; + } + + private void populateContextVariables(EvaluationContext ctx, MethodInvocation mi) { + Object[] args = mi.getArguments(); + Object targetObject = mi.getThis(); + Method method = ClassUtils.getMostSpecificMethod(mi.getMethod(), targetObject.getClass()); + String[] paramNames = parameterNameDiscoverer.getParameterNames(method); + + for(int i=0; i < args.length; i++) { + ctx.setVariable(paramNames[i], args[i]); + } + } + + private PostInvocationExpressionBasedMethodConfigAttribute findMethodAccessControlExpression(ConfigAttributeDefinition config) { + // Find the MethodAccessControlExpression attribute + for (ConfigAttribute attribute : config.getConfigAttributes()) { + if (attribute instanceof PostInvocationExpressionBasedMethodConfigAttribute) { + return (PostInvocationExpressionBasedMethodConfigAttribute)attribute; + } + } + + return null; + } + + public boolean supports(ConfigAttribute attribute) { + return attribute instanceof PostInvocationExpressionBasedMethodConfigAttribute; + } + + public boolean supports(Class clazz) { + return clazz.isAssignableFrom(MethodInvocation.class); + } + + + +} diff --git a/core/src/main/java/org/springframework/security/expression/support/MethodExpressionVoter.java b/core/src/main/java/org/springframework/security/expression/support/MethodExpressionVoter.java new file mode 100644 index 0000000000..19fb8bb2d4 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/support/MethodExpressionVoter.java @@ -0,0 +1,109 @@ +package org.springframework.security.expression.support; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.StandardEvaluationContext; +import org.springframework.security.Authentication; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.expression.ExpressionUtils; +import org.springframework.security.expression.SecurityExpressionRoot; +import org.springframework.security.vote.AccessDecisionVoter; +import org.springframework.util.ClassUtils; + +/** + * Voter which performs the actions for @PreFilter and @PostAuthorize annotations. + *

+ * If only a @PreFilter condition is specified, it will vote to grant access, otherwise it will vote + * to grant or deny access depending on whether the @PostAuthorize expression evaluates to 'true' or 'false', + * respectively. + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ +public class MethodExpressionVoter implements AccessDecisionVoter { + protected final Log logger = LogFactory.getLog(getClass()); + + // TODO: Share this between classes + private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + + public boolean supports(ConfigAttribute attribute) { + return attribute instanceof AbstractExpressionBasedMethodConfigAttribute; + } + + public boolean supports(Class clazz) { + return clazz.isAssignableFrom(MethodInvocation.class); + } + + public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { + PreInvocationExpressionBasedMethodConfigAttribute mace = findMethodAccessControlExpression(config); + + if (mace == null) { + // No expression based metadata, so abstain + return ACCESS_ABSTAIN; + } + + StandardEvaluationContext ctx = new StandardEvaluationContext(); + Object filterTarget = + populateContextVariablesAndFindFilterTarget(ctx, (MethodInvocation)object, mace.getFilterTarget()); + + ctx.setRootObject(new SecurityExpressionRoot(authentication)); + + Expression preFilter = mace.getFilterExpression(); + Expression preAuthorize = mace.getAuthorizeExpression(); + + if (preFilter != null) { + // TODO: Allow null target if only single parameter, or single collection/array? + Object filtered = ExpressionUtils.doFilter(filterTarget, preFilter, ctx); + } + + if (preAuthorize == null) { + return ACCESS_GRANTED; + } + + return ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) ? ACCESS_GRANTED : ACCESS_DENIED; + } + + private Object populateContextVariablesAndFindFilterTarget(EvaluationContext ctx, MethodInvocation mi, + String filterTargetName) { + + Object[] args = mi.getArguments(); + Object targetObject = mi.getThis(); + Method method = ClassUtils.getMostSpecificMethod(mi.getMethod(), targetObject.getClass()); + Object filterTarget = null; + String[] paramNames = parameterNameDiscoverer.getParameterNames(method); + + for(int i=0; i < args.length; i++) { + ctx.setVariable(paramNames[i], args[i]); + if (filterTargetName != null && paramNames[i].equals(filterTargetName)) { + filterTarget = args[i]; + } + } + + if (filterTargetName != null && filterTarget == null) { + throw new IllegalArgumentException("No filter target argument with name " + filterTargetName + + " found in method: " + method.getName()); + } + + return filterTarget; + } + + private PreInvocationExpressionBasedMethodConfigAttribute findMethodAccessControlExpression(ConfigAttributeDefinition config) { + // Find the MethodAccessControlExpression attribute + for (ConfigAttribute attribute : config.getConfigAttributes()) { + if (attribute instanceof AbstractExpressionBasedMethodConfigAttribute) { + return (PreInvocationExpressionBasedMethodConfigAttribute)attribute; + } + } + + return null; + } +} diff --git a/core/src/main/java/org/springframework/security/expression/support/PostInvocationExpressionBasedMethodConfigAttribute.java b/core/src/main/java/org/springframework/security/expression/support/PostInvocationExpressionBasedMethodConfigAttribute.java new file mode 100644 index 0000000000..52a94ac7dc --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/support/PostInvocationExpressionBasedMethodConfigAttribute.java @@ -0,0 +1,12 @@ +package org.springframework.security.expression.support; + +import org.springframework.expression.ParseException; + +class PostInvocationExpressionBasedMethodConfigAttribute extends AbstractExpressionBasedMethodConfigAttribute { + + PostInvocationExpressionBasedMethodConfigAttribute(String filterExpression, String authorizeExpression) + throws ParseException { + super(filterExpression, authorizeExpression); + } + +} diff --git a/core/src/main/java/org/springframework/security/expression/support/PreInvocationExpressionBasedMethodConfigAttribute.java b/core/src/main/java/org/springframework/security/expression/support/PreInvocationExpressionBasedMethodConfigAttribute.java new file mode 100644 index 0000000000..1c522e1a96 --- /dev/null +++ b/core/src/main/java/org/springframework/security/expression/support/PreInvocationExpressionBasedMethodConfigAttribute.java @@ -0,0 +1,18 @@ +package org.springframework.security.expression.support; + +import org.springframework.expression.ParseException; + +class PreInvocationExpressionBasedMethodConfigAttribute extends AbstractExpressionBasedMethodConfigAttribute { + private final String filterTarget; + + PreInvocationExpressionBasedMethodConfigAttribute(String filterExpression, String filterTarget, + String authorizeExpression) throws ParseException { + super(filterExpression, authorizeExpression); + + this.filterTarget = filterTarget; + } + + String getFilterTarget() { + return filterTarget; + } +} diff --git a/core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java b/core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java index 237617ff8f..14c9023f09 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java +++ b/core/src/main/java/org/springframework/security/intercept/method/AbstractFallbackMethodDefinitionSource.java @@ -2,6 +2,7 @@ package org.springframework.security.intercept.method; import java.lang.reflect.Method; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.aopalliance.intercept.MethodInvocation; @@ -9,6 +10,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.CodeSignature; +import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -88,12 +90,14 @@ public abstract class AbstractFallbackMethodDefinitionSource implements MethodDe } else { // We need to work it out. - ConfigAttributeDefinition cfgAtt = computeAttributes(method, targetClass); + List attributes = computeAttributes(method, targetClass); + ConfigAttributeDefinition cfgAtt = null; // Put it in the cache. - if (cfgAtt == null) { + if (attributes == null) { this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE); - } - else { + } else { + cfgAtt = new ConfigAttributeDefinition(attributes); + if (logger.isDebugEnabled()) { logger.debug("Adding security method [" + cacheKey + "] with attribute [" + cfgAtt + "]"); } @@ -110,12 +114,12 @@ public abstract class AbstractFallbackMethodDefinitionSource implements MethodDe * @param targetClass the target class for this invocation (may be null) * @return */ - private ConfigAttributeDefinition computeAttributes(Method method, Class targetClass) { + private List computeAttributes(Method method, Class targetClass) { // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); // First try is the method in the target class. - ConfigAttributeDefinition attr = findAttributes(specificMethod, targetClass); + List attr = findAttributes(specificMethod, targetClass); if (attr != null) { return attr; } @@ -152,7 +156,7 @@ public abstract class AbstractFallbackMethodDefinitionSource implements MethodDe * @param targetClass the target class for the invocation (may be null) * @return the security metadata (or null if no metadata applies) */ - protected abstract ConfigAttributeDefinition findAttributes(Method method, Class targetClass); + protected abstract List findAttributes(Method method, Class targetClass); /** * Obtains the security metadata registered against the specified class. @@ -166,7 +170,7 @@ public abstract class AbstractFallbackMethodDefinitionSource implements MethodDe * @param clazz the target class for the invocation (never null) * @return the security metadata (or null if no metadata applies) */ - protected abstract ConfigAttributeDefinition findAttributes(Class clazz); + protected abstract List findAttributes(Class clazz); private static class DefaultCacheKey { diff --git a/core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java b/core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java index bc0a68f1d7..d9afe74907 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java +++ b/core/src/main/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSource.java @@ -27,6 +27,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -54,10 +55,10 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); /** Map from RegisteredMethod to ConfigAttributeDefinition */ - protected Map methodMap = new HashMap(); + protected Map> methodMap = new HashMap(); /** Map from RegisteredMethod to name pattern used for registration */ - private Map nameMap = new HashMap(); + private Map nameMap = new HashMap(); //~ Methods ======================================================================================================== @@ -73,21 +74,21 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); - addSecureMethod((String)entry.getKey(), (ConfigAttributeDefinition)entry.getValue()); + addSecureMethod((String)entry.getKey(), (List)entry.getValue()); } } /** * Implementation does not support class-level attributes. */ - protected ConfigAttributeDefinition findAttributes(Class clazz) { + protected List findAttributes(Class clazz) { return null; } /** * Will walk the method inheritance tree to find the most specific declaration applicable. */ - protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { + protected List findAttributes(Method method, Class targetClass) { if (targetClass == null) { return null; } @@ -95,10 +96,10 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini return findAttributesSpecifiedAgainst(method, targetClass); } - private ConfigAttributeDefinition findAttributesSpecifiedAgainst(Method method, Class clazz) { + private List findAttributesSpecifiedAgainst(Method method, Class clazz) { RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz); if (methodMap.containsKey(registeredMethod)) { - return (ConfigAttributeDefinition) methodMap.get(registeredMethod); + return (List) methodMap.get(registeredMethod); } // Search superclass if (clazz.getSuperclass() != null) { @@ -114,7 +115,7 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini * @param name type and method name, separated by a dot * @param attr required authorities associated with the method */ - public void addSecureMethod(String name, ConfigAttributeDefinition attr) { + public void addSecureMethod(String name, List attr) { int lastDotIndex = name.lastIndexOf("."); if (lastDotIndex == -1) { @@ -138,7 +139,7 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini * @param mappedName mapped method name, which the javaType has declared or inherited * @param attr required authorities associated with the method */ - public void addSecureMethod(Class javaType, String mappedName, ConfigAttributeDefinition attr) { + public void addSecureMethod(Class javaType, String mappedName, List attr) { String name = javaType.getName() + '.' + mappedName; if (logger.isDebugEnabled()) { @@ -187,7 +188,7 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini * the existing match will be retained, so that if this method is called for a more general pointcut * it will not override a more specific one which has already been added. This */ - public void addSecureMethod(Class javaType, Method method, ConfigAttributeDefinition attr) { + public void addSecureMethod(Class javaType, Method method, List attr) { RegisteredMethod key = new RegisteredMethod(method, javaType); if (methodMap.containsKey(key)) { @@ -204,7 +205,7 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini * @param method the method to be secured * @param attr required authorities associated with the method */ - private void addSecureMethod(RegisteredMethod method, ConfigAttributeDefinition attr) { + private void addSecureMethod(RegisteredMethod method, List attr) { Assert.notNull(method, "RegisteredMethod required"); Assert.notNull(attr, "Configuration attribute required"); if (logger.isInfoEnabled()) { @@ -219,7 +220,13 @@ public class MapBasedMethodDefinitionSource extends AbstractFallbackMethodDefini * @return the attributes explicitly defined against this bean */ public Collection getConfigAttributeDefinitions() { - return Collections.unmodifiableCollection(methodMap.values()); + List configAttrs = new ArrayList(methodMap.values().size()); + + for(List attrList : methodMap.values()) { + configAttrs.add(new ConfigAttributeDefinition(attrList)); + } + + return configAttrs; } /** diff --git a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java deleted file mode 100644 index cddfae5298..0000000000 --- a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionAttributes.java +++ /dev/null @@ -1,71 +0,0 @@ -/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.intercept.method; - -import java.lang.reflect.Method; -import java.util.Collection; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.metadata.Attributes; -import org.springframework.security.ConfigAttribute; -import org.springframework.security.ConfigAttributeDefinition; -import org.springframework.util.Assert; - - -/** - * Provides {@link ConfigAttributeDefinition}s for a method signature (via the lookupAttributes method) - * by delegating to a configured {@link Attributes} object. The latter may use Commons attributes - * or some other approach to determine the ConfigAttributes which apply. - * - *

- * Note that attributes defined against parent classes (either for their methods or interfaces) are not - * detected. The attributes must be defined against an explicit method or interface on the intercepted class. - *

- * - * Attributes detected that do not implement {@link ConfigAttribute} will be ignored. - * - * @author Cameron Braid - * @author Ben Alex - * @version $Id$ - */ -public class MethodDefinitionAttributes extends AbstractFallbackMethodDefinitionSource implements InitializingBean { - //~ Instance fields ================================================================================================ - - private Attributes attributes; - - //~ Methods ======================================================================================================== - - public void afterPropertiesSet() throws Exception { - Assert.notNull(attributes, "attributes required"); - } - - public Collection getConfigAttributeDefinitions() { - return null; - } - - protected ConfigAttributeDefinition findAttributes(Class clazz) { - return ConfigAttributeDefinition.createFiltered(attributes.getAttributes(clazz)); - } - - protected ConfigAttributeDefinition findAttributes(Method method, Class targetClass) { - return ConfigAttributeDefinition.createFiltered(attributes.getAttributes(method)); - } - - public void setAttributes(Attributes attributes) { - Assert.notNull(attributes, "Attributes required"); - this.attributes = attributes; - } -} diff --git a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java index 296d99b97c..15ddc8c94e 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java +++ b/core/src/main/java/org/springframework/security/intercept/method/MethodDefinitionSourceEditor.java @@ -15,7 +15,8 @@ package org.springframework.security.intercept.method; -import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.SecurityConfig; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,7 +26,9 @@ import org.springframework.util.StringUtils; import java.beans.PropertyEditorSupport; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Properties; import java.util.Map; import java.util.LinkedHashMap; @@ -65,8 +68,13 @@ public class MethodDefinitionSourceEditor extends PropertyEditorSupport { String value = props.getProperty(name); String[] tokens = StringUtils.commaDelimitedListToStringArray(value); + List attributes = new ArrayList(tokens.length); - mappings.put(name, new ConfigAttributeDefinition(tokens)); + for(String token : tokens) { + attributes.add(new SecurityConfig(token)); + } + + mappings.put(name, attributes); } setValue(new MapBasedMethodDefinitionSource(mappings)); diff --git a/core/src/main/java/org/springframework/security/intercept/method/ProtectPointcutPostProcessor.java b/core/src/main/java/org/springframework/security/intercept/method/ProtectPointcutPostProcessor.java index 92a9b9cc1f..6e33d870cf 100644 --- a/core/src/main/java/org/springframework/security/intercept/method/ProtectPointcutPostProcessor.java +++ b/core/src/main/java/org/springframework/security/intercept/method/ProtectPointcutPostProcessor.java @@ -4,6 +4,7 @@ import java.lang.reflect.Method; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -14,6 +15,7 @@ import org.aspectj.weaver.tools.PointcutParser; import org.aspectj.weaver.tools.PointcutPrimitive; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor; import org.springframework.util.Assert; @@ -57,7 +59,7 @@ public final class ProtectPointcutPostProcessor implements BeanPostProcessor { private static final Log logger = LogFactory.getLog(ProtectPointcutPostProcessor.class); - private Map pointcutMap = new LinkedHashMap(); /** Key: string-based pointcut, value: ConfigAttributeDefinition */ + private Map> pointcutMap = new LinkedHashMap(); private MapBasedMethodDefinitionSource mapBasedMethodDefinitionSource; private PointcutParser parser; @@ -119,7 +121,7 @@ public final class ProtectPointcutPostProcessor implements BeanPostProcessor { // Handle accordingly if (matches) { - ConfigAttributeDefinition attr = (ConfigAttributeDefinition) pointcutMap.get(expression.getPointcutExpression()); + List attr = pointcutMap.get(expression.getPointcutExpression()); if (logger.isDebugEnabled()) { logger.debug("AspectJ pointcut expression '" + expression.getPointcutExpression() + "' matches target class '" + targetClass.getName() + "' (bean ID '" + beanName + "') for method '" + method + "'; registering security configuration attribute '" + attr + "'"); @@ -131,18 +133,17 @@ public final class ProtectPointcutPostProcessor implements BeanPostProcessor { return matches; } - public void setPointcutMap(Map map) { + public void setPointcutMap(Map> map) { Assert.notEmpty(map); Iterator i = map.keySet().iterator(); while (i.hasNext()) { String expression = i.next().toString(); - Object value = map.get(expression); - Assert.isInstanceOf(ConfigAttributeDefinition.class, value, "Map keys must be instances of ConfigAttributeDefinition"); - addPointcut(expression, (ConfigAttributeDefinition) value); + List value = map.get(expression); + addPointcut(expression, value); } } - private void addPointcut(String pointcutExpression, ConfigAttributeDefinition definition) { + private void addPointcut(String pointcutExpression, List definition) { Assert.hasText(pointcutExpression, "An AspectJ pointcut expression is required"); Assert.notNull(definition, "ConfigAttributeDefinition required"); pointcutExpression = replaceBooleanOperators(pointcutExpression); diff --git a/core/src/main/java/org/springframework/security/util/InMemoryXmlApplicationContext.java b/core/src/main/java/org/springframework/security/util/InMemoryXmlApplicationContext.java index 8f1083906a..d9df879c7a 100644 --- a/core/src/main/java/org/springframework/security/util/InMemoryXmlApplicationContext.java +++ b/core/src/main/java/org/springframework/security/util/InMemoryXmlApplicationContext.java @@ -13,9 +13,9 @@ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext " xmlns:b='http://www.springframework.org/schema/beans'\n" + " xmlns:aop='http://www.springframework.org/schema/aop'\n" + " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n" + - " xsi:schemaLocation='http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd\n" + - "http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd\n" + - "http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.2.xsd'>\n"; + " xsi:schemaLocation='http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\n" + + "http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd\n" + + "http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.5.xsd'>\n"; private static final String BEANS_CLOSE = "\n"; Resource inMemoryXml; diff --git a/core/src/main/resources/META-INF/spring.schemas b/core/src/main/resources/META-INF/spring.schemas index 5bf71855e0..9b0c7c8952 100644 --- a/core/src/main/resources/META-INF/spring.schemas +++ b/core/src/main/resources/META-INF/spring.schemas @@ -1,5 +1,6 @@ -http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-2.0.4.xsd +http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-2.5.xsd http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/springframework/security/config/spring-security-2.0.xsd http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd +http\://www.springframework.org/schema/security/spring-security-2.5.xsd=org/springframework/security/config/spring-security-2.5.xsd diff --git a/core/src/test/java/org/springframework/security/annotation/BusinessService.java b/core/src/test/java/org/springframework/security/annotation/BusinessService.java index 2b7724a422..af2ecb2541 100644 --- a/core/src/test/java/org/springframework/security/annotation/BusinessService.java +++ b/core/src/test/java/org/springframework/security/annotation/BusinessService.java @@ -15,9 +15,13 @@ package org.springframework.security.annotation; +import java.util.List; + import javax.annotation.security.RolesAllowed; import javax.annotation.security.PermitAll; +import org.springframework.security.expression.annotation.PreAuthorize; + /** * @version $Id$ */ @@ -28,6 +32,7 @@ public interface BusinessService { @Secured({"ROLE_ADMIN"}) @RolesAllowed({"ROLE_ADMIN"}) + @PreAuthorize("hasRole('ROLE_ADMIN')") public void someAdminMethod(); @Secured({"ROLE_USER", "ROLE_ADMIN"}) @@ -45,4 +50,11 @@ public interface BusinessService { public int someOther(String s); public int someOther(int input); + + public List methodReturningAList(List someList); + + public Object[] methodReturningAnArray(Object[] someArray); + + public List methodReturningAList(String userName, String extraParam); + } diff --git a/core/src/test/java/org/springframework/security/annotation/BusinessServiceImpl.java b/core/src/test/java/org/springframework/security/annotation/BusinessServiceImpl.java index 79287c2858..9d79e5a8b3 100644 --- a/core/src/test/java/org/springframework/security/annotation/BusinessServiceImpl.java +++ b/core/src/test/java/org/springframework/security/annotation/BusinessServiceImpl.java @@ -1,5 +1,8 @@ package org.springframework.security.annotation; +import java.util.ArrayList; +import java.util.List; + /** * * @author Joe Scalise @@ -33,4 +36,17 @@ public class BusinessServiceImpl implements BusinessService { public int someOther(int input) { return input; } + + public List methodReturningAList(List someList) { + return someList; + } + + public List methodReturningAList(String userName, String arg2) { + return new ArrayList(); + } + + public Object[] methodReturningAnArray(Object[] someArray) { + return null; + } + } diff --git a/core/src/test/java/org/springframework/security/annotation/Jsr250BusinessServiceImpl.java b/core/src/test/java/org/springframework/security/annotation/Jsr250BusinessServiceImpl.java index 70f13e7a5f..d79a77ffc4 100644 --- a/core/src/test/java/org/springframework/security/annotation/Jsr250BusinessServiceImpl.java +++ b/core/src/test/java/org/springframework/security/annotation/Jsr250BusinessServiceImpl.java @@ -1,5 +1,8 @@ package org.springframework.security.annotation; +import java.util.ArrayList; +import java.util.List; + import javax.annotation.security.RolesAllowed; import javax.annotation.security.PermitAll; @@ -34,4 +37,17 @@ public class Jsr250BusinessServiceImpl implements BusinessService { public int someOther(int input) { return input; } + + public List methodReturningAList(List someList) { + return someList; + } + + public List methodReturningAList(String userName, String arg2) { + return new ArrayList(); + } + + public Object[] methodReturningAnArray(Object[] someArray) { + return null; + } + } diff --git a/core/src/test/java/org/springframework/security/annotation/Jsr250MethodDefinitionSourceTests.java b/core/src/test/java/org/springframework/security/annotation/Jsr250MethodDefinitionSourceTests.java index 56762dd1fc..c78666472b 100644 --- a/core/src/test/java/org/springframework/security/annotation/Jsr250MethodDefinitionSourceTests.java +++ b/core/src/test/java/org/springframework/security/annotation/Jsr250MethodDefinitionSourceTests.java @@ -2,6 +2,8 @@ package org.springframework.security.annotation; import static org.junit.Assert.assertEquals; +import java.util.List; + import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; @@ -9,6 +11,7 @@ import javax.annotation.security.RolesAllowed; import junit.framework.Assert; import org.junit.Test; +import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; /** @@ -17,56 +20,56 @@ import org.springframework.security.ConfigAttributeDefinition; * @version $Id$ */ public class Jsr250MethodDefinitionSourceTests { - Jsr250MethodDefinitionSource mds = new Jsr250MethodDefinitionSource(); + Jsr250MethodDefinitionSource mds = new Jsr250MethodDefinitionSource(); A a = new A(); UserAllowedClass userAllowed = new UserAllowedClass(); DenyAllClass denyAll = new DenyAllClass(); @Test public void methodWithRolesAllowedHasCorrectAttribute() throws Exception { - ConfigAttributeDefinition accessAttributes = mds.findAttributes(a.getClass().getMethod("adminMethod"), null); - assertEquals(1, accessAttributes.getConfigAttributes().size()); - assertEquals("ADMIN", accessAttributes.getConfigAttributes().iterator().next().toString()); + List accessAttributes = mds.findAttributes(a.getClass().getMethod("adminMethod"), null); + assertEquals(1, accessAttributes.size()); + assertEquals("ADMIN", accessAttributes.get(0).toString()); } @Test public void permitAllMethodHasPermitAllAttribute() throws Exception { - ConfigAttributeDefinition accessAttributes = mds.findAttributes(a.getClass().getMethod("permitAllMethod"), null); - assertEquals(1, accessAttributes.getConfigAttributes().size()); - assertEquals("javax.annotation.security.PermitAll", accessAttributes.getConfigAttributes().iterator().next().toString()); + List accessAttributes = mds.findAttributes(a.getClass().getMethod("permitAllMethod"), null); + assertEquals(1, accessAttributes.size()); + assertEquals("javax.annotation.security.PermitAll", accessAttributes.get(0).toString()); } @Test public void noRoleMethodHasDenyAllAttributeWithDenyAllClass() throws Exception { - ConfigAttributeDefinition accessAttributes = mds.findAttributes(denyAll.getClass()); - assertEquals(1, accessAttributes.getConfigAttributes().size()); - assertEquals("javax.annotation.security.DenyAll", accessAttributes.getConfigAttributes().iterator().next().toString()); + List accessAttributes = mds.findAttributes(denyAll.getClass()); + assertEquals(1, accessAttributes.size()); + assertEquals("javax.annotation.security.DenyAll", accessAttributes.get(0).toString()); } @Test public void adminMethodHasAdminAttributeWithDenyAllClass() throws Exception { - ConfigAttributeDefinition accessAttributes = mds.findAttributes(denyAll.getClass().getMethod("adminMethod"), null); - assertEquals(1, accessAttributes.getConfigAttributes().size()); - assertEquals("ADMIN", accessAttributes.getConfigAttributes().iterator().next().toString()); + List accessAttributes = mds.findAttributes(denyAll.getClass().getMethod("adminMethod"), null); + assertEquals(1, accessAttributes.size()); + assertEquals("ADMIN", accessAttributes.get(0).toString()); } @Test public void noRoleMethodHasNoAttributes() throws Exception { - ConfigAttributeDefinition accessAttributes = mds.findAttributes(a.getClass().getMethod("noRoleMethod"), null); + List accessAttributes = mds.findAttributes(a.getClass().getMethod("noRoleMethod"), null); Assert.assertNull(accessAttributes); } - + @Test public void classRoleIsAppliedToNoRoleMethod() throws Exception { - ConfigAttributeDefinition accessAttributes = mds.findAttributes(userAllowed.getClass().getMethod("noRoleMethod"), null); + List accessAttributes = mds.findAttributes(userAllowed.getClass().getMethod("noRoleMethod"), null); Assert.assertNull(accessAttributes); } @Test public void methodRoleOverridesClassRole() throws Exception { - ConfigAttributeDefinition accessAttributes = mds.findAttributes(userAllowed.getClass().getMethod("adminMethod"), null); - assertEquals(1, accessAttributes.getConfigAttributes().size()); - assertEquals("ADMIN", accessAttributes.getConfigAttributes().iterator().next().toString()); + List accessAttributes = mds.findAttributes(userAllowed.getClass().getMethod("adminMethod"), null); + assertEquals(1, accessAttributes.size()); + assertEquals("ADMIN", accessAttributes.get(0).toString()); } //~ Inner Classes ====================================================================================================== @@ -87,7 +90,7 @@ public class Jsr250MethodDefinitionSourceTests { public void noRoleMethod() {} @RolesAllowed("ADMIN") - public void adminMethod() {} + public void adminMethod() {} } @DenyAll @@ -96,7 +99,7 @@ public class Jsr250MethodDefinitionSourceTests { public void noRoleMethod() {} @RolesAllowed("ADMIN") - public void adminMethod() {} + public void adminMethod() {} } diff --git a/core/src/test/java/org/springframework/security/annotation/SecuredMethodDefinitionSourceTests.java b/core/src/test/java/org/springframework/security/annotation/SecuredMethodDefinitionSourceTests.java index 1bb761ea6f..78ba303baa 100644 --- a/core/src/test/java/org/springframework/security/annotation/SecuredMethodDefinitionSourceTests.java +++ b/core/src/test/java/org/springframework/security/annotation/SecuredMethodDefinitionSourceTests.java @@ -15,11 +15,13 @@ package org.springframework.security.annotation; import java.lang.reflect.Method; +import java.util.List; import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.SecurityConfig; import org.springframework.util.StringUtils; @@ -50,22 +52,19 @@ public class SecuredMethodDefinitionSourceTests extends TestCase { fail("Should be a superMethod called 'someUserMethod3' on class!"); } - ConfigAttributeDefinition attrs = this.mds.findAttributes(method, DepartmentServiceImpl.class); + List attrs = mds.findAttributes(method, DepartmentServiceImpl.class); assertNotNull(attrs); if (logger.isDebugEnabled()) { - logger.debug("attrs: " + StringUtils.collectionToCommaDelimitedString(attrs.getConfigAttributes())); + logger.debug("attrs: " + StringUtils.collectionToCommaDelimitedString(attrs)); } // expect 1 attribute - assertTrue("Did not find 1 attribute", attrs.getConfigAttributes().size() == 1); + assertTrue("Did not find 1 attribute", attrs.size() == 1); // should have 1 SecurityConfig - for (Object obj : attrs.getConfigAttributes()) { - assertTrue(obj instanceof SecurityConfig); - - SecurityConfig sc = (SecurityConfig) obj; + for (ConfigAttribute sc : attrs) { assertEquals("Found an incorrect role", "ROLE_ADMIN", sc.getAttribute()); } @@ -77,37 +76,35 @@ public class SecuredMethodDefinitionSourceTests extends TestCase { fail("Should be a superMethod called 'someUserMethod3' on class!"); } - ConfigAttributeDefinition superAttrs = this.mds.findAttributes(superMethod, DepartmentServiceImpl.class); + List superAttrs = this.mds.findAttributes(superMethod, DepartmentServiceImpl.class); assertNotNull(superAttrs); if (logger.isDebugEnabled()) { - logger.debug("superAttrs: " + StringUtils.collectionToCommaDelimitedString(superAttrs.getConfigAttributes())); + logger.debug("superAttrs: " + StringUtils.collectionToCommaDelimitedString(superAttrs)); } // This part of the test relates to SEC-274 // expect 1 attribute - assertTrue("Did not find 1 attribute", superAttrs.getConfigAttributes().size() == 1); + assertEquals("Did not find 1 attribute", 1, superAttrs.size()); // should have 1 SecurityConfig - for (Object obj : superAttrs.getConfigAttributes()) { - assertTrue(obj instanceof SecurityConfig); - SecurityConfig sc = (SecurityConfig) obj; + for (ConfigAttribute sc : superAttrs) { assertEquals("Found an incorrect role", "ROLE_ADMIN", sc.getAttribute()); } } public void testGetAttributesClass() { - ConfigAttributeDefinition attrs = this.mds.findAttributes(BusinessService.class); + List attrs = this.mds.findAttributes(BusinessService.class); assertNotNull(attrs); // expect 1 annotation - assertTrue(attrs.getConfigAttributes().size() == 1); + assertEquals(1, attrs.size()); // should have 1 SecurityConfig - SecurityConfig sc = (SecurityConfig) attrs.getConfigAttributes().iterator().next(); + SecurityConfig sc = ((SecurityConfig) attrs.get(0)); - assertTrue(sc.getAttribute().equals("ROLE_USER")); + assertEquals("ROLE_USER", sc.getAttribute()); } public void testGetAttributesMethod() { @@ -119,21 +116,19 @@ public class SecuredMethodDefinitionSourceTests extends TestCase { fail("Should be a method called 'someUserAndAdminMethod' on class!"); } - ConfigAttributeDefinition attrs = this.mds.findAttributes(method, BusinessService.class); + List attrs = this.mds.findAttributes(method, BusinessService.class); assertNotNull(attrs); // expect 2 attributes - assertTrue(attrs.getConfigAttributes().size() == 2); + assertEquals(2, attrs.size()); boolean user = false; boolean admin = false; // should have 2 SecurityConfigs - for (Object obj : attrs.getConfigAttributes()) { - assertTrue(obj instanceof SecurityConfig); - - SecurityConfig sc = (SecurityConfig) obj; + for (ConfigAttribute sc : attrs) { + assertTrue(sc instanceof SecurityConfig); if (sc.getAttribute().equals("ROLE_USER")) { user = true; @@ -145,5 +140,5 @@ public class SecuredMethodDefinitionSourceTests extends TestCase { // expect to have ROLE_USER and ROLE_ADMIN assertTrue(user && admin); } - + } diff --git a/core/src/test/java/org/springframework/security/config/CustomAfterInvocationProviderBeanDefinitionDecoratorTests.java b/core/src/test/java/org/springframework/security/config/CustomAfterInvocationProviderBeanDefinitionDecoratorTests.java index 73d3c6c274..33222274a4 100644 --- a/core/src/test/java/org/springframework/security/config/CustomAfterInvocationProviderBeanDefinitionDecoratorTests.java +++ b/core/src/test/java/org/springframework/security/config/CustomAfterInvocationProviderBeanDefinitionDecoratorTests.java @@ -33,8 +33,8 @@ public class CustomAfterInvocationProviderBeanDefinitionDecoratorTests { MethodSecurityInterceptor msi = (MethodSecurityInterceptor) appContext.getBean(BeanIds.METHOD_SECURITY_INTERCEPTOR); AfterInvocationProviderManager apm = (AfterInvocationProviderManager) msi.getAfterInvocationManager(); assertNotNull(apm); - assertEquals(1, apm.getProviders().size()); - assertTrue(apm.getProviders().get(0) instanceof MockAfterInvocationProvider); + assertEquals(2, apm.getProviders().size()); + assertTrue(apm.getProviders().get(1) instanceof MockAfterInvocationProvider); } private void setContext(String context) { diff --git a/core/src/test/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParserTests.java b/core/src/test/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParserTests.java index 4b2c63767e..c55ec7c8e7 100644 --- a/core/src/test/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParserTests.java @@ -3,6 +3,9 @@ package org.springframework.security.config; import static org.junit.Assert.*; import static org.springframework.security.config.ConfigTestUtils.*; +import java.util.ArrayList; +import java.util.List; + import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; @@ -180,6 +183,50 @@ public class GlobalMethodSecurityBeanDefinitionParserTests { target.someUserMethod1(); } + @Test(expected=AccessDeniedException.class) + public void accessIsDeniedForHasRoleExpression() { + setContext( + "" + + "" + + AUTH_PROVIDER_XML); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword")); + target = (BusinessService) appContext.getBean("target"); + target.someAdminMethod(); + } + + @Test + public void preAndPostFilterAnnotationsWorkWithLists() { + setContext( + "" + + "" + + AUTH_PROVIDER_XML); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword")); + target = (BusinessService) appContext.getBean("target"); + List arg = new ArrayList(); + arg.add("joe"); + arg.add("bob"); + arg.add("sam"); + List result = target.methodReturningAList(arg); + // Expression is (filterObject == name or filterObject == 'sam'), so "joe" should be gone after pre-filter + // PostFilter should remove sam from the return object + assertEquals(1, result.size()); + assertEquals("bob", result.get(0)); + } + + @Test + public void preAndPostFilterAnnotationsWorkWithArrays() { + setContext( + "" + + "" + + AUTH_PROVIDER_XML); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword")); + target = (BusinessService) appContext.getBean("target"); + Object[] arg = new String[] {"joe", "bob", "sam"}; + Object[] result = target.methodReturningAnArray(arg); + assertEquals(1, result.length); + assertEquals("bob", result[0]); + } + private void setContext(String context) { appContext = new InMemoryXmlApplicationContext(context); } diff --git a/core/src/test/java/org/springframework/security/expression/support/MethodExpressionVoterTests.java b/core/src/test/java/org/springframework/security/expression/support/MethodExpressionVoterTests.java new file mode 100644 index 0000000000..7d0c15947d --- /dev/null +++ b/core/src/test/java/org/springframework/security/expression/support/MethodExpressionVoterTests.java @@ -0,0 +1,71 @@ +package org.springframework.security.expression.support; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.aopalliance.intercept.MethodInvocation; +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.annotation.ExpressionProtectedBusinessServiceImpl; +import org.springframework.security.expression.support.AbstractExpressionBasedMethodConfigAttribute; +import org.springframework.security.expression.support.MethodExpressionVoter; +import org.springframework.security.providers.TestingAuthenticationToken; +import org.springframework.security.util.SimpleMethodInvocation; +import org.springframework.security.vote.AccessDecisionVoter; + +public class MethodExpressionVoterTests { + private TestingAuthenticationToken joe = new TestingAuthenticationToken("joe", "joespass", "blah"); + private MethodInvocation miStringArgs; + private MethodInvocation miListArg; + private List listArg; + + @Before + public void setUp() throws Exception { + Method m = ExpressionProtectedBusinessServiceImpl.class.getMethod("methodReturningAList", + String.class, String.class); + miStringArgs = new SimpleMethodInvocation(new Object(), m, new String[] {"joe", "arg2Value"}); + m = ExpressionProtectedBusinessServiceImpl.class.getMethod("methodReturningAList", List.class); + listArg = new ArrayList(Arrays.asList("joe", "bob")); + miListArg = new SimpleMethodInvocation(new Object(), m, new Object[] {listArg}); + } + + @Test + public void hasRoleExpressionAllowsUserWithRole() throws Exception { + MethodExpressionVoter am = new MethodExpressionVoter(); + ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new PreInvocationExpressionBasedMethodConfigAttribute(null, null, "hasRole('blah')")); + + assertEquals(AccessDecisionVoter.ACCESS_GRANTED, am.vote(joe, miStringArgs, cad)); + } + + @Test + public void hasRoleExpressionDeniesUserWithoutRole() throws Exception { + MethodExpressionVoter am = new MethodExpressionVoter(); + ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new PreInvocationExpressionBasedMethodConfigAttribute(null, null, "hasRole('joedoesnt')")); + + assertEquals(AccessDecisionVoter.ACCESS_DENIED, am.vote(joe, miStringArgs, cad)); + } + + @Test + public void matchingArgAgainstAuthenticationNameIsSuccessful() throws Exception { + MethodExpressionVoter am = new MethodExpressionVoter(); + ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new PreInvocationExpressionBasedMethodConfigAttribute(null, null, "(#userName == name) and (name == 'joe')")); + + assertEquals(AccessDecisionVoter.ACCESS_GRANTED, am.vote(joe, miStringArgs, cad)); + } + + @Test + public void accessIsGrantedIfNoPreAuthorizeAttributeIsUsed() throws Exception { + MethodExpressionVoter am = new MethodExpressionVoter(); + ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new PreInvocationExpressionBasedMethodConfigAttribute("(name == 'jim')", "someList", null)); + + assertEquals(AccessDecisionVoter.ACCESS_GRANTED, am.vote(joe, miListArg, cad)); + // All objects should have been removed, because the expression is always false + assertEquals(0, listArg.size()); + } + +} diff --git a/core/src/test/java/org/springframework/security/intercept/method/AbstractMethodDefinitionSourceTests.java b/core/src/test/java/org/springframework/security/intercept/method/AbstractMethodDefinitionSourceTests.java deleted file mode 100644 index f4635d6b14..0000000000 --- a/core/src/test/java/org/springframework/security/intercept/method/AbstractMethodDefinitionSourceTests.java +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.intercept.method; - -import junit.framework.TestCase; - -import org.springframework.security.util.SimpleMethodInvocation; - -import org.aopalliance.intercept.MethodInvocation; - - -/** - * Tests {@link AbstractMethodDefinitionSource} and associated {@link ConfigAttributeDefinition}. - * - * @author Ben Alex - * @version $Id$ - */ -public class AbstractMethodDefinitionSourceTests extends TestCase { - //~ Constructors =================================================================================================== - - public AbstractMethodDefinitionSourceTests() { - super(); - } - - public AbstractMethodDefinitionSourceTests(String arg0) { - super(arg0); - } - - //~ Methods ======================================================================================================== - - public static void main(String[] args) { - junit.textui.TestRunner.run(AbstractMethodDefinitionSourceTests.class); - } - - public final void setUp() throws Exception { - super.setUp(); - } - - public void testDoesNotSupportAnotherObject() { - MockMethodDefinitionSource mds = new MockMethodDefinitionSource(false, true); - assertFalse(mds.supports(String.class)); - } - - public void testGetAttributesForANonMethodInvocation() { - MockMethodDefinitionSource mds = new MockMethodDefinitionSource(false, true); - - try { - mds.getAttributes(new String()); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - assertTrue(true); - } - } - - public void testGetAttributesForANullObject() { - MockMethodDefinitionSource mds = new MockMethodDefinitionSource(false, true); - - try { - mds.getAttributes(null); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - assertTrue(true); - } - } - - public void testGetAttributesForMethodInvocation() { - MockMethodDefinitionSource mds = new MockMethodDefinitionSource(false, true); - - try { - mds.getAttributes(new SimpleMethodInvocation()); - fail("Should have thrown UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - assertTrue(true); - } - } - - public void testSupportsMethodInvocation() { - MockMethodDefinitionSource mds = new MockMethodDefinitionSource(false, true); - assertTrue(mds.supports(MethodInvocation.class)); - } -} diff --git a/core/src/test/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSourceTests.java b/core/src/test/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSourceTests.java index 1465a0b14c..53d32acc1b 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSourceTests.java +++ b/core/src/test/java/org/springframework/security/intercept/method/MapBasedMethodDefinitionSourceTests.java @@ -3,10 +3,13 @@ package org.springframework.security.intercept.method; import static org.junit.Assert.*; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; import org.junit.Before; import org.junit.Test; -import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.SecurityConfig; /** * Tests for {@link MapBasedMethodDefinitionSource}. @@ -15,8 +18,8 @@ import org.springframework.security.ConfigAttributeDefinition; * @since 2.0.4 */ public class MapBasedMethodDefinitionSourceTests { - private final ConfigAttributeDefinition ROLE_A = new ConfigAttributeDefinition("ROLE_A"); - private final ConfigAttributeDefinition ROLE_B = new ConfigAttributeDefinition("ROLE_B"); + private final List ROLE_A = Arrays.asList(new SecurityConfig("ROLE_A")); + private final List ROLE_B = Arrays.asList(new SecurityConfig("ROLE_B")); private MapBasedMethodDefinitionSource mds; private Method someMethodString; private Method someMethodInteger; @@ -32,7 +35,7 @@ public class MapBasedMethodDefinitionSourceTests { public void wildcardedMatchIsOverwrittenByMoreSpecificMatch() { mds.addSecureMethod(MockService.class, "some*", ROLE_A); mds.addSecureMethod(MockService.class, "someMethod*", ROLE_B); - assertEquals(ROLE_B, mds.getAttributes(someMethodInteger, MockService.class)); + assertEquals(ROLE_B, mds.getAttributes(someMethodInteger, MockService.class).getConfigAttributes()); } @Test @@ -40,8 +43,8 @@ public class MapBasedMethodDefinitionSourceTests { mds.addSecureMethod(MockService.class, someMethodInteger, ROLE_A); mds.addSecureMethod(MockService.class, someMethodString, ROLE_B); - assertEquals(ROLE_A, mds.getAttributes(someMethodInteger, MockService.class)); - assertEquals(ROLE_B, mds.getAttributes(someMethodString, MockService.class)); + assertEquals(ROLE_A, mds.getAttributes(someMethodInteger, MockService.class).getConfigAttributes()); + assertEquals(ROLE_B, mds.getAttributes(someMethodString, MockService.class).getConfigAttributes()); } private class MockService { diff --git a/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java b/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java deleted file mode 100644 index e693c20ebd..0000000000 --- a/core/src/test/java/org/springframework/security/intercept/method/MethodDefinitionAttributesTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.intercept.method; - -import java.lang.reflect.Method; - -import junit.framework.Assert; - -import org.junit.Test; -import org.springframework.security.ConfigAttributeDefinition; -import org.springframework.security.ITargetObject; - - -/** - * Tests {@link MethodDefinitionAttributes}. - * - * @author Cameron Braid - * @author Ben Alex - * @version $Id$ - */ -public class MethodDefinitionAttributesTests { - - private MethodDefinitionAttributes build() { - MethodDefinitionAttributes mda = new MethodDefinitionAttributes(); - mda.setAttributes(new MockAttributes()); - return mda; - } - - @Test - public void testMethodsReturned() throws Exception { - Class clazz = ITargetObject.class; - Method method = clazz.getMethod("countLength", new Class[] {String.class}); - ConfigAttributeDefinition result = build().findAttributes(method, ITargetObject.class); - Assert.assertEquals(1, result.getConfigAttributes().size()); - } - - @Test - public void testClassesReturned() throws Exception { - Class clazz = ITargetObject.class; - ConfigAttributeDefinition result = build().findAttributes(ITargetObject.class); - Assert.assertEquals(1, result.getConfigAttributes().size()); - } - -} diff --git a/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java b/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java index 232dabc42a..f90c943d73 100644 --- a/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java +++ b/core/src/test/java/org/springframework/security/intercept/method/MockMethodDefinitionSource.java @@ -15,6 +15,8 @@ package org.springframework.security.intercept.method; +import org.aopalliance.intercept.MethodInvocation; +import org.aspectj.lang.JoinPoint; import org.springframework.security.ConfigAttributeDefinition; import java.lang.reflect.Method; @@ -29,7 +31,7 @@ import java.util.Collection; * @author Ben Alex * @version $Id$ */ -public class MockMethodDefinitionSource extends AbstractMethodDefinitionSource { +public class MockMethodDefinitionSource implements MethodDefinitionSource { //~ Instance fields ================================================================================================ private List list; @@ -65,14 +67,19 @@ public class MockMethodDefinitionSource extends AbstractMethodDefinitionSource { return list; } else { return null; - } + } } - protected ConfigAttributeDefinition lookupAttributes(Method method) { + public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException { throw new UnsupportedOperationException("mock method not implemented"); } - public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) { + public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) { throw new UnsupportedOperationException("mock method not implemented"); - } + } + + public boolean supports(Class clazz) { + return (MethodInvocation.class.isAssignableFrom(clazz) || JoinPoint.class.isAssignableFrom(clazz)); + } + } diff --git a/pom.xml b/pom.xml index 6caa3fda3b..121e910f44 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ acl taglibs itest + spring-el Spring Security @@ -76,11 +77,16 @@ - - spring-external - Spring Portfolio Release Repository - http://s3.amazonaws.com/maven.springframework.org/external - + + com.springsource.repository.bundles.release + SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases + http://repository.springsource.com/maven/bundles/release + + + com.springsource.repository.bundles.external + SpringSource Enterprise Bundle Repository - External Bundle Releases + http://repository.springsource.com/maven/bundles/external + @@ -634,14 +640,13 @@ org.aspectj - aspectjweaver - true - 1.5.4 + com.springsource.org.aspectj.runtime + 1.6.2.RELEASE org.aspectj - aspectjrt - 1.5.4 + com.springsource.org.aspectj.weaver + 1.6.2.RELEASE org.springframework diff --git a/samples/pom.xml b/samples/pom.xml index fae9d3d73d..66aafd7952 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -24,15 +24,6 @@ javax.servlet servlet-api - - org.aspectj - aspectjrt - - - org.aspectj - aspectjweaver - true - javax.servlet jstl