From 5f6aa9c49e29c663ff99d74de86eea9c97dbb8b6 Mon Sep 17 00:00:00 2001
From: Ben Alex
+ * Typically used to ensure the principal is permitted to access the domain
+ * object instance returned by a service layer bean. Can also be used to
+ * mutate the domain object instance so the principal is only able to access
+ * authorised bean properties or
+ * Special consideration should be given to using an
+ *
+ * This allows the Object returned from a secure object invocation,
+ * being able to modify the Object or throw an {@link
+ * AccessDeniedException}.
+ *
+ * Collection elements. Often used
+ * in conjunction with an {@link net.sf.acegisecurity.acl.AclManager} to
+ * obtain the access control list applicable for the domain object instance.
+ * AfterInvocationManager on bean methods that modify a database.
+ * Typically an AfterInvocationManager is used with read-only
+ * methods, such as public DomainObject getById(id). If used with
+ * methods that modify a database, a transaction manager should be used to
+ * ensure any AccessDeniedException will cause a rollback of the
+ * changes made by the transaction.
+ * Object, make an access control decision or optionally
+ * modify the returned Object.
+ *
+ * @param authentication the caller that invoked the method
+ * @param object the secured object that was called
+ * @param config the configuration attributes associated with the secured
+ * object that was invoked
+ * @param returnedObject the Object that was returned from the
+ * secure object invocation
+ *
+ * @return the Object that will ultimately be returned to the
+ * caller (if an implementation does not wish to modify the object
+ * to be returned to the caller, the implementation should simply
+ * return the same object it was passed by the
+ * returnedObject method argument)
+ *
+ * @throws AccessDeniedException if access is denied
+ */
+ public Object decide(Authentication authentication, Object object,
+ ConfigAttributeDefinition config, Object returnedObject)
+ throws AccessDeniedException;
+
+ /**
+ * Indicates whether this AfterInvocationManager is able to
+ * process "after invocation" requests presented with the passed
+ * ConfigAttribute.
+ *
+ * AbstractSecurityInterceptor to check every
+ * configuration attribute can be consumed by the configured
+ * AccessDecisionManager and/or RunAsManager
+ * and/or AfterInvocationManager.
+ * AbstractSecurityInterceptor
+ *
+ * @return true if this AfterInvocationManager can support the
+ * passed configuration attribute
+ */
+ public boolean supports(ConfigAttribute attribute);
+
+ /**
+ * Indicates whether the AfterInvocationManager implementation
+ * is able to provide access control decisions for the indicated secured
+ * object type.
+ *
+ * @param clazz the class that is being queried
+ *
+ * @return true if the implementation can process the
+ * indicated class
+ */
+ public boolean supports(Class clazz);
+}
diff --git a/core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java b/core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java
index 5711ade986..384414b296 100644
--- a/core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java
+++ b/core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java
@@ -17,6 +17,7 @@ package net.sf.acegisecurity.intercept;
import net.sf.acegisecurity.AccessDecisionManager;
import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.AfterInvocationManager;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
import net.sf.acegisecurity.AuthenticationException;
@@ -74,7 +75,7 @@ import java.util.Set;
* For an invocation that is secured (there is a
* ConfigAttributeDefinition for the secure object invocation):
*
- *
+ *
*
*
*
@@ -110,7 +116,7 @@ import java.util.Set;
* For an invocation that is public (there is no
* Authentication object on
@@ -103,6 +104,11 @@ import java.util.Set;
* object, return the ContextHolder to the object that existed
* after the call to AuthenticationManager.
* AfterInvocationManager is defined, invoke the invocation
+ * manager and allow it to replace the object due to be returned to the
+ * caller.
+ * ConfigAttributeDefinition for the secure object invocation):
*
- *
+ *
*
* ContextHolder contains a SecureContext, set
* the isAuthenticated flag on the Authentication
@@ -128,9 +134,9 @@ import java.util.Set;
*
* Object that should be returned to the caller. The subclass
+ * will then return that result or exception to the original caller.
*
AbstractSecurityInterceptor all
+ * support the indicated secure object class.
+ *
+ * @return the type of secure object the subclass provides services for
+ */
+ public abstract Class getSecureObjectClass();
+
public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
public void setAccessDecisionManager(
@@ -223,51 +249,116 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
logger.warn(
"Could not validate configuration attributes as the MethodDefinitionSource did not return a ConfigAttributeDefinition Iterator");
}
+ } else {
+ Set set = new HashSet();
+
+ while (iter.hasNext()) {
+ ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
+ .next();
+ Iterator attributes = def.getConfigAttributes();
+
+ while (attributes.hasNext()) {
+ ConfigAttribute attr = (ConfigAttribute) attributes
+ .next();
+
+ if (!this.runAsManager.supports(attr)
+ && !this.accessDecisionManager.supports(attr)
+ && ((this.afterInvocationManager == null)
+ || !this.afterInvocationManager.supports(attr))) {
+ set.add(attr);
+ }
+ }
+ }
- return;
+ if (set.size() == 0) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Validated configuration attributes");
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported configuration attributes: "
+ + set.toString());
+ }
}
+ }
- Set set = new HashSet();
+ if (getSecureObjectClass() == null) {
+ throw new IllegalArgumentException(
+ "Subclass must provide a non-null response to getSecureObjectClass()");
+ }
- while (iter.hasNext()) {
- ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
- .next();
- Iterator attributes = def.getConfigAttributes();
+ if (!this.accessDecisionManager.supports(getSecureObjectClass())) {
+ throw new IllegalArgumentException(
+ "AccessDecisionManager does not support secure object class: "
+ + getSecureObjectClass());
+ }
- while (attributes.hasNext()) {
- ConfigAttribute attr = (ConfigAttribute) attributes.next();
+ boolean result = this.obtainObjectDefinitionSource().supports(getSecureObjectClass());
- if (!this.runAsManager.supports(attr)
- && !this.accessDecisionManager.supports(attr)) {
- set.add(attr);
- }
- }
- }
+ if (!result) {
+ throw new IllegalArgumentException(
+ "ObjectDefinitionSource does not support secure object class: "
+ + getSecureObjectClass());
+ }
- if (set.size() == 0) {
- if (logger.isInfoEnabled()) {
- logger.info("Validated configuration attributes");
- }
- } else {
- throw new IllegalArgumentException(
- "Unsupported configuration attributes: " + set.toString());
- }
+ if (!this.runAsManager.supports(getSecureObjectClass())) {
+ throw new IllegalArgumentException(
+ "RunAsManager does not support secure object class: "
+ + getSecureObjectClass());
+ }
+
+ if ((this.afterInvocationManager != null)
+ && !this.afterInvocationManager.supports(getSecureObjectClass())) {
+ throw new IllegalArgumentException(
+ "AfterInvocationManager does not support secure object class: "
+ + getSecureObjectClass());
+ }
+
+ if (!this.obtainObjectDefinitionSource().supports(getSecureObjectClass())) {
+ throw new IllegalArgumentException(
+ "ObjectDefinitionSource does not support secure object class: "
+ + getSecureObjectClass());
}
}
- protected void afterInvocation(InterceptorStatusToken token) {
+ /**
+ * Completes the work of the AbstractSecurityInterceptor after
+ * the secure object invocation has been complete
+ *
+ * @param token as returned by the {@link #beforeInvocation(Object)}}
+ * method
+ * @param returnedObject any object returned from the secure object
+ * invocation (may benull)
+ *
+ * @return the object the secure object invocation should ultimately return
+ * to its caller (may be null)
+ */
+ protected Object afterInvocation(InterceptorStatusToken token,
+ Object returnedObject) {
if (token == null) {
- return;
+ // public object
+ return returnedObject;
}
- if (logger.isDebugEnabled()) {
- logger.debug("Reverting to original Authentication: "
- + token.getAuthenticated().toString());
+ if (token.isContextHolderRefreshRequired()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Reverting to original Authentication: "
+ + token.getAuthentication().toString());
+ }
+
+ SecureContext secureContext = (SecureContext) ContextHolder
+ .getContext();
+ secureContext.setAuthentication(token.getAuthentication());
+ ContextHolder.setContext(secureContext);
}
- SecureContext secureContext = (SecureContext) ContextHolder.getContext();
- secureContext.setAuthentication(token.getAuthenticated());
- ContextHolder.setContext(secureContext);
+ if (afterInvocationManager != null) {
+ returnedObject = afterInvocationManager.decide(token
+ .getAuthentication(), token.getSecureObject(),
+ token.getAttr(), returnedObject);
+ }
+
+ return returnedObject;
}
protected InterceptorStatusToken beforeInvocation(Object object) {
@@ -275,10 +366,11 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
throw new IllegalArgumentException("Object was null");
}
- if (!this.obtainObjectDefinitionSource().supports(object.getClass())) {
+ if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
- "ObjectDefinitionSource does not support objects of type "
- + object.getClass());
+ "Security invocation attempted for object " + object
+ + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ + getSecureObjectClass());
}
ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource()
@@ -365,7 +457,8 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
"RunAsManager did not change Authentication object");
}
- return null; // no further work post-invocation
+ return new InterceptorStatusToken(authenticated, false, attr,
+ object); // no further work post-invocation
} else {
if (logger.isDebugEnabled()) {
logger.debug("Switching to RunAs Authentication: "
@@ -375,10 +468,8 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
context.setAuthentication(runAs);
ContextHolder.setContext((Context) context);
- InterceptorStatusToken token = new InterceptorStatusToken();
- token.setAuthenticated(authenticated);
-
- return token; // revert to token.Authenticated post-invocation
+ return new InterceptorStatusToken(authenticated, true, attr,
+ object); // revert to token.Authenticated post-invocation
}
} else {
if (logger.isDebugEnabled()) {
diff --git a/core/src/main/java/org/acegisecurity/intercept/InterceptorStatusToken.java b/core/src/main/java/org/acegisecurity/intercept/InterceptorStatusToken.java
index 0d73384dde..95eda0e501 100644
--- a/core/src/main/java/org/acegisecurity/intercept/InterceptorStatusToken.java
+++ b/core/src/main/java/org/acegisecurity/intercept/InterceptorStatusToken.java
@@ -16,6 +16,7 @@
package net.sf.acegisecurity.intercept;
import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
/**
@@ -23,14 +24,9 @@ import net.sf.acegisecurity.Authentication;
*
*
* This class reflects the status of the security interception, so that the
- * final call to AbstractSecurityInterceptor can tidy up
- * correctly.
- *
- * Whilst this class currently only wraps a single object, it has been modelled
- * as a class so that future changes to the operation of
- * AbstractSecurityInterceptor are abstracted from subclasses.
+ * final call to {@link
+ * net.sf.acegisecurity.intercept.AbstractSecurityInterceptor#afterInvocation(InterceptorStatusToken,
+ * Object)} can tidy up correctly.
*
- * The secure object type is org.aspectj.lang.JointPoint, which is
+ * The secure object type is org.aspectj.lang.JoinPoint, which is
* passed from the relevant around() advice. The
* around() advice also passes an anonymous implementation of
* {@link AspectJCallback} which contains the call for AspectJ to continue
@@ -64,18 +64,8 @@ public class AspectJSecurityInterceptor extends AbstractSecurityInterceptor {
return this.objectDefinitionSource;
}
- public void afterPropertiesSet() {
- super.afterPropertiesSet();
-
- if (!this.getAccessDecisionManager().supports(JoinPoint.class)) {
- throw new IllegalArgumentException(
- "AccessDecisionManager does not support JointPoint");
- }
-
- if (!this.getRunAsManager().supports(JoinPoint.class)) {
- throw new IllegalArgumentException(
- "RunAsManager does not support JointPoint");
- }
+ public Class getSecureObjectClass() {
+ return JoinPoint.class;
}
/**
@@ -91,13 +81,13 @@ public class AspectJSecurityInterceptor extends AbstractSecurityInterceptor {
* @return The returned value from the method invocation
*/
public Object invoke(JoinPoint jp, AspectJCallback advisorProceed) {
- Object result;
+ Object result = null;
InterceptorStatusToken token = super.beforeInvocation(jp);
try {
result = advisorProceed.proceedWithObject();
} finally {
- super.afterInvocation(token);
+ result = super.afterInvocation(token, result);
}
return result;
diff --git a/core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java b/core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java
index c54b00314a..e288a651d7 100644
--- a/core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java
+++ b/core/src/main/java/org/acegisecurity/intercept/web/FilterSecurityInterceptor.java
@@ -59,18 +59,8 @@ public class FilterSecurityInterceptor extends AbstractSecurityInterceptor {
return this.objectDefinitionSource;
}
- public void afterPropertiesSet() {
- super.afterPropertiesSet();
-
- if (!this.getAccessDecisionManager().supports(FilterInvocation.class)) {
- throw new IllegalArgumentException(
- "AccessDecisionManager does not support FilterInvocation");
- }
-
- if (!this.getRunAsManager().supports(FilterInvocation.class)) {
- throw new IllegalArgumentException(
- "RunAsManager does not support FilterInvocation");
- }
+ public Class getSecureObjectClass() {
+ return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws Throwable {
@@ -79,7 +69,7 @@ public class FilterSecurityInterceptor extends AbstractSecurityInterceptor {
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
- super.afterInvocation(token);
+ super.afterInvocation(token, null);
}
}