diff --git a/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java b/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java index 4eaa3865d8..33b973477e 100644 --- a/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java +++ b/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java @@ -105,9 +105,11 @@ public class AclPermissionEvaluator implements PermissionEvaluator { if (permission instanceof String) { String permString = (String)permission; - Permission p = BasePermission.buildFromName(permString); + Permission p = null; - if (p == null) { + try { + p = BasePermission.buildFromName(permString); + } catch(IllegalArgumentException notfound) { p = BasePermission.buildFromName(permString.toUpperCase()); } @@ -123,6 +125,10 @@ public class AclPermissionEvaluator implements PermissionEvaluator { this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; } + public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) { + this.objectIdentityGenerator = objectIdentityGenerator; + } + public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { this.sidRetrievalStrategy = sidRetrievalStrategy; } diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java index d53adc80a2..40ea0a99be 100644 --- a/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java +++ b/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java @@ -466,10 +466,6 @@ public class AclImplTests { @Test public void testIsSidLoaded() throws Exception { - AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl(new GrantedAuthority[] { - new GrantedAuthorityImpl("ROLE_GENERAL"), new GrantedAuthorityImpl("ROLE_GENERAL"), - new GrantedAuthorityImpl("ROLE_GENERAL") }); - AuditLogger auditLogger = new ConsoleAuditLogger(); Sid[] loadedSids = new Sid[] { new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_IGNORED") }; MutableAcl acl = new AclImpl(objectIdentity, new Long(1), mockAuthzStrategy, mockAuditLogger, null, loadedSids, true, new PrincipalSid( "johndoe")); 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 b9c88d3d95..249721182c 100644 --- a/core/src/main/java/org/springframework/security/config/BeanIds.java +++ b/core/src/main/java/org/springframework/security/config/BeanIds.java @@ -1,9 +1,9 @@ package org.springframework.security.config; /** - * Contains all the default Bean IDs created by the namespace support in Spring Security 2. + * Contains globally used default Bean IDs for beans created by the namespace support in Spring Security 2. *

- * These are mainly intended for internal use. + * These are intended for internal use. * * @author Ben Alex * @version $Id$ @@ -31,7 +31,7 @@ public abstract class BeanIds { 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 METHOD_ACCESS_MANAGER = "_defaultMethodAccessManager"; public static final String WEB_ACCESS_MANAGER = "_webAccessManager"; public static final String AUTHENTICATION_MANAGER = "_authenticationManager"; public static final String AFTER_INVOCATION_MANAGER = "_afterInvocationManager"; @@ -55,11 +55,11 @@ public abstract class BeanIds { 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 GLOBAL_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 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"; 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 2d4ba085ab..bfb75a8d89 100644 --- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java +++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java @@ -68,17 +68,6 @@ abstract class ConfigUtils { 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); - } - /** * Creates and registers the bean definition for the default ProviderManager instance and returns * the BeanDefinition for it. This method will typically be called when registering authentication providers diff --git a/core/src/main/java/org/springframework/security/config/Elements.java b/core/src/main/java/org/springframework/security/config/Elements.java index e7d1ba2834..9d1e8ea577 100644 --- a/core/src/main/java/org/springframework/security/config/Elements.java +++ b/core/src/main/java/org/springframework/security/config/Elements.java @@ -10,34 +10,35 @@ abstract class Elements { public static final String AUTHENTICATION_MANAGER = "authentication-manager"; public static final String USER_SERVICE = "user-service"; - public static final String JDBC_USER_SERVICE = "jdbc-user-service"; - public static final String FILTER_CHAIN_MAP = "filter-chain-map"; - public static final String INTERCEPT_METHODS = "intercept-methods"; - public static final String INTERCEPT_URL = "intercept-url"; - public static final String AUTHENTICATION_PROVIDER = "authentication-provider"; - public static final String HTTP = "http"; - public static final String LDAP_PROVIDER = "ldap-authentication-provider"; - public static final String LDAP_SERVER = "ldap-server"; + public static final String JDBC_USER_SERVICE = "jdbc-user-service"; + public static final String FILTER_CHAIN_MAP = "filter-chain-map"; + public static final String INTERCEPT_METHODS = "intercept-methods"; + public static final String INTERCEPT_URL = "intercept-url"; + public static final String AUTHENTICATION_PROVIDER = "authentication-provider"; + public static final String HTTP = "http"; + public static final String LDAP_PROVIDER = "ldap-authentication-provider"; + public static final String LDAP_SERVER = "ldap-server"; public static final String LDAP_USER_SERVICE = "ldap-user-service"; public static final String PROTECT_POINTCUT = "protect-pointcut"; + public static final String PERMISSON_EVALUATOR = "permission-evaluator"; public static final String PROTECT = "protect"; - public static final String CONCURRENT_SESSIONS = "concurrent-session-control"; - public static final String LOGOUT = "logout"; - public static final String FORM_LOGIN = "form-login"; - public static final String OPENID_LOGIN = "openid-login"; - public static final String BASIC_AUTH = "http-basic"; - public static final String REMEMBER_ME = "remember-me"; - public static final String ANONYMOUS = "anonymous"; - public static final String FILTER_CHAIN = "filter-chain"; - public static final String GLOBAL_METHOD_SECURITY = "global-method-security"; - public static final String PASSWORD_ENCODER = "password-encoder"; - public static final String SALT_SOURCE = "salt-source"; - public static final String PORT_MAPPINGS = "port-mappings"; + public static final String CONCURRENT_SESSIONS = "concurrent-session-control"; + public static final String LOGOUT = "logout"; + public static final String FORM_LOGIN = "form-login"; + public static final String OPENID_LOGIN = "openid-login"; + public static final String BASIC_AUTH = "http-basic"; + public static final String REMEMBER_ME = "remember-me"; + public static final String ANONYMOUS = "anonymous"; + public static final String FILTER_CHAIN = "filter-chain"; + public static final String GLOBAL_METHOD_SECURITY = "global-method-security"; + public static final String PASSWORD_ENCODER = "password-encoder"; + public static final String SALT_SOURCE = "salt-source"; + public static final String PORT_MAPPINGS = "port-mappings"; public static final String PORT_MAPPING = "port-mapping"; public static final String CUSTOM_FILTER = "custom-filter"; public static final String CUSTOM_AUTH_PROVIDER = "custom-authentication-provider"; - public static final String CUSTOM_AFTER_INVOCATION_PROVIDER = "custom-after-invocation-provider"; + public static final String CUSTOM_AFTER_INVOCATION_PROVIDER = "custom-after-invocation-provider"; public static final String X509 = "x509"; - public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filter-invocation-definition-source"; + public static final String FILTER_INVOCATION_DEFINITION_SOURCE = "filter-invocation-definition-source"; public static final String LDAP_PASSWORD_COMPARE = "password-compare"; } 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 10d0b65012..41c18c857c 100644 --- a/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParser.java @@ -6,6 +6,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.aop.config.AopNamespaceUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -17,12 +19,17 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.ConfigAttribute; import org.springframework.security.SecurityConfig; +import org.springframework.security.expression.DefaultSecurityExpressionHandler; import org.springframework.security.expression.support.MethodExpressionAfterInvocationProvider; +import org.springframework.security.expression.support.MethodExpressionVoter; import org.springframework.security.intercept.method.DelegatingMethodDefinitionSource; import org.springframework.security.intercept.method.MapBasedMethodDefinitionSource; import org.springframework.security.intercept.method.ProtectPointcutPostProcessor; import org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor; import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor; +import org.springframework.security.vote.AffirmativeBased; +import org.springframework.security.vote.AuthenticatedVoter; +import org.springframework.security.vote.RoleVoter; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -34,24 +41,51 @@ import org.w3c.dom.Element; * @version $Id$ */ 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 final Log logger = LogFactory.getLog(getClass()); + + static final String SECURED_DEPENDENCY_CLASS = "org.springframework.security.annotation.Secured"; + static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource"; + static final String EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.expression.support.ExpressionAnnotationMethodDefinitionSource"; + static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource"; + static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter"; + + /* + * Internal Bean IDs which are only used within this class + */ + static final String SECURITY_INTERCEPTOR_ID = "_globalMethodSecurityInterceptor"; + static final String INTERCEPTOR_POST_PROCESSOR_ID = "_globalMethodSecurityInterceptorPostProcessor"; + static final String ACCESS_MANAGER_ID = "_globalMethodSecurityAccessManager"; + static final String DELEGATING_METHOD_DEFINITION_SOURCE_ID = "_delegatingMethodDefinitionSource"; + static final String EXPRESSION_HANDLER_ID = "_expressionHandler"; + private static final String ATT_ACCESS = "access"; private static final String ATT_EXPRESSION = "expression"; 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"; + private static final String ATT_USE_EXPRESSIONS = "expression-annotations"; public BeanDefinition parse(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); // The list of method metadata delegates ManagedList delegates = new ManagedList(); - boolean jsr250Enabled = registerAnnotationBasedMethodDefinitionSources(element, parserContext, delegates); + boolean jsr250Enabled = "enabled".equals(element.getAttribute(ATT_USE_JSR250)); + boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED)); + boolean expressionsEnabled = "enabled".equals(element.getAttribute(ATT_USE_EXPRESSIONS)); + + if (expressionsEnabled) { + delegates.add(BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); + } + + if (useSecured) { + delegates.add(BeanDefinitionBuilder.rootBeanDefinition(SECURED_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); + } + + if (jsr250Enabled) { + delegates.add(BeanDefinitionBuilder.rootBeanDefinition(JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); + } MapBasedMethodDefinitionSource mapBasedMethodDefinitionSource = new MapBasedMethodDefinitionSource(); delegates.add(mapBasedMethodDefinitionSource); @@ -66,21 +100,11 @@ 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.registerDefaultMethodAccessManagerIfNecessary(parserContext); - - if (jsr250Enabled) { - ConfigUtils.addVoter(new RootBeanDefinition(JSR_250_VOTER_CLASS, null, null), parserContext); - } - - accessManagerId = BeanIds.METHOD_ACCESS_MANAGER; + registerAccessManager(element, parserContext, jsr250Enabled, expressionsEnabled); + accessManagerId = ACCESS_MANAGER_ID; } registerMethodSecurityInterceptor(parserContext, accessManagerId, source); @@ -93,38 +117,58 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { } /** - * Checks whether el-based, JSR-250 and/or Secured annotations are enabled and adds the appropriate - * MethodDefinitionSource delegates if required. + * Register the default AccessDecisionManager. Adds the special JSR 250 voter jsr-250 is enabled and an + * expression voter if expression-based access control is enabled. If expressions are in use, a after-invocation + * provider will also be registered to handle post-invocation filtering and authorization expression annotations. */ - 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)); + private void registerAccessManager(Element element, ParserContext pc, boolean jsr250Enabled, boolean expressionsEnabled) { + Element permissionEvaluatorElt = DomUtils.getChildElementByTagName(element, Elements.PERMISSON_EVALUATOR); + BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class); + ManagedList voters = new ManagedList(4); - if (useExpressions) { - delegates.add(BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); + if (expressionsEnabled) { + BeanDefinitionBuilder expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityExpressionHandler.class); + BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class); + BeanDefinitionBuilder afterInvocationProvider = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAfterInvocationProvider.class); + + if (permissionEvaluatorElt != null) { + String ref = permissionEvaluatorElt.getAttribute("ref"); + logger.info("Using bean '" + ref + "' as PermissionEvaluator implementation"); + expressionHandler.addPropertyReference("permissionEvaluator", ref); + } else { + logger.warn("Expressions were enabled but no PermissionEvaluator was configured. " + + "All hasPermision() expressions will evaluate to false."); + } + + pc.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, expressionHandler.getBeanDefinition()); + + expressionVoter.addPropertyReference("expressionHandler", EXPRESSION_HANDLER_ID); + afterInvocationProvider.addPropertyReference("expressionHandler", EXPRESSION_HANDLER_ID); + ConfigUtils.getRegisteredAfterInvocationProviders(pc).add(afterInvocationProvider.getBeanDefinition()); + voters.add(expressionVoter.getBeanDefinition()); } - if (useSecured) { - delegates.add(BeanDefinitionBuilder.rootBeanDefinition(SECURED_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); + voters.add(new RootBeanDefinition(RoleVoter.class)); + voters.add(new RootBeanDefinition(AuthenticatedVoter.class)); + + if (jsr250Enabled) { + voters.add(new RootBeanDefinition(JSR_250_VOTER_CLASS, null, null)); } - if (useJsr250) { - delegates.add(BeanDefinitionBuilder.rootBeanDefinition(JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition()); - } + accessMgrBuilder.addPropertyValue("decisionVoters", voters); - return useJsr250; + pc.getRegistry().registerBeanDefinition(ACCESS_MANAGER_ID, accessMgrBuilder.getBeanDefinition()); } private void registerDelegatingMethodDefinitionSource(ParserContext parserContext, ManagedList delegates, Object source) { - if (parserContext.getRegistry().containsBeanDefinition(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE)) { + if (parserContext.getRegistry().containsBeanDefinition(DELEGATING_METHOD_DEFINITION_SOURCE_ID)) { parserContext.getReaderContext().error("Duplicate detected.", source); } RootBeanDefinition delegatingMethodDefinitionSource = new RootBeanDefinition(DelegatingMethodDefinitionSource.class); delegatingMethodDefinitionSource.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); delegatingMethodDefinitionSource.setSource(source); delegatingMethodDefinitionSource.getPropertyValues().addPropertyValue("methodDefinitionSources", delegates); - parserContext.getRegistry().registerBeanDefinition(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE, delegatingMethodDefinitionSource); + parserContext.getRegistry().registerBeanDefinition(DELEGATING_METHOD_DEFINITION_SOURCE_ID, delegatingMethodDefinitionSource); } private void registerProtectPointcutPostProcessor(ParserContext parserContext, Map pointcutMap, @@ -173,11 +217,11 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { interceptor.getPropertyValues().addPropertyValue("accessDecisionManager", new RuntimeBeanReference(accessManagerId)); interceptor.getPropertyValues().addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER)); - interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE)); - parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR, interceptor); - parserContext.registerComponent(new BeanComponentDefinition(interceptor, BeanIds.METHOD_SECURITY_INTERCEPTOR)); + interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(DELEGATING_METHOD_DEFINITION_SOURCE_ID)); + parserContext.getRegistry().registerBeanDefinition(SECURITY_INTERCEPTOR_ID, interceptor); + parserContext.registerComponent(new BeanComponentDefinition(interceptor, SECURITY_INTERCEPTOR_ID)); - parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR_POST_PROCESSOR, + parserContext.getRegistry().registerBeanDefinition(INTERCEPTOR_POST_PROCESSOR_ID, new RootBeanDefinition(MethodSecurityInterceptorPostProcessor.class)); } @@ -185,8 +229,8 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser { RootBeanDefinition advisor = new RootBeanDefinition(MethodDefinitionSourceAdvisor.class); advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); advisor.setSource(source); - advisor.getConstructorArgumentValues().addGenericArgumentValue(BeanIds.METHOD_SECURITY_INTERCEPTOR); - advisor.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(BeanIds.DELEGATING_METHOD_DEFINITION_SOURCE)); + advisor.getConstructorArgumentValues().addGenericArgumentValue(SECURITY_INTERCEPTOR_ID); + advisor.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(DELEGATING_METHOD_DEFINITION_SOURCE_ID)); parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_DEFINITION_SOURCE_ADVISOR, advisor); } diff --git a/core/src/main/java/org/springframework/security/config/MethodSecurityInterceptorPostProcessor.java b/core/src/main/java/org/springframework/security/config/MethodSecurityInterceptorPostProcessor.java index 5324c2acd1..852c1dc9f2 100644 --- a/core/src/main/java/org/springframework/security/config/MethodSecurityInterceptorPostProcessor.java +++ b/core/src/main/java/org/springframework/security/config/MethodSecurityInterceptorPostProcessor.java @@ -10,12 +10,12 @@ import org.springframework.security.AfterInvocationManager; import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor; /** - * BeanPostProcessor which sets the AfterInvocationManager on the default MethodSecurityInterceptor, - * if one has been configured. - * + * BeanPostProcessor which sets the AfterInvocationManager on the global MethodSecurityInterceptor, + * if one has been configured. + * * @author Luke Taylor * @version $Id$ - * + * */ public class MethodSecurityInterceptorPostProcessor implements BeanPostProcessor, BeanFactoryAware{ private Log logger = LogFactory.getLog(getClass()); @@ -23,26 +23,26 @@ public class MethodSecurityInterceptorPostProcessor implements BeanPostProcessor private BeanFactory beanFactory; public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if(!BeanIds.METHOD_SECURITY_INTERCEPTOR.equals(beanName)) { + if(!GlobalMethodSecurityBeanDefinitionParser.SECURITY_INTERCEPTOR_ID.equals(beanName)) { return bean; } MethodSecurityInterceptor interceptor = (MethodSecurityInterceptor) bean; if (beanFactory.containsBean(BeanIds.AFTER_INVOCATION_MANAGER)) { - logger.debug("Setting AfterInvocationManaer on MethodSecurityInterceptor"); - interceptor.setAfterInvocationManager((AfterInvocationManager) - beanFactory.getBean(BeanIds.AFTER_INVOCATION_MANAGER)); + logger.debug("Setting AfterInvocationManaer on MethodSecurityInterceptor"); + interceptor.setAfterInvocationManager((AfterInvocationManager) + beanFactory.getBean(BeanIds.AFTER_INVOCATION_MANAGER)); } return bean; } - public Object postProcessAfterInitialization(Object bean, String beanName) { - return bean; - } + public Object postProcessAfterInitialization(Object bean, String beanName) { + return bean; + } - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } } diff --git a/core/src/main/java/org/springframework/security/expression/DefaultSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/expression/DefaultSecurityExpressionHandler.java index ab87d4afe6..8665d89691 100644 --- a/core/src/main/java/org/springframework/security/expression/DefaultSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/expression/DefaultSecurityExpressionHandler.java @@ -6,6 +6,8 @@ import java.util.HashSet; import java.util.Set; 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; @@ -14,7 +16,22 @@ import org.springframework.security.Authentication; import org.springframework.security.AuthenticationTrustResolver; import org.springframework.security.AuthenticationTrustResolverImpl; +/** + * The standard implementation of SecurityExpressionHandler which uses a {@link SecurityEvaluationContext} + * as the EvaluationContext implementation and configures it with a {@link SecurityExpressionRoot} instance + * as the expression root object. + *

+ * A single instance should usually be shared between the expression voter and after-invocation provider. + * + * + * @author Luke Taylor + * @version $Id$ + * @since 2.5 + */ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandler { + + protected final Log logger = LogFactory.getLog(getClass()); + private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator(); private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); @@ -32,11 +49,20 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl return ctx; } - public Object doFilter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) { + public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) { SecurityExpressionRoot rootObject = (SecurityExpressionRoot) ctx.getRootContextObject(); Set removeList = new HashSet(); + if (logger.isDebugEnabled()) { + logger.debug("Filtering with expression: " + filterExpression.getExpressionString()); + } + if (filterTarget instanceof Collection) { + Collection collection = (Collection)filterTarget; + + if (logger.isDebugEnabled()) { + logger.debug("Filtering collection with " + collection.size() + " elements"); + } for (Object filterObject : (Collection)filterTarget) { rootObject.setFilterObject(filterObject); @@ -45,6 +71,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl } } + if (logger.isDebugEnabled()) { + logger.debug("Removing elements: " + removeList); + } + for(Object toRemove : removeList) { ((Collection)filterTarget).remove(toRemove); } @@ -55,6 +85,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl if (filterTarget.getClass().isArray()) { Object[] array = (Object[])filterTarget; + if (logger.isDebugEnabled()) { + logger.debug("Filtering collection with " + array.length + " elements"); + } + for (int i = 0; i < array.length; i++) { rootObject.setFilterObject(array[i]); @@ -63,6 +97,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl } } + if (logger.isDebugEnabled()) { + logger.debug("Removing elements: " + removeList); + } + Object[] filtered = (Object[]) Array.newInstance(filterTarget.getClass().getComponentType(), array.length - removeList.size()); for (int i = 0, j = 0; i < array.length; i++) { diff --git a/core/src/main/java/org/springframework/security/expression/DenyAllPermissionEvaluator.java b/core/src/main/java/org/springframework/security/expression/DenyAllPermissionEvaluator.java index 0046d0f76e..d38802dabf 100644 --- a/core/src/main/java/org/springframework/security/expression/DenyAllPermissionEvaluator.java +++ b/core/src/main/java/org/springframework/security/expression/DenyAllPermissionEvaluator.java @@ -2,6 +2,8 @@ package org.springframework.security.expression; import java.io.Serializable; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.security.Authentication; /** @@ -12,12 +14,15 @@ import org.springframework.security.Authentication; * @version $Id$ * @since 2.5 */ -public class DenyAllPermissionEvaluator implements PermissionEvaluator { +public final class DenyAllPermissionEvaluator implements PermissionEvaluator { + + private final Log logger = LogFactory.getLog(getClass()); /** * @return false always */ - public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + public boolean hasPermission(Authentication authentication, Object target, Object permission) { + logger.warn("Denying user " + authentication.getName() + " permission '" + permission + "' on object " + target); return false; } @@ -26,6 +31,8 @@ public class DenyAllPermissionEvaluator implements PermissionEvaluator { */ public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { + logger.warn("Denying user " + authentication.getName() + " permission '" + permission + "' on object with Id '" + + targetId); return false; } diff --git a/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java index 2ec01dfc8a..2b795dd279 100644 --- a/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/expression/SecurityExpressionHandler.java @@ -30,10 +30,10 @@ public interface SecurityExpressionHandler { * {@link #createEvaluationContext(Authentication, MethodInvocation)} * @return the filtered collection or array */ - Object doFilter(Object filterTarget, Expression filterExpression, EvaluationContext ctx); + Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx); /** - * Used to inform the expression system of the return object + * Used to inform the expression system of the return object for the given evaluation context. * * @param returnObject the return object value * @param ctx the context within which the object should be set diff --git a/core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java index 09128c9819..25a8c9d81a 100644 --- a/core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java +++ b/core/src/main/java/org/springframework/security/expression/SecurityExpressionRoot.java @@ -33,7 +33,7 @@ public class SecurityExpressionRoot { public final String write = "write"; public final String create = "create"; public final String delete = "delete"; - public final String admin = "admin"; + public final String admin = "administration"; SecurityExpressionRoot(Authentication a) { 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 index 86456e850d..a3533fad03 100644 --- a/core/src/main/java/org/springframework/security/expression/support/MethodExpressionAfterInvocationProvider.java +++ b/core/src/main/java/org/springframework/security/expression/support/MethodExpressionAfterInvocationProvider.java @@ -51,7 +51,7 @@ public class MethodExpressionAfterInvocationProvider implements AfterInvocationP } if (returnedObject != null) { - returnedObject = expressionHandler.doFilter(returnedObject, postFilter, ctx); + returnedObject = expressionHandler.filter(returnedObject, postFilter, ctx); } else { if (logger.isDebugEnabled()) { logger.debug("Return object is null, filtering will be skipped"); @@ -90,6 +90,7 @@ public class MethodExpressionAfterInvocationProvider implements AfterInvocationP return clazz.isAssignableFrom(MethodInvocation.class); } - - + public void setExpressionHandler(SecurityExpressionHandler expressionHandler) { + this.expressionHandler = expressionHandler; + } } 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 index 4c8845082b..0ce4648d25 100644 --- a/core/src/main/java/org/springframework/security/expression/support/MethodExpressionVoter.java +++ b/core/src/main/java/org/springframework/security/expression/support/MethodExpressionVoter.java @@ -55,7 +55,7 @@ public class MethodExpressionVoter implements AccessDecisionVoter { if (preFilter != null) { Object filterTarget = findFilterTarget(mace.getFilterTarget(), ctx, mi); - expressionHandler.doFilter(filterTarget, preFilter, ctx); + expressionHandler.filter(filterTarget, preFilter, ctx); } if (preAuthorize == null) { @@ -104,4 +104,8 @@ public class MethodExpressionVoter implements AccessDecisionVoter { return null; } + + public void setExpressionHandler(SecurityExpressionHandler expressionHandler) { + this.expressionHandler = expressionHandler; + } } diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc index a72bc76d5e..468249df35 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.rnc @@ -181,10 +181,10 @@ protect.attlist &= global-method-security = ## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250. - element global-method-security {global-method-security.attlist, protect-pointcut*} + element global-method-security {global-method-security.attlist, permission-evaluator?, protect-pointcut*} global-method-security.attlist &= ## Specifies whether the use of Spring Security's expression-based annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "disabled". - attribute spel-annotations {"disabled" | "enabled" }? + attribute expression-annotations {"disabled" | "enabled" }? global-method-security.attlist &= ## Specifies whether the use of Spring Security's @Secured annotations should be enabled for this application context. Defaults to "disabled". attribute secured-annotations {"disabled" | "enabled" }? @@ -195,6 +195,10 @@ global-method-security.attlist &= ## Optional AccessDecisionManager bean ID to override the default used for method security. attribute access-decision-manager-ref {xsd:string}? +permission-evaluator = + ## Defines the PermissionEvaluator implementation which will be used to evaluate calls to hasPermission() expressions + element permission-evaluator {ref} + custom-after-invocation-provider = ## Used to decorate an AfterInvocationProvider to specify that it should be used with method security. element custom-after-invocation-provider {empty} diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd index 324fe84a0a..e9810ff814 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.5.xsd @@ -565,6 +565,7 @@ + Defines a protected pointcut and the access control configuration @@ -581,7 +582,7 @@ - + Specifies whether the use of Spring Security's expression-based annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for @@ -626,6 +627,15 @@ + + + Defines the PermissionEvaluator implementation which will be used to + evaluate calls to hasPermission() expressions + + + + + Used to decorate an AfterInvocationProvider to specify that it should be 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 33222274a4..5a2ee94736 100644 --- a/core/src/test/java/org/springframework/security/config/CustomAfterInvocationProviderBeanDefinitionDecoratorTests.java +++ b/core/src/test/java/org/springframework/security/config/CustomAfterInvocationProviderBeanDefinitionDecoratorTests.java @@ -30,11 +30,11 @@ public class CustomAfterInvocationProviderBeanDefinitionDecoratorTests { ConfigTestUtils.AUTH_PROVIDER_XML ); - MethodSecurityInterceptor msi = (MethodSecurityInterceptor) appContext.getBean(BeanIds.METHOD_SECURITY_INTERCEPTOR); + MethodSecurityInterceptor msi = (MethodSecurityInterceptor) appContext.getBean(GlobalMethodSecurityBeanDefinitionParser.SECURITY_INTERCEPTOR_ID); AfterInvocationProviderManager apm = (AfterInvocationProviderManager) msi.getAfterInvocationManager(); assertNotNull(apm); - assertEquals(2, apm.getProviders().size()); - assertTrue(apm.getProviders().get(1) instanceof MockAfterInvocationProvider); + assertEquals(1, apm.getProviders().size()); + assertTrue(apm.getProviders().get(0) 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 41a4f4da65..8428e303cd 100644 --- a/core/src/test/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/GlobalMethodSecurityBeanDefinitionParserTests.java @@ -11,11 +11,13 @@ import org.junit.Test; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.security.AccessDeniedException; +import org.springframework.security.Authentication; import org.springframework.security.AuthenticationCredentialsNotFoundException; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.annotation.BusinessService; import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.TestingAuthenticationToken; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.util.InMemoryXmlApplicationContext; @@ -69,8 +71,9 @@ public class GlobalMethodSecurityBeanDefinitionParserTests { @Test(expected=AccessDeniedException.class) public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { loadContext(); - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", - new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_SOMEOTHERROLE")}); + TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password", "ROLE_SOMEOTHERROLE"); + token.setAuthenticated(true); + SecurityContextHolder.getContext().setAuthentication(token); target.someAdminMethod(); @@ -186,7 +189,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests { @Test(expected=AccessDeniedException.class) public void accessIsDeniedForHasRoleExpression() { setContext( - "" + + "" + "" + AUTH_PROVIDER_XML); SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword")); @@ -197,7 +200,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests { @Test public void preAndPostFilterAnnotationsWorkWithLists() { setContext( - "" + + "" + "" + AUTH_PROVIDER_XML); SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword")); @@ -216,7 +219,7 @@ public class GlobalMethodSecurityBeanDefinitionParserTests { @Test public void prePostFilterAnnotationWorksWithArrays() { setContext( - "" + + "" + "" + AUTH_PROVIDER_XML); SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("bob","bobspassword")); diff --git a/samples/contacts/src/main/java/sample/contact/AddPermission.java b/samples/contacts/src/main/java/sample/contact/AddPermission.java index 8a1cca06e0..ba4a2e60d2 100644 --- a/samples/contacts/src/main/java/sample/contact/AddPermission.java +++ b/samples/contacts/src/main/java/sample/contact/AddPermission.java @@ -14,7 +14,7 @@ */ package sample.contact; -import org.springframework.security.acl.basic.SimpleAclEntry; +import org.springframework.security.acls.domain.BasePermission; /** @@ -27,7 +27,7 @@ public class AddPermission { //~ Instance fields ================================================================================================ public Contact contact; - public Integer permission = new Integer(SimpleAclEntry.READ); + public Integer permission = BasePermission.READ.getMask(); public String recipient; //~ Methods ======================================================================================================== diff --git a/samples/contacts/src/main/java/sample/contact/AddPermissionController.java b/samples/contacts/src/main/java/sample/contact/AddPermissionController.java index b61599876b..25793ec3bb 100644 --- a/samples/contacts/src/main/java/sample/contact/AddPermissionController.java +++ b/samples/contacts/src/main/java/sample/contact/AddPermissionController.java @@ -26,7 +26,7 @@ import org.springframework.util.Assert; import org.springframework.validation.BindException; -import org.springframework.web.bind.RequestUtils; +import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.SimpleFormController; import org.springframework.web.servlet.view.RedirectView; @@ -67,7 +67,7 @@ public class AddPermissionController extends SimpleFormController implements Ini protected Object formBackingObject(HttpServletRequest request) throws Exception { - int contactId = RequestUtils.getRequiredIntParameter(request, "contactId"); + int contactId = ServletRequestUtils.getRequiredIntParameter(request, "contactId"); Contact contact = contactManager.getById(new Long(contactId)); diff --git a/samples/contacts/src/main/java/sample/contact/AdminPermissionController.java b/samples/contacts/src/main/java/sample/contact/AdminPermissionController.java index ede2cc63dc..104e80589a 100644 --- a/samples/contacts/src/main/java/sample/contact/AdminPermissionController.java +++ b/samples/contacts/src/main/java/sample/contact/AdminPermissionController.java @@ -22,7 +22,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; -import org.springframework.web.bind.RequestUtils; +import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; @@ -57,7 +57,7 @@ public class AdminPermissionController implements Controller, InitializingBean { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - int id = RequestUtils.getRequiredIntParameter(request, "contactId"); + int id = ServletRequestUtils.getRequiredIntParameter(request, "contactId"); Contact contact = contactManager.getById(new Long(id)); Acl acl = aclService.readAclById(new ObjectIdentityImpl(contact)); diff --git a/samples/contacts/src/main/java/sample/contact/ClientApplication.java b/samples/contacts/src/main/java/sample/contact/ClientApplication.java index 0ef3688db7..cad61af083 100644 --- a/samples/contacts/src/main/java/sample/contact/ClientApplication.java +++ b/samples/contacts/src/main/java/sample/contact/ClientApplication.java @@ -36,8 +36,9 @@ import java.util.Map; /** - * Demonstrates accessing the {@link ContactManager} via remoting protocols.

Based on Spring's JPetStore sample, - * written by Juergen Hoeller.

+ * Demonstrates accessing the {@link ContactManager} via remoting protocols. + *

+ * Based on Spring's JPetStore sample, written by Juergen Hoeller. * * @author Ben Alex */ diff --git a/samples/contacts/src/main/java/sample/contact/Contact.java b/samples/contacts/src/main/java/sample/contact/Contact.java index 74c5fb4d50..2e028bff39 100644 --- a/samples/contacts/src/main/java/sample/contact/Contact.java +++ b/samples/contacts/src/main/java/sample/contact/Contact.java @@ -43,8 +43,6 @@ public class Contact implements Serializable { //~ Methods ======================================================================================================== /** - * DOCUMENT ME! - * * @return Returns the email. */ public String getEmail() { @@ -52,8 +50,6 @@ public class Contact implements Serializable { } /** - * DOCUMENT ME! - * * @return Returns the id. */ public Long getId() { @@ -61,8 +57,6 @@ public class Contact implements Serializable { } /** - * DOCUMENT ME! - * * @return Returns the name. */ public String getName() { @@ -70,8 +64,6 @@ public class Contact implements Serializable { } /** - * DOCUMENT ME! - * * @param email The email to set. */ public void setEmail(String email) { @@ -83,8 +75,6 @@ public class Contact implements Serializable { } /** - * DOCUMENT ME! - * * @param name The name to set. */ public void setName(String name) { diff --git a/samples/contacts/src/main/java/sample/contact/ContactManager.java b/samples/contacts/src/main/java/sample/contact/ContactManager.java index b5cc30d8f3..97cdb37af8 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactManager.java +++ b/samples/contacts/src/main/java/sample/contact/ContactManager.java @@ -16,6 +16,8 @@ package sample.contact; import org.springframework.security.acls.Permission; import org.springframework.security.acls.sid.Sid; +import org.springframework.security.expression.annotation.PostFilter; +import org.springframework.security.expression.annotation.PreAuthorize; import java.util.List; @@ -28,19 +30,28 @@ import java.util.List; */ public interface ContactManager { //~ Methods ======================================================================================================== - + @PreAuthorize("hasPermission(#contact, admin)") public void addPermission(Contact contact, Sid recipient, Permission permission); - public void create(Contact contact); - - public void delete(Contact contact); - + @PreAuthorize("hasPermission(#contact, admin)") public void deletePermission(Contact contact, Sid recipient, Permission permission); + @PreAuthorize("hasRole('ROLE_USER')") + public void create(Contact contact); + + @PreAuthorize("hasPermission(#contact, 'delete') or hasPermission(#contact, admin)") + public void delete(Contact contact); + + @PreAuthorize("hasRole('ROLE_USER')") + @PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)") public List getAll(); + @PreAuthorize("hasRole('ROLE_USER')") public List getAllRecipients(); + @PreAuthorize( + "hasPermission(#id, 'sample.contact.Contact', read) or " + + "hasPermission(#id, 'sample.contact.Contact', admin)") public Contact getById(Long id); public Contact getRandomContact(); diff --git a/samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java b/samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java index df81964cc4..28f160887a 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java +++ b/samples/contacts/src/main/java/sample/contact/ContactManagerBackend.java @@ -30,6 +30,7 @@ import org.springframework.security.acls.sid.Sid; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.userdetails.UserDetails; +import org.springframework.transaction.annotation.Transactional; import org.springframework.beans.factory.InitializingBean; @@ -47,6 +48,7 @@ import java.util.Random; * @author Ben Alex * @version $Id$ */ +@Transactional public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean { //~ Instance fields ================================================================================================ @@ -124,6 +126,7 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C } } + @Transactional(readOnly=true) public List getAll() { if (logger.isDebugEnabled()) { logger.debug("Returning all contacts"); @@ -132,6 +135,7 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C return contactDao.findAll(); } + @Transactional(readOnly=true) public List getAllRecipients() { if (logger.isDebugEnabled()) { logger.debug("Returning all recipients"); @@ -142,6 +146,7 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C return list; } + @Transactional(readOnly=true) public Contact getById(Long id) { if (logger.isDebugEnabled()) { logger.debug("Returning contact with id: " + id); @@ -152,9 +157,8 @@ public class ContactManagerBackend extends ApplicationObjectSupport implements C /** * This is a public method. - * - * @return DOCUMENT ME! */ + @Transactional(readOnly=true) public Contact getRandomContact() { if (logger.isDebugEnabled()) { logger.debug("Returning random contact"); diff --git a/samples/contacts/src/main/java/sample/contact/DeletePermissionController.java b/samples/contacts/src/main/java/sample/contact/DeletePermissionController.java index 654eccb90d..367c4fa4ae 100644 --- a/samples/contacts/src/main/java/sample/contact/DeletePermissionController.java +++ b/samples/contacts/src/main/java/sample/contact/DeletePermissionController.java @@ -24,7 +24,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; -import org.springframework.web.bind.RequestUtils; +import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; @@ -60,9 +60,9 @@ public class DeletePermissionController implements Controller, InitializingBean public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // ">Del - int contactId = RequestUtils.getRequiredIntParameter(request, "contactId"); - String sid = RequestUtils.getRequiredStringParameter(request, "sid"); - int mask = RequestUtils.getRequiredIntParameter(request, "permission"); + int contactId = ServletRequestUtils.getRequiredIntParameter(request, "contactId"); + String sid = ServletRequestUtils.getRequiredStringParameter(request, "sid"); + int mask = ServletRequestUtils.getRequiredIntParameter(request, "permission"); Contact contact = contactManager.getById(new Long(contactId)); diff --git a/samples/contacts/src/main/java/sample/contact/PublicIndexController.java b/samples/contacts/src/main/java/sample/contact/PublicIndexController.java index 2b89b4e35c..f1ad324af6 100644 --- a/samples/contacts/src/main/java/sample/contact/PublicIndexController.java +++ b/samples/contacts/src/main/java/sample/contact/PublicIndexController.java @@ -51,7 +51,7 @@ public class PublicIndexController implements Controller, InitializingBean { } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + throws ServletException, IOException { Contact rnd = contactManager.getRandomContact(); return new ModelAndView("hello", "contact", rnd); diff --git a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java b/samples/contacts/src/main/java/sample/contact/SecureIndexController.java index dad05ba918..5de6c2cc56 100644 --- a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java +++ b/samples/contacts/src/main/java/sample/contact/SecureIndexController.java @@ -17,6 +17,11 @@ package sample.contact; import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.Authentication; +import org.springframework.security.acls.Permission; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.expression.PermissionEvaluator; import org.springframework.util.Assert; import org.springframework.web.servlet.ModelAndView; @@ -35,38 +40,55 @@ import javax.servlet.http.HttpServletResponse; /** * Controller for secure index page. + *

+ * This controller displays a list of all the contacts for which the current user has read or admin permissions. + * It makes a call to {@link ContactManager#getAll()} which automatically filters the returned list using Spring + * Security's ACL mechanism (see the expression annotations on this interface for the details). + *

+ * In addition to rendering the list of contacts, the view will also include a "Del" or "Admin" link beside the + * contact, depending on whether the user has the corresponding permissions (admin permission is assumed to imply + * delete here). This information is stored in the model using the injected {@link PermissionEvaluator} instance. + * The implementation should be an instance of {@link AclPermissionEvaluator} or one which is compatible with Spring + * Security's ACL module. * * @author Ben Alex * @version $Id$ */ public class SecureIndexController implements Controller, InitializingBean { + private final static Permission[] HAS_DELETE = new Permission[] {BasePermission.DELETE, BasePermission.ADMINISTRATION}; + private final static Permission[] HAS_ADMIN = new Permission[] {BasePermission.ADMINISTRATION}; + //~ Instance fields ================================================================================================ private ContactManager contactManager; + private PermissionEvaluator permissionEvaluator; //~ Methods ======================================================================================================== public void afterPropertiesSet() throws Exception { Assert.notNull(contactManager, "A ContactManager implementation is required"); - } - - public ContactManager getContactManager() { - return contactManager; + Assert.notNull(permissionEvaluator, "A PermissionEvaluator implementation is required"); } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - List myContactsList = contactManager.getAll(); - Contact[] myContacts; + throws ServletException, IOException { + List myContactsList = contactManager.getAll(); + Map hasDelete = new HashMap(myContactsList.size()); + Map hasAdmin = new HashMap(myContactsList.size()); - if (myContactsList.size() == 0) { - myContacts = null; - } else { - myContacts = (Contact[]) myContactsList.toArray(new Contact[] {}); + Authentication user = SecurityContextHolder.getContext().getAuthentication(); + + for (Contact contact : myContactsList) { + hasDelete.put(contact, + permissionEvaluator.hasPermission(user, contact, HAS_DELETE) ? Boolean.TRUE : Boolean.FALSE); + hasAdmin.put(contact, + permissionEvaluator.hasPermission(user, contact, HAS_ADMIN) ? Boolean.TRUE : Boolean.FALSE); } Map model = new HashMap(); - model.put("contacts", myContacts); + model.put("contacts", myContactsList); + model.put("hasDeletePermission", hasDelete); + model.put("hasAdminPermission", hasAdmin); return new ModelAndView("index", "model", model); } @@ -74,4 +96,8 @@ public class SecureIndexController implements Controller, InitializingBean { public void setContactManager(ContactManager contact) { this.contactManager = contact; } + + public void setPermissionEvaluator(PermissionEvaluator pe) { + this.permissionEvaluator = pe; + } } diff --git a/samples/contacts/src/main/resources/applicationContext-common-authorization.xml b/samples/contacts/src/main/resources/applicationContext-common-authorization.xml index aa361bbc46..68d5e89f0e 100644 --- a/samples/contacts/src/main/resources/applicationContext-common-authorization.xml +++ b/samples/contacts/src/main/resources/applicationContext-common-authorization.xml @@ -14,77 +14,7 @@ - $Id$ --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -128,38 +58,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/contacts/src/main/resources/applicationContext-common-business.xml b/samples/contacts/src/main/resources/applicationContext-common-business.xml index e26e9feda2..f5d36e5b5c 100644 --- a/samples/contacts/src/main/resources/applicationContext-common-business.xml +++ b/samples/contacts/src/main/resources/applicationContext-common-business.xml @@ -9,10 +9,10 @@ --> + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> @@ -26,38 +26,31 @@ + + - - - - - - - - - - - + + diff --git a/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml b/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml index 59709f2b29..5157ae0984 100644 --- a/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml +++ b/samples/contacts/src/main/webapp/WEB-INF/applicationContext-security.xml @@ -11,15 +11,18 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.5.xsd"> + + + - + @@ -28,20 +31,24 @@ - + - + - - + + - - - - + + + - + + + + + diff --git a/samples/contacts/src/main/webapp/WEB-INF/contacts-servlet.xml b/samples/contacts/src/main/webapp/WEB-INF/contacts-servlet.xml index 3fe00d250c..73a2338cd9 100644 --- a/samples/contacts/src/main/webapp/WEB-INF/contacts-servlet.xml +++ b/samples/contacts/src/main/webapp/WEB-INF/contacts-servlet.xml @@ -9,7 +9,7 @@ - + @@ -21,6 +21,7 @@ + @@ -75,9 +76,9 @@ - - - - + + + + diff --git a/samples/contacts/src/main/webapp/WEB-INF/jsp/index.jsp b/samples/contacts/src/main/webapp/WEB-INF/jsp/index.jsp index 2b92f1073f..1945221044 100644 --- a/samples/contacts/src/main/webapp/WEB-INF/jsp/index.jsp +++ b/samples/contacts/src/main/webapp/WEB-INF/jsp/index.jsp @@ -7,7 +7,7 @@

- + - - - - - - + + + + + +
idNameEmail
@@ -18,12 +18,12 @@ ">Del">Admin Permission">Del">Admin Permission
diff --git a/samples/contacts/src/test/java/sample/contact/GetAllContactsTests.java b/samples/contacts/src/test/java/sample/contact/GetAllContactsTests.java index 926f98d547..36dfe3956c 100644 --- a/samples/contacts/src/test/java/sample/contact/GetAllContactsTests.java +++ b/samples/contacts/src/test/java/sample/contact/GetAllContactsTests.java @@ -14,22 +14,24 @@ */ package sample.contact; -import org.springframework.security.Authentication; - -import org.springframework.security.acls.domain.BasePermission; -import org.springframework.security.acls.sid.PrincipalSid; - -import org.springframework.security.context.SecurityContextHolder; - -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; - -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; - -import org.springframework.test.AbstractTransactionalSpringContextTests; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.util.Iterator; import java.util.List; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.Authentication; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.sid.PrincipalSid; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + /** * Tests {@link ContactManager}. @@ -37,9 +39,15 @@ import java.util.List; * @author David Leal * @author Ben Alex */ -public class GetAllContactsTests extends AbstractTransactionalSpringContextTests { +@ContextConfiguration(locations={ + "/applicationContext-common-authorization.xml", + "/applicationContext-common-business.xml", + "/applicationContext-contacts-test.xml"}) +@RunWith(SpringJUnit4ClassRunner.class) +public class GetAllContactsTests { //~ Instance fields ================================================================================================ + @Autowired protected ContactManager contactManager; //~ Methods ======================================================================================================== @@ -59,7 +67,7 @@ public class GetAllContactsTests extends AbstractTransactionalSpringContextTests fail("List of contacts should have contained: " + id); } - protected void assertNotContainsContact(String id, List contacts) { + void assertDoestNotContainContact(String id, List contacts) { Iterator iter = contacts.iterator(); while (iter.hasNext()) { @@ -71,15 +79,6 @@ public class GetAllContactsTests extends AbstractTransactionalSpringContextTests } } - protected String[] getConfigLocations() { - setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); - - return new String[] { - "applicationContext-common-authorization.xml", "applicationContext-common-business.xml", - "applicationContext-contacts-test.xml" - }; - } - /** * Locates the first Contact of the exact name specified.

Uses the {@link * ContactManager#getAll()} method.

@@ -120,14 +119,12 @@ public class GetAllContactsTests extends AbstractTransactionalSpringContextTests SecurityContextHolder.getContext().setAuthentication(authRequest); } - protected void onTearDownInTransaction() { + @After + public void onTearDownInTransaction() { SecurityContextHolder.clearContext(); } - public void setContactManager(ContactManager contactManager) { - this.contactManager = contactManager; - } - + @Test public void testDianne() { makeActiveUser("dianne"); // has ROLE_USER @@ -139,11 +136,12 @@ public class GetAllContactsTests extends AbstractTransactionalSpringContextTests assertContainsContact(Long.toString(6), contacts); assertContainsContact(Long.toString(8), contacts); - assertNotContainsContact(Long.toString(1), contacts); - assertNotContainsContact(Long.toString(2), contacts); - assertNotContainsContact(Long.toString(3), contacts); + assertDoestNotContainContact(Long.toString(1), contacts); + assertDoestNotContainContact(Long.toString(2), contacts); + assertDoestNotContainContact(Long.toString(3), contacts); } + @Test public void testrod() { makeActiveUser("rod"); // has ROLE_SUPERVISOR @@ -156,13 +154,14 @@ public class GetAllContactsTests extends AbstractTransactionalSpringContextTests assertContainsContact(Long.toString(3), contacts); assertContainsContact(Long.toString(4), contacts); - assertNotContainsContact(Long.toString(5), contacts); + assertDoestNotContainContact(Long.toString(5), contacts); Contact c1 = contactManager.getById(new Long(4)); contactManager.deletePermission(c1, new PrincipalSid("bob"), BasePermission.ADMINISTRATION); } + @Test public void testScott() { makeActiveUser("scott"); // has ROLE_USER @@ -176,6 +175,6 @@ public class GetAllContactsTests extends AbstractTransactionalSpringContextTests assertContainsContact(Long.toString(8), contacts); assertContainsContact(Long.toString(9), contacts); - assertNotContainsContact(Long.toString(1), contacts); + assertDoestNotContainContact(Long.toString(1), contacts); } } diff --git a/samples/contacts/src/test/resources/applicationContext-contacts-test.xml b/samples/contacts/src/test/resources/applicationContext-contacts-test.xml index 37e8e7f5a8..39b71cdc66 100644 --- a/samples/contacts/src/test/resources/applicationContext-contacts-test.xml +++ b/samples/contacts/src/test/resources/applicationContext-contacts-test.xml @@ -10,9 +10,12 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.5.xsd"> + + + @@ -21,8 +24,8 @@ - - - + + +