Răsfoiți Sursa

Initial commit of a concrete AfterInvocationManager.

Ben Alex 21 ani în urmă
părinte
comite
612971e134

+ 68 - 0
core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProvider.java

@@ -0,0 +1,68 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+
+/**
+ * Indicates a class is responsible for participating in an {@link
+ * AfterInvocationProviderManager} decision.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AfterInvocationProvider {
+    //~ Methods ================================================================
+
+    public Object decide(Authentication authentication, Object object,
+        ConfigAttributeDefinition config, Object returnedObject)
+        throws AccessDeniedException;
+
+    /**
+     * Indicates whether this <code>AfterInvocationProvider</code> is able to
+     * participate in a decision involving 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>AccessDecisionManager</code>.
+     * </p>
+     *
+     * @param attribute a configuration attribute that has been configured
+     *        against the <code>AbstractSecurityInterceptor</code>
+     *
+     * @return true if this <code>AfterInvocationProvider</code> can support
+     *         the passed configuration attribute
+     */
+    public boolean supports(ConfigAttribute attribute);
+
+    /**
+     * Indicates whether the <code>AfterInvocationProvider</code> is able to
+     * provide "after invocation" processing for the indicated secured object
+     * type.
+     *
+     * @param clazz the class of secure object that is being queried
+     *
+     * @return true if the implementation can process the indicated class
+     */
+    public boolean supports(Class clazz);
+}

+ 167 - 0
core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProviderManager.java

@@ -0,0 +1,167 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.AfterInvocationManager;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Provider-based implementation of {@link AfterInvocationManager}.
+ * 
+ * <p>
+ * Handles configuration of a bean context defined list of  {@link
+ * AfterInvocationProvider}s.
+ * </p>
+ * 
+ * <p>
+ * Every <code>AfterInvocationProvider</code> will be polled when the {@link
+ * #decide(Authentication, Object, ConfigAttributeDefinition, Object)} method
+ * is called. The <code>Object</code> returned from each provider will be
+ * presented to the successive provider for processing. This means each
+ * provider <b>must</b> ensure they return the <code>Object</code>, even if
+ * they are not interested in the "after invocation" decision (perhaps as the
+ * secure object invocation did not include a configuration attribute a given
+ * provider is configured to respond to).
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AfterInvocationProviderManager implements AfterInvocationManager,
+    InitializingBean {
+    //~ Static fields/initializers =============================================
+
+    protected static final Log logger = LogFactory.getLog(AfterInvocationProviderManager.class);
+
+    //~ Instance fields ========================================================
+
+    private List providers;
+
+    //~ Methods ================================================================
+
+    public void setProviders(List newList) {
+        checkIfValidList(newList);
+
+        Iterator iter = newList.iterator();
+
+        while (iter.hasNext()) {
+            Object currentObject = null;
+
+            try {
+                currentObject = iter.next();
+
+                AfterInvocationProvider attemptToCast = (AfterInvocationProvider) currentObject;
+            } catch (ClassCastException cce) {
+                throw new IllegalArgumentException("AfterInvocationProvider "
+                    + currentObject.getClass().getName()
+                    + " must implement AfterInvocationProvider");
+            }
+        }
+
+        this.providers = newList;
+    }
+
+    public List getProviders() {
+        return this.providers;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        checkIfValidList(this.providers);
+    }
+
+    public Object decide(Authentication authentication, Object object,
+        ConfigAttributeDefinition config, Object returnedObject)
+        throws AccessDeniedException {
+        Iterator iter = this.providers.iterator();
+
+        Object result = returnedObject;
+
+        while (iter.hasNext()) {
+            AfterInvocationProvider provider = (AfterInvocationProvider) iter
+                .next();
+            result = provider.decide(authentication, object, config, result);
+        }
+
+        return result;
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        Iterator iter = this.providers.iterator();
+
+        while (iter.hasNext()) {
+            AfterInvocationProvider provider = (AfterInvocationProvider) iter
+                .next();
+
+            logger.fatal("Evaluating " + attribute + " against " + provider);
+
+            if (provider.supports(attribute)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Iterates through all <code>AfterInvocationProvider</code>s and ensures
+     * each can support the presented class.
+     * 
+     * <p>
+     * If one or more providers cannot support the presented class,
+     * <code>false</code> is returned.
+     * </p>
+     *
+     * @param clazz the secure object class being queries
+     *
+     * @return if the <code>AfterInvocationProviderManager</code> can support
+     *         the secure object class, which requires every one of its
+     *         <code>AfterInvocationProvider</code>s to support the secure
+     *         object class
+     */
+    public boolean supports(Class clazz) {
+        Iterator iter = this.providers.iterator();
+
+        while (iter.hasNext()) {
+            AfterInvocationProvider provider = (AfterInvocationProvider) iter
+                .next();
+
+            if (!provider.supports(clazz)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void checkIfValidList(List listToCheck) {
+        if ((listToCheck == null) || (listToCheck.size() == 0)) {
+            throw new IllegalArgumentException(
+                "A list of AfterInvocationProviders is required");
+        }
+    }
+}

+ 250 - 0
core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java

@@ -0,0 +1,250 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthorizationServiceException;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.acl.AclManager;
+import net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+
+/**
+ * <p>
+ * Given a <code>Collection</code> of domain object instances returned from a
+ * secure object invocation, remove any <code>Collection</code> elements the
+ * principal does not have appropriate permission to access as defined by the
+ * {@link AclManager}.
+ * </p>
+ * 
+ * <p>
+ * The <code>AclManager</code> is used to retrieve the access control list
+ * (ACL) permissions associated with each <code>Collection</code>  domain
+ * object instance element for the current <code>Authentication</code> object.
+ * This class is designed to process {@link AclEntry}s that are subclasses of
+ * {@link net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry} only.
+ * Generally these are obtained by using the {@link
+ * net.sf.acegisecurity.acl.basic.BasicAclProvider}.
+ * </p>
+ * 
+ * <p>
+ * This after invocation provider will fire if any {@link
+ * ConfigAttribute#getAttribute()} matches the {@link
+ * #processConfigAttribute}. The provider will then lookup the ACLs from the
+ * <code>AclManager</code> and ensure the principal is {@link
+ * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry#isPermitted(int)} for
+ * at least one of the {@link #requirePermission}s for each
+ * <code>Collection</code> element. If the principal does not have at least
+ * one of the permissions, that element will not be included in the returned
+ * <code>Collection</code>.
+ * </p>
+ * 
+ * <p>
+ * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code>
+ * with a {@link #processConfigAttribute} of
+ * <code>AFTER_ACL_COLLECTION_READ</code> and a {@link #requirePermission} of
+ * <code>SimpleAclEntry.READ</code>. These are also the defaults.
+ * </p>
+ * 
+ * <p>
+ * The <code>AclManager</code> is allowed to return any implementations of
+ * <code>AclEntry</code> it wishes. However, this provider will only be able
+ * to validate against <code>AbstractBasicAclEntry</code>s, and thus a
+ * <code>Collection</code> element will be filtered from the resulting
+ * <code>Collection</code> if no <code>AclEntry</code> is of type
+ * <code>AbstractBasicAclEntry</code>.
+ * </p>
+ * 
+ * <p>
+ * If the provided <code>returnObject</code> is <code>null</code>, a
+ * <code>null</code><code>Collection</code> will be returned. If the provided
+ * <code>returnObject</code> is not a <code>Collection</code>, an {@link
+ * AuthorizationServiceException} will be thrown.
+ * </p>
+ * 
+ * <p>
+ * All comparisons and prefixes are case sensitive.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BasicAclEntryAfterInvocationCollectionFilteringProvider
+    implements AfterInvocationProvider, InitializingBean {
+    //~ Instance fields ========================================================
+
+    private AclManager aclManager;
+    private String processConfigAttribute = "AFTER_ACL_COLLECTION_READ";
+    private int[] requirePermission = {SimpleAclEntry.READ};
+
+    //~ Methods ================================================================
+
+    public void setAclManager(AclManager aclManager) {
+        this.aclManager = aclManager;
+    }
+
+    public AclManager getAclManager() {
+        return aclManager;
+    }
+
+    public void setProcessConfigAttribute(String processConfigAttribute) {
+        this.processConfigAttribute = processConfigAttribute;
+    }
+
+    public String getProcessConfigAttribute() {
+        return processConfigAttribute;
+    }
+
+    public void setRequirePermission(int[] requirePermission) {
+        this.requirePermission = requirePermission;
+    }
+
+    public int[] getRequirePermission() {
+        return requirePermission;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (processConfigAttribute == null) {
+            throw new IllegalArgumentException(
+                "A processConfigAttribute is mandatory");
+        }
+
+        if ((requirePermission == null) || (requirePermission.length == 0)) {
+            throw new IllegalArgumentException(
+                "One or more requirePermission entries is mandatory");
+        }
+
+        if (aclManager == null) {
+            throw new IllegalArgumentException("An aclManager is mandatory");
+        }
+    }
+
+    public Object decide(Authentication authentication, Object object,
+        ConfigAttributeDefinition config, Object returnedObject)
+        throws AccessDeniedException {
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attr = (ConfigAttribute) iter.next();
+
+            if (this.supports(attr)) {
+                // Need to process the Collection for this invocation
+                if (returnedObject == null) {
+                    return null;
+                }
+
+                if (!(returnedObject instanceof Collection)) {
+                    throw new AuthorizationServiceException(
+                        "A Collection (or null) was required as the returnedObject, but the returnedObject was: "
+                        + returnedObject);
+                }
+
+                Collection collection = (Collection) returnedObject;
+
+                // We create a Set of objects to be removed from the Collection,
+                // as ConcurrentModificationException prevents removal during
+                // iteration, and making a new Collection to be returned is
+                // problematic as the original Collection implementation passed
+                // to the method may not necessarily be re-constructable (as
+                // the Collection(collection) constructor is not guaranteed and
+                // manually adding may lose sort order or other capabilities)
+                Set removeList = new HashSet();
+
+                // Locate unauthorised Collection elements
+                Iterator collectionIter = collection.iterator();
+
+                while (collectionIter.hasNext()) {
+                    Object domainObject = collectionIter.next();
+
+                    boolean hasPermission = false;
+
+                    AclEntry[] acls = null;
+
+                    if (domainObject == null) {
+                        hasPermission = true;
+                    } else {
+                        acls = aclManager.getAcls(domainObject, authentication);
+                    }
+
+                    if ((acls != null) && (acls.length != 0)) {
+                        for (int i = 0; i < acls.length; i++) {
+                            // Locate processable AclEntrys
+                            if (acls[i] instanceof AbstractBasicAclEntry) {
+                                AbstractBasicAclEntry processableAcl = (AbstractBasicAclEntry) acls[i];
+
+                                // See if principal has any of the required permissions
+                                for (int y = 0; y < requirePermission.length;
+                                    y++) {
+                                    if (processableAcl.isPermitted(
+                                            requirePermission[y])) {
+                                        hasPermission = true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    if (!hasPermission) {
+                        removeList.add(domainObject);
+                    }
+                }
+
+                // Now the Iterator has ended, remove Objects from Collection
+                Iterator removeIter = removeList.iterator();
+
+                while (removeIter.hasNext()) {
+                    collection.remove(removeIter.next());
+                }
+
+                return collection;
+            }
+        }
+
+        return returnedObject;
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        if ((attribute.getAttribute() != null)
+            && attribute.getAttribute().equals(getProcessConfigAttribute())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * This implementation supports any type of class, because it does not
+     * query the presented secure object.
+     *
+     * @param clazz the secure object
+     *
+     * @return always <code>true</code>
+     */
+    public boolean supports(Class clazz) {
+        return true;
+    }
+}

+ 211 - 0
core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java

@@ -0,0 +1,211 @@
+/* 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.afterinvocation;
+
+import net.sf.acegisecurity.AccessDeniedException;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.acl.AclManager;
+import net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry;
+import net.sf.acegisecurity.acl.basic.SimpleAclEntry;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.Iterator;
+
+
+/**
+ * <p>
+ * Given a domain object instance returned from a secure object invocation,
+ * ensures the principal has appropriate permission as defined by the {@link
+ * AclManager}.
+ * </p>
+ * 
+ * <p>
+ * The <code>AclManager</code> is used to retrieve the access control list
+ * (ACL) permissions associated with a domain object instance for the current
+ * <code>Authentication</code> object. This class is designed to process
+ * {@link AclEntry}s that are subclasses of {@link
+ * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry} only. Generally these
+ * are obtained by using the {@link
+ * net.sf.acegisecurity.acl.basic.BasicAclProvider}.
+ * </p>
+ * 
+ * <p>
+ * This after invocation provider will fire if any  {@link
+ * ConfigAttribute#getAttribute()} matches the {@link
+ * #processConfigAttribute}. The provider will then lookup the ACLs from the
+ * <code>AclManager</code> and ensure the principal is {@link
+ * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry#isPermitted(int)} for
+ * at least one of the {@link #requirePermission}s.
+ * </p>
+ * 
+ * <p>
+ * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code>
+ * with a {@link #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a
+ * {@link #requirePermission} of  <code>SimpleAclEntry.READ</code>. These are
+ * also the defaults.
+ * </p>
+ * 
+ * <p>
+ * If the principal does not have sufficient permissions, an
+ * <code>AccessDeniedException</code> will be thrown.
+ * </p>
+ * 
+ * <p>
+ * The <code>AclManager</code> is allowed to return any implementations of
+ * <code>AclEntry</code> it wishes. However, this provider will only be able
+ * to validate against <code>AbstractBasicAclEntry</code>s, and thus access
+ * will be denied if no <code>AclEntry</code> is of type
+ * <code>AbstractBasicAclEntry</code>.
+ * </p>
+ * 
+ * <p>
+ * If the provided <code>returnObject</code> is <code>null</code>, permission
+ * will always be granted and <code>null</code> will be returned.
+ * </p>
+ * 
+ * <p>
+ * All comparisons and prefixes are case sensitive.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BasicAclEntryAfterInvocationProvider
+    implements AfterInvocationProvider, InitializingBean {
+    //~ Instance fields ========================================================
+
+    private AclManager aclManager;
+    private String processConfigAttribute = "AFTER_ACL_READ";
+    private int[] requirePermission = {SimpleAclEntry.READ};
+
+    //~ Methods ================================================================
+
+    public void setAclManager(AclManager aclManager) {
+        this.aclManager = aclManager;
+    }
+
+    public AclManager getAclManager() {
+        return aclManager;
+    }
+
+    public void setProcessConfigAttribute(String processConfigAttribute) {
+        this.processConfigAttribute = processConfigAttribute;
+    }
+
+    public String getProcessConfigAttribute() {
+        return processConfigAttribute;
+    }
+
+    public void setRequirePermission(int[] requirePermission) {
+        this.requirePermission = requirePermission;
+    }
+
+    public int[] getRequirePermission() {
+        return requirePermission;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (processConfigAttribute == null) {
+            throw new IllegalArgumentException(
+                "A processConfigAttribute is mandatory");
+        }
+
+        if ((requirePermission == null) || (requirePermission.length == 0)) {
+            throw new IllegalArgumentException(
+                "One or more requirePermission entries is mandatory");
+        }
+
+        if (aclManager == null) {
+            throw new IllegalArgumentException("An aclManager is mandatory");
+        }
+    }
+
+    public Object decide(Authentication authentication, Object object,
+        ConfigAttributeDefinition config, Object returnedObject)
+        throws AccessDeniedException {
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attr = (ConfigAttribute) iter.next();
+
+            if (this.supports(attr)) {
+                // Need to make an access decision on this invocation
+                if (returnedObject == null) {
+                    // AclManager interface contract prohibits nulls
+                    // As they have permission to null/nothing, grant access
+                    return null;
+                }
+
+                AclEntry[] acls = aclManager.getAcls(returnedObject,
+                        authentication);
+
+                if ((acls == null) || (acls.length == 0)) {
+                    throw new AccessDeniedException("Authentication: "
+                        + authentication.toString()
+                        + " has NO permissions at all to the domain object: "
+                        + returnedObject);
+                }
+
+                for (int i = 0; i < acls.length; i++) {
+                    // Locate processable AclEntrys
+                    if (acls[i] instanceof AbstractBasicAclEntry) {
+                        AbstractBasicAclEntry processableAcl = (AbstractBasicAclEntry) acls[i];
+
+                        // See if principal has any of the required permissions
+                        for (int y = 0; y < requirePermission.length; y++) {
+                            if (processableAcl.isPermitted(requirePermission[y])) {
+                                return returnedObject;
+                            }
+                        }
+                    }
+                }
+
+                // No permissions match
+                throw new AccessDeniedException("Authentication: "
+                    + authentication.toString()
+                    + " has ACL permissions to the domain object, but not the required ACL permission to the domain object: "
+                    + returnedObject);
+            }
+        }
+
+        return returnedObject;
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        if ((attribute.getAttribute() != null)
+            && attribute.getAttribute().equals(getProcessConfigAttribute())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * This implementation supports any type of class, because it does not
+     * query the presented secure object.
+     *
+     * @param clazz the secure object
+     *
+     * @return always <code>true</code>
+     */
+    public boolean supports(Class clazz) {
+        return true;
+    }
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/afterinvocation/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Used for post-processing of an object returned from a secure object invocation.
+</body>
+</html>