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
+ * 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 @@
Based on Spring's JPetStore sample,
- * written by Juergen Hoeller.
+ * 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 {
//
+ * 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
Uses the {@link
* ContactManager#getAll()} method.
-
-
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
-id Name Email
-
-
-
+
+ Contact of the exact name specified.