Преглед на файлове

Refactoring to support "after invocation" processing.

Ben Alex преди 21 години
родител
ревизия
5f6aa9c49e

+ 103 - 0
core/src/main/java/org/acegisecurity/AfterInvocationManager.java

@@ -0,0 +1,103 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity;
+
+/**
+ * Reviews the <code>Object</code> returned from a secure object invocation,
+ * being able to modify the <code>Object</code> or throw an {@link
+ * AccessDeniedException}.
+ * 
+ * <p>
+ * 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 <code>Collection</code> 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.
+ * </p>
+ * 
+ * <p>
+ * Special consideration should be given to using an
+ * <code>AfterInvocationManager</code> on bean methods that modify a database.
+ * Typically an <code>AfterInvocationManager</code> is used with read-only
+ * methods, such as <code>public DomainObject getById(id)</code>. If used with
+ * methods that modify a database, a transaction manager should be used to
+ * ensure any <code>AccessDeniedException</code> will cause a rollback of the
+ * changes made by the transaction.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AfterInvocationManager {
+    //~ Methods ================================================================
+
+    /**
+     * Given the details of a secure object invocation including its returned
+     * <code>Object</code>, make an access control decision or optionally
+     * modify the returned <code>Object</code>.
+     *
+     * @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 <code>Object</code> that was returned from the
+     *        secure object invocation
+     *
+     * @return the <code>Object</code> 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
+     *         <code>returnedObject</code> method argument)
+     *
+     * @throws AccessDeniedException if access is denied
+     */
+    public Object decide(Authentication authentication, Object object,
+        ConfigAttributeDefinition config, Object returnedObject)
+        throws AccessDeniedException;
+
+    /**
+     * Indicates whether this <code>AfterInvocationManager</code> is able to
+     * process "after invocation" requests presented with the passed
+     * <code>ConfigAttribute</code>.
+     * 
+     * <p>
+     * This allows the <code>AbstractSecurityInterceptor</code> to check every
+     * configuration attribute can be consumed by the configured
+     * <code>AccessDecisionManager</code> and/or <code>RunAsManager</code>
+     * and/or <code>AfterInvocationManager</code>.
+     * </p>
+     *
+     * @param attribute a configuration attribute that has been configured
+     *        against the <code>AbstractSecurityInterceptor</code>
+     *
+     * @return true if this <code>AfterInvocationManager</code> can support the
+     *         passed configuration attribute
+     */
+    public boolean supports(ConfigAttribute attribute);
+
+    /**
+     * Indicates whether the <code>AfterInvocationManager</code> implementation
+     * is able to provide access control decisions for the indicated secured
+     * object type.
+     *
+     * @param clazz the class that is being queried
+     *
+     * @return <code>true</code> if the implementation can process the
+     *         indicated class
+     */
+    public boolean supports(Class clazz);
+}

+ 134 - 43
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
  * <code>ConfigAttributeDefinition</code> for the secure object invocation):
  * 
- * <ol>
+ * <ol type="a">
  * <li>
  * Authenticate the request against the configured {@link
  * AuthenticationManager}, replacing the <code>Authentication</code> object on
@@ -103,6 +104,11 @@ import java.util.Set;
  * object, return the <code>ContextHolder</code> to the object that existed
  * after the call to <code>AuthenticationManager</code>.
  * </li>
+ * <li>
+ * If an <code>AfterInvocationManager</code> is defined, invoke the invocation
+ * manager and allow it to replace the object due to be returned to the
+ * caller.
+ * </li>
  * </ol>
  * 
  * </li>
@@ -110,7 +116,7 @@ import java.util.Set;
  * For an invocation that is public (there is no
  * <code>ConfigAttributeDefinition</code> for the secure object invocation):
  * 
- * <ol>
+ * <ol type="a">
  * <li>
  * If the <code>ContextHolder</code> contains a <code>SecureContext</code>, set
  * the <code>isAuthenticated</code> flag on the <code>Authentication</code>
@@ -128,9 +134,9 @@ import java.util.Set;
  * 
  * </li>
  * <li>
- * Control again returns to the concrete subclass, which will return to the
- * caller any result or exception that occurred when it proceeded with the
- * execution of the secure object.
+ * Control again returns to the concrete subclass, along with the
+ * <code>Object</code> that should be returned to the caller.  The subclass
+ * will then return that  result or exception to the original caller.
  * </li>
  * </ol>
  * </p>
@@ -147,6 +153,7 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
     //~ Instance fields ========================================================
 
     private AccessDecisionManager accessDecisionManager;
+    private AfterInvocationManager afterInvocationManager;
     private ApplicationContext context;
     private AuthenticationManager authenticationManager;
     private RunAsManager runAsManager = new NullRunAsManager();
@@ -154,11 +161,30 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
 
     //~ Methods ================================================================
 
+    public void setAfterInvocationManager(
+        AfterInvocationManager afterInvocationManager) {
+        this.afterInvocationManager = afterInvocationManager;
+    }
+
+    public AfterInvocationManager getAfterInvocationManager() {
+        return afterInvocationManager;
+    }
+
     public void setApplicationContext(ApplicationContext applicationContext)
         throws BeansException {
         this.context = applicationContext;
     }
 
+    /**
+     * Indicates the type of secure objects the subclass will be presenting to
+     * the abstract parent for processing. This is used to ensure
+     * collaborators wired to the <code>AbstractSecurityInterceptor</code> 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 <code>AbstractSecurityInterceptor</code> 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 be<code>null</code>)
+     *
+     * @return the object the secure object invocation should ultimately return
+     *         to its caller (may be <code>null</code>)
+     */
+    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()) {

+ 35 - 13
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;
  * 
  * <P>
  * This class reflects the status of the security interception, so that the
- * final call to <code>AbstractSecurityInterceptor</code> can tidy up
- * correctly.
- * </p>
- * 
- * <P>
- * Whilst this class currently only wraps a single object, it has been modelled
- * as a class so that future changes to the operation of
- * <code>AbstractSecurityInterceptor</code> are abstracted from subclasses.
+ * final call to {@link
+ * net.sf.acegisecurity.intercept.AbstractSecurityInterceptor#afterInvocation(InterceptorStatusToken,
+ * Object)} can tidy up correctly.
  * </p>
  *
  * @author Ben Alex
@@ -39,15 +35,41 @@ import net.sf.acegisecurity.Authentication;
 public class InterceptorStatusToken {
     //~ Instance fields ========================================================
 
-    private Authentication authenticated;
+    private Authentication authentication;
+    private ConfigAttributeDefinition attr;
+    private Object secureObject;
+    private boolean contextHolderRefreshRequired;
+
+    //~ Constructors ===========================================================
+
+    public InterceptorStatusToken(Authentication authentication,
+        boolean contextHolderRefreshRequired, ConfigAttributeDefinition attr,
+        Object secureObject) {
+        this.authentication = authentication;
+        this.contextHolderRefreshRequired = contextHolderRefreshRequired;
+        this.attr = attr;
+        this.secureObject = secureObject;
+    }
+
+    protected InterceptorStatusToken() {
+        throw new IllegalArgumentException("Cannot use default constructor");
+    }
 
     //~ Methods ================================================================
 
-    public void setAuthenticated(Authentication authenticated) {
-        this.authenticated = authenticated;
+    public ConfigAttributeDefinition getAttr() {
+        return attr;
+    }
+
+    public Authentication getAuthentication() {
+        return authentication;
+    }
+
+    public boolean isContextHolderRefreshRequired() {
+        return contextHolderRefreshRequired;
     }
 
-    public Authentication getAuthenticated() {
-        return authenticated;
+    public Object getSecureObject() {
+        return secureObject;
     }
 }

+ 4 - 14
core/src/main/java/org/acegisecurity/intercept/method/aopalliance/MethodSecurityInterceptor.java

@@ -58,18 +58,8 @@ public class MethodSecurityInterceptor extends AbstractSecurityInterceptor
         return this.objectDefinitionSource;
     }
 
-    public void afterPropertiesSet() {
-        super.afterPropertiesSet();
-
-        if (!this.getAccessDecisionManager().supports(MethodInvocation.class)) {
-            throw new IllegalArgumentException(
-                "AccessDecisionManager does not support MethodInvocation");
-        }
-
-        if (!this.getRunAsManager().supports(MethodInvocation.class)) {
-            throw new IllegalArgumentException(
-                "RunAsManager does not support MethodInvocation");
-        }
+    public Class getSecureObjectClass() {
+        return MethodInvocation.class;
     }
 
     /**
@@ -83,13 +73,13 @@ public class MethodSecurityInterceptor extends AbstractSecurityInterceptor
      * @throws Throwable if any error occurs
      */
     public Object invoke(MethodInvocation mi) throws Throwable {
-        Object result;
+        Object result = null;
         InterceptorStatusToken token = super.beforeInvocation(mi);
 
         try {
             result = mi.proceed();
         } finally {
-            super.afterInvocation(token);
+            result = super.afterInvocation(token, result);
         }
 
         return result;

+ 5 - 15
core/src/main/java/org/acegisecurity/intercept/method/aspectj/AspectJSecurityInterceptor.java

@@ -35,7 +35,7 @@ import org.aspectj.lang.JoinPoint;
  * </p>
  * 
  * <p>
- * The secure object type is <code>org.aspectj.lang.JointPoint</code>, which is
+ * The secure object type is <code>org.aspectj.lang.JoinPoint</code>, which is
  * passed from the relevant <code>around()</code> advice. The
  * <code>around()</code> 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;

+ 3 - 13
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);
         }
     }