diff --git a/core/src/main/java/org/acegisecurity/AuthorizationServiceException.java b/core/src/main/java/org/acegisecurity/AuthorizationServiceException.java new file mode 100644 index 0000000000..dde0dc0ab9 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AuthorizationServiceException.java @@ -0,0 +1,53 @@ +/* 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; + +/** + * Thrown if an authorization request could not be processed due to a system + * problem. + * + *

+ * This might be thrown if an AccessDecisionManager implementation + * could not locate a required method argument, for example. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class AuthorizationServiceException extends AccessDeniedException { + //~ Constructors =========================================================== + + /** + * Constructs an AuthorizationServiceException with the + * specified message. + * + * @param msg the detail message + */ + public AuthorizationServiceException(String msg) { + super(msg); + } + + /** + * Constructs an AuthorizationServiceException with the + * specified message and root cause. + * + * @param msg the detail message + * @param t root cause + */ + public AuthorizationServiceException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/core/src/main/java/org/acegisecurity/vote/BasicAclEntryVoter.java b/core/src/main/java/org/acegisecurity/vote/BasicAclEntryVoter.java new file mode 100644 index 0000000000..a41b80a49a --- /dev/null +++ b/core/src/main/java/org/acegisecurity/vote/BasicAclEntryVoter.java @@ -0,0 +1,277 @@ +/* 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.vote; + +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 org.aopalliance.intercept.MethodInvocation; + +import org.springframework.beans.factory.InitializingBean; + +import java.util.Iterator; + + +/** + *

+ * Given a domain object instance passed as a method argument, 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}. + *

+ * + *

+ * The voter will vote if any {@link ConfigAttribute#getAttribute()} matches + * the {@link #processConfigAttribute}. The provider will then locate the + * first method argument of type {@link #processDomainObjectClass}. Assuming + * that method argument is non-null, 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. + *

+ * + *

+ * If the method argument is null, the voter will abstain from + * voting. If the method argument could not be found, an {@link + * net.sf.acegisecurity.AuthorizationServiceException} will be thrown. + *

+ * + *

+ * In practical terms users will typically setup a number of + * BasicAclEntryVoters. Each will have a different {@link + * #processDomainObjectClass}, {@link #processConfigAttribute} and {@link + * #requirePermission} combination. For example, a small application might + * employ the following instances of BasicAclEntryVoter: + * + *

+ * + * Alternatively, you could have used a common superclass or interface for the + * {@link #processDomainObjectClass} if both BankAccount and + * Customer had common parents. + *

+ * + *

+ * If the principal does not have sufficient permissions, the voter will vote + * to deny access. + *

+ * + *

+ * 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 vote to + * deny access will be made if no AclEntry is of type + * AbstractBasicAclEntry. + *

+ * + *

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

+ * + * @author Ben Alex + * @version $Id$ + */ +public class BasicAclEntryVoter implements AccessDecisionVoter, + InitializingBean { + //~ Instance fields ======================================================== + + private AclManager aclManager; + private Class processDomainObjectClass; + private String processConfigAttribute; + private int[] requirePermission; + + //~ 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 setProcessDomainObjectClass(Class processDomainObjectClass) { + this.processDomainObjectClass = processDomainObjectClass; + } + + public Class getProcessDomainObjectClass() { + return processDomainObjectClass; + } + + 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"); + } + + if (processDomainObjectClass == null) { + throw new IllegalArgumentException( + "A processDomainObjectClass is mandatory"); + } + } + + public boolean supports(ConfigAttribute attribute) { + if ((attribute.getAttribute() != null) + && attribute.getAttribute().startsWith(getProcessConfigAttribute())) { + return true; + } else { + return false; + } + } + + /** + * This implementation supports only + * MethodSecurityInterceptor, because it queries the + * presented MethodInvocation. + * + * @param clazz the secure object + * + * @return true if the secure object is + * MethodInvocation, false otherwise + */ + public boolean supports(Class clazz) { + return (MethodInvocation.class.isAssignableFrom(clazz)); + } + + public int vote(Authentication authentication, Object object, + ConfigAttributeDefinition config) { + 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 + // Attempt to locate the domain object instance to process + Object domainObject = getDomainObjectInstance(object); + + // If domain object is null, vote to abstain + if (domainObject == null) { + return AccessDecisionVoter.ACCESS_ABSTAIN; + } + + // Obtain the ACLs applicable to the domain object + AclEntry[] acls = aclManager.getAcls(domainObject, + authentication); + + // If principal has no permissions for domain object, deny + if ((acls == null) || (acls.length == 0)) { + return AccessDecisionVoter.ACCESS_DENIED; + } + + // Principal has some permissions for domain object, check them + 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 AccessDecisionVoter.ACCESS_GRANTED; + } + } + } + } + + // No permissions match + return AccessDecisionVoter.ACCESS_DENIED; + } + } + + // No configuration attribute matched, so abstain + return AccessDecisionVoter.ACCESS_ABSTAIN; + } + + private Object getDomainObjectInstance(Object secureObject) { + if (secureObject instanceof MethodInvocation) { + MethodInvocation invocation = (MethodInvocation) secureObject; + + for (int i = 0; i < invocation.getArguments().length; i++) { + Class argClass = invocation.getArguments()[i].getClass(); + + if (processDomainObjectClass.isAssignableFrom(argClass)) { + return invocation.getArguments()[i]; + } + } + + throw new AuthorizationServiceException("MethodInvocation: " + + invocation + " did not provide any argument of type: " + + processDomainObjectClass); + } + + return null; // should never happen + } +}