From 612971e1343cbd7b4327d3ef3696a9f1b7cb2780 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 15 Nov 2004 01:45:03 +0000 Subject: [PATCH] Initial commit of a concrete AfterInvocationManager. --- .../AfterInvocationProvider.java | 68 +++++ .../AfterInvocationProviderManager.java | 167 ++++++++++++ ...InvocationCollectionFilteringProvider.java | 250 ++++++++++++++++++ .../BasicAclEntryAfterInvocationProvider.java | 211 +++++++++++++++ .../afterinvocation/package.html | 5 + 5 files changed, 701 insertions(+) create mode 100644 core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProvider.java create mode 100644 core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProviderManager.java create mode 100644 core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java create mode 100644 core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java create mode 100644 core/src/main/java/org/acegisecurity/afterinvocation/package.html diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProvider.java b/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProvider.java new file mode 100644 index 0000000000..143426aa17 --- /dev/null +++ b/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 AfterInvocationProvider is able to + * participate in a decision involving the passed + * ConfigAttribute. + * + *

+ * This allows the AbstractSecurityInterceptor to check every + * configuration attribute can be consumed by the configured + * AccessDecisionManager and/or RunAsManager + * and/or AccessDecisionManager. + *

+ * + * @param attribute a configuration attribute that has been configured + * against the AbstractSecurityInterceptor + * + * @return true if this AfterInvocationProvider can support + * the passed configuration attribute + */ + public boolean supports(ConfigAttribute attribute); + + /** + * Indicates whether the AfterInvocationProvider 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); +} diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProviderManager.java b/core/src/main/java/org/acegisecurity/afterinvocation/AfterInvocationProviderManager.java new file mode 100644 index 0000000000..c7bc8de911 --- /dev/null +++ b/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}. + * + *

+ * Handles configuration of a bean context defined list of {@link + * AfterInvocationProvider}s. + *

+ * + *

+ * Every AfterInvocationProvider will be polled when the {@link + * #decide(Authentication, Object, ConfigAttributeDefinition, Object)} method + * is called. The Object returned from each provider will be + * presented to the successive provider for processing. This means each + * provider must ensure they return the Object, 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). + *

+ * + * @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 AfterInvocationProviders and ensures + * each can support the presented class. + * + *

+ * If one or more providers cannot support the presented class, + * false is returned. + *

+ * + * @param clazz the secure object class being queries + * + * @return if the AfterInvocationProviderManager can support + * the secure object class, which requires every one of its + * AfterInvocationProviders 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"); + } + } +} diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationCollectionFilteringProvider.java new file mode 100644 index 0000000000..d8c2f8809b --- /dev/null +++ b/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; + + +/** + *

+ * Given a Collection of domain object instances returned from a + * secure object invocation, remove any Collection elements the + * principal does not have appropriate permission to access as defined by the + * {@link AclManager}. + *

+ * + *

+ * The AclManager is used to retrieve the access control list + * (ACL) permissions associated with each Collection domain + * object instance element for the current Authentication 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}. + *

+ * + *

+ * This after invocation provider will fire if any {@link + * ConfigAttribute#getAttribute()} matches the {@link + * #processConfigAttribute}. The provider will then lookup the ACLs from the + * AclManager 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 + * Collection element. If the principal does not have at least + * one of the permissions, that element will not be included in the returned + * Collection. + *

+ * + *

+ * Often users will setup a BasicAclEntryAfterInvocationProvider + * with a {@link #processConfigAttribute} of + * AFTER_ACL_COLLECTION_READ and a {@link #requirePermission} of + * SimpleAclEntry.READ. These are also the defaults. + *

+ * + *

+ * The AclManager is allowed to return any implementations of + * AclEntry it wishes. However, this provider will only be able + * to validate against AbstractBasicAclEntrys, and thus a + * Collection element will be filtered from the resulting + * Collection if no AclEntry is of type + * AbstractBasicAclEntry. + *

+ * + *

+ * If the provided returnObject is null, a + * nullCollection will be returned. If the provided + * returnObject is not a Collection, an {@link + * AuthorizationServiceException} will be thrown. + *

+ * + *

+ * All comparisons and prefixes are case sensitive. + *

+ * + * @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 true + */ + public boolean supports(Class clazz) { + return true; + } +} diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java b/core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java new file mode 100644 index 0000000000..73f555a1d2 --- /dev/null +++ b/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; + + +/** + *

+ * Given a domain object instance returned from a secure object invocation, + * ensures the principal has appropriate permission as defined by the {@link + * AclManager}. + *

+ * + *

+ * The AclManager is used to retrieve the access control list + * (ACL) permissions associated with a domain object instance for the current + * Authentication 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}. + *

+ * + *

+ * This after invocation provider will fire if any {@link + * ConfigAttribute#getAttribute()} matches the {@link + * #processConfigAttribute}. The provider will then lookup the ACLs from the + * AclManager and ensure the principal is {@link + * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry#isPermitted(int)} for + * at least one of the {@link #requirePermission}s. + *

+ * + *

+ * Often users will setup a BasicAclEntryAfterInvocationProvider + * with a {@link #processConfigAttribute} of AFTER_ACL_READ and a + * {@link #requirePermission} of SimpleAclEntry.READ. These are + * also the defaults. + *

+ * + *

+ * If the principal does not have sufficient permissions, an + * AccessDeniedException will be thrown. + *

+ * + *

+ * The AclManager is allowed to return any implementations of + * AclEntry it wishes. However, this provider will only be able + * to validate against AbstractBasicAclEntrys, and thus access + * will be denied if no AclEntry is of type + * AbstractBasicAclEntry. + *

+ * + *

+ * If the provided returnObject is null, permission + * will always be granted and null will be returned. + *

+ * + *

+ * All comparisons and prefixes are case sensitive. + *

+ * + * @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 true + */ + public boolean supports(Class clazz) { + return true; + } +} diff --git a/core/src/main/java/org/acegisecurity/afterinvocation/package.html b/core/src/main/java/org/acegisecurity/afterinvocation/package.html new file mode 100644 index 0000000000..9446663d6b --- /dev/null +++ b/core/src/main/java/org/acegisecurity/afterinvocation/package.html @@ -0,0 +1,5 @@ + + +Used for post-processing of an object returned from a secure object invocation. + +