Преглед изворни кода

SEC-52: Move potentially useful methods to an abstract superclass so that other voters can use them.

Ben Alex пре 20 година
родитељ
комит
31a1f0be1a

+ 166 - 0
core/src/main/java/org/acegisecurity/vote/AbstractAclVoter.java

@@ -0,0 +1,166 @@
+/* Copyright 2004, 2005 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.AuthorizationServiceException;
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.acl.AclEntry;
+import net.sf.acegisecurity.acl.AclManager;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * <p>
+ * Given a domain object instance passed as a method argument, 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.BasicAclEntry} only. Generally these are
+ * obtained by using the {@link
+ * net.sf.acegisecurity.acl.basic.BasicAclProvider}.
+ * </p>
+ * 
+ * <p>
+ * 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 <code>AclManager</code> and ensure the principal is {@link
+ * net.sf.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for at least
+ * one of the {@link #requirePermission}s.
+ * </p>
+ * 
+ * <p>
+ * If the method argument is <code>null</code>, the voter will abstain from
+ * voting. If the method argument could not be found, an {@link
+ * net.sf.acegisecurity.AuthorizationServiceException} will be thrown.
+ * </p>
+ * 
+ * <p>
+ * In practical terms users will typically setup a number of
+ * <code>BasicAclEntryVoter</code>s. Each will have a different {@link
+ * #processDomainObjectClass}, {@link #processConfigAttribute} and {@link
+ * #requirePermission} combination. For example, a small application might
+ * employ the following instances of <code>BasicAclEntryVoter</code>:
+ * 
+ * <ul>
+ * <li>
+ * Process domain object class <code>BankAccount</code>, configuration
+ * attribute <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission
+ * <code>SimpleAclEntry.READ</code>
+ * </li>
+ * <li>
+ * Process domain object class <code>BankAccount</code>, configuration
+ * attribute <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list
+ * <code>SimpleAclEntry.WRITE</code> and <code>SimpleAclEntry.CREATE</code>
+ * (allowing the principal to have <b>either</b> of these two permissions
+ * </li>
+ * <li>
+ * Process domain object class <code>Customer</code>, configuration attribute
+ * <code>VOTE_ACL_CUSTOMER_READ</code>, require permission
+ * <code>SimpleAclEntry.READ</code>
+ * </li>
+ * <li>
+ * Process domain object class <code>Customer</code>, configuration attribute
+ * <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list
+ * <code>SimpleAclEntry.WRITE</code> and <code>SimpleAclEntry.CREATE</code>
+ * </li>
+ * </ul>
+ * 
+ * Alternatively, you could have used a common superclass or interface for the
+ * {@link #processDomainObjectClass} if both <code>BankAccount</code> and
+ * <code>Customer</code> had common parents.
+ * </p>
+ * 
+ * <p>
+ * If the principal does not have sufficient permissions, the voter will vote
+ * to deny access.
+ * </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 vote to
+ * deny access will be made if no <code>AclEntry</code> is of type
+ * <code>AbstractBasicAclEntry</code>.
+ * </p>
+ * 
+ * <p>
+ * All comparisons and prefixes are case sensitive.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class AbstractAclVoter implements AccessDecisionVoter {
+    //~ Instance fields ========================================================
+
+    private Class processDomainObjectClass;
+
+    //~ Methods ================================================================
+
+    public void setProcessDomainObjectClass(Class processDomainObjectClass) {
+        Assert.notNull(processDomainObjectClass,
+            "processDomainObjectClass cannot be set to null");
+        this.processDomainObjectClass = processDomainObjectClass;
+    }
+
+    public Class getProcessDomainObjectClass() {
+        return processDomainObjectClass;
+    }
+
+    /**
+     * This implementation supports only
+     * <code>MethodSecurityInterceptor</code>, because it queries the
+     * presented <code>MethodInvocation</code>.
+     *
+     * @param clazz the secure object
+     *
+     * @return <code>true</code> if the secure object is
+     *         <code>MethodInvocation</code>, <code>false</code> otherwise
+     */
+    public boolean supports(Class clazz) {
+        return (MethodInvocation.class.isAssignableFrom(clazz));
+    }
+
+    protected Object getDomainObjectInstance(Object secureObject) {
+        MethodInvocation invocation = (MethodInvocation) secureObject;
+
+        // Check if this MethodInvocation provides the required argument
+        Method method = invocation.getMethod();
+        Class[] params = method.getParameterTypes();
+
+        for (int i = 0; i < params.length; i++) {
+            if (processDomainObjectClass.isAssignableFrom(params[i])) {
+                return invocation.getArguments()[i];
+            }
+        }
+
+        throw new AuthorizationServiceException("MethodInvocation: "
+            + invocation + " did not provide any argument of type: "
+            + processDomainObjectClass);
+    }
+}

+ 7 - 52
core/src/main/java/org/acegisecurity/vote/BasicAclEntryVoter.java

@@ -23,8 +23,6 @@ import net.sf.acegisecurity.acl.AclEntry;
 import net.sf.acegisecurity.acl.AclManager;
 import net.sf.acegisecurity.acl.basic.BasicAclEntry;
 
-import org.aopalliance.intercept.MethodInvocation;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -49,8 +47,8 @@ import java.util.Iterator;
  * (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.BasicAclEntry} only. Generally these
- * are obtained by using the {@link
+ * net.sf.acegisecurity.acl.basic.BasicAclEntry} only. Generally these are
+ * obtained by using the {@link
  * net.sf.acegisecurity.acl.basic.BasicAclProvider}.
  * </p>
  * 
@@ -60,8 +58,8 @@ import java.util.Iterator;
  * first method argument of type {@link #processDomainObjectClass}. Assuming
  * that method argument is non-null, the provider will then lookup the ACLs
  * from the <code>AclManager</code> and ensure the principal is {@link
- * net.sf.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for
- * at least one of the {@link #requirePermission}s.
+ * net.sf.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for at least
+ * one of the {@link #requirePermission}s.
  * </p>
  * 
  * <p>
@@ -126,8 +124,8 @@ import java.util.Iterator;
  * @author Ben Alex
  * @version $Id$
  */
-public class BasicAclEntryVoter implements AccessDecisionVoter,
-    InitializingBean {
+public class BasicAclEntryVoter extends AbstractAclVoter
+    implements InitializingBean {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(BasicAclEntryVoter.class);
@@ -135,7 +133,6 @@ public class BasicAclEntryVoter implements AccessDecisionVoter,
     //~ Instance fields ========================================================
 
     private AclManager aclManager;
-    private Class processDomainObjectClass;
     private String internalMethod;
     private String processConfigAttribute;
     private int[] requirePermission;
@@ -179,14 +176,6 @@ public class BasicAclEntryVoter implements AccessDecisionVoter,
         return processConfigAttribute;
     }
 
-    public void setProcessDomainObjectClass(Class processDomainObjectClass) {
-        this.processDomainObjectClass = processDomainObjectClass;
-    }
-
-    public Class getProcessDomainObjectClass() {
-        return processDomainObjectClass;
-    }
-
     public void setRequirePermission(int[] requirePermission) {
         this.requirePermission = requirePermission;
     }
@@ -199,8 +188,6 @@ public class BasicAclEntryVoter implements AccessDecisionVoter,
         Assert.notNull(processConfigAttribute,
             "A processConfigAttribute is mandatory");
         Assert.notNull(aclManager, "An aclManager is mandatory");
-        Assert.notNull(processDomainObjectClass,
-            "A processDomainObjectClass is mandatory");
 
         if ((requirePermission == null) || (requirePermission.length == 0)) {
             throw new IllegalArgumentException(
@@ -217,20 +204,6 @@ public class BasicAclEntryVoter implements AccessDecisionVoter,
         }
     }
 
-    /**
-     * This implementation supports only
-     * <code>MethodSecurityInterceptor</code>, because it queries the
-     * presented <code>MethodInvocation</code>.
-     *
-     * @param clazz the secure object
-     *
-     * @return <code>true</code> if the secure object is
-     *         <code>MethodInvocation</code>, <code>false</code> otherwise
-     */
-    public boolean supports(Class clazz) {
-        return (MethodInvocation.class.isAssignableFrom(clazz));
-    }
-
     public int vote(Authentication authentication, Object object,
         ConfigAttributeDefinition config) {
         Iterator iter = config.getConfigAttributes();
@@ -305,7 +278,7 @@ public class BasicAclEntryVoter implements AccessDecisionVoter,
                 for (int i = 0; i < acls.length; i++) {
                     // Locate processable AclEntrys
                     if (acls[i] instanceof BasicAclEntry) {
-                    	BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
+                        BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
 
                         // See if principal has any of the required permissions
                         for (int y = 0; y < requirePermission.length; y++) {
@@ -324,22 +297,4 @@ public class BasicAclEntryVoter implements AccessDecisionVoter,
         // No configuration attribute matched, so abstain
         return AccessDecisionVoter.ACCESS_ABSTAIN;
     }
-
-    private Object getDomainObjectInstance(Object secureObject) {
-        MethodInvocation invocation = (MethodInvocation) secureObject;
-
-        // Check if this MethodInvocation provides the required argument
-        Method method = invocation.getMethod();
-        Class[] params = method.getParameterTypes();
-
-        for (int i = 0; i < params.length; i++) {
-            if (processDomainObjectClass.isAssignableFrom(params[i])) {
-                return invocation.getArguments()[i];
-            }
-        }
-
-        throw new AuthorizationServiceException("MethodInvocation: "
-            + invocation + " did not provide any argument of type: "
-            + processDomainObjectClass);
-    }
 }

+ 2 - 14
core/src/test/java/org/acegisecurity/vote/BasicAclEntryVoterTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -156,22 +156,10 @@ public class BasicAclEntryVoterTests extends TestCase {
 
     public void testStartupRejectsMissingProcessDomainObjectClass()
         throws Exception {
-        AclManager aclManager = new MockAclManager("domain1", "marissa",
-                new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
-                        "marissa", new MockAclObjectIdentity(), null,
-                        SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
-                        "marissa", new MockAclObjectIdentity(), null,
-                        SimpleAclEntry.READ), new SimpleAclEntry("marissa",
-                        new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
-
-        // Wire up a voter
         BasicAclEntryVoter voter = new BasicAclEntryVoter();
-        voter.setAclManager(aclManager);
-        voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
-        voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
 
         try {
-            voter.afterPropertiesSet();
+            voter.setProcessDomainObjectClass(null);
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);