Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: net/sf/acegisecurity/vote/BasicAclEntryVoter.java


1   /* Copyright 2004 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package net.sf.acegisecurity.vote;
17  
18  import net.sf.acegisecurity.Authentication;
19  import net.sf.acegisecurity.AuthorizationServiceException;
20  import net.sf.acegisecurity.ConfigAttribute;
21  import net.sf.acegisecurity.ConfigAttributeDefinition;
22  import net.sf.acegisecurity.acl.AclEntry;
23  import net.sf.acegisecurity.acl.AclManager;
24  import net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry;
25  
26  import org.aopalliance.intercept.MethodInvocation;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import org.springframework.beans.factory.InitializingBean;
32  import org.springframework.util.Assert;
33  
34  import java.lang.reflect.InvocationTargetException;
35  import java.lang.reflect.Method;
36  
37  import java.util.Iterator;
38  
39  
40  /**
41   * <p>
42   * Given a domain object instance passed as a method argument, ensures the
43   * principal has appropriate permission as defined by the {@link AclManager}.
44   * </p>
45   * 
46   * <p>
47   * The <code>AclManager</code> is used to retrieve the access control list
48   * (ACL) permissions associated with a domain object instance for the current
49   * <code>Authentication</code> object. This class is designed to process
50   * {@link AclEntry}s that are subclasses of {@link
51   * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry} only. Generally these
52   * are obtained by using the {@link
53   * net.sf.acegisecurity.acl.basic.BasicAclProvider}.
54   * </p>
55   * 
56   * <p>
57   * The voter will vote if any  {@link ConfigAttribute#getAttribute()} matches
58   * the {@link #processConfigAttribute}. The provider will then locate the
59   * first method argument of type {@link #processDomainObjectClass}. Assuming
60   * that method argument is non-null, the provider will then lookup the ACLs
61   * from the <code>AclManager</code> and ensure the principal is {@link
62   * net.sf.acegisecurity.acl.basic.AbstractBasicAclEntry#isPermitted(int)} for
63   * at least one of the {@link #requirePermission}s.
64   * </p>
65   * 
66   * <p>
67   * If the method argument is <code>null</code>, the voter will abstain from
68   * voting. If the method argument could not be found, an {@link
69   * net.sf.acegisecurity.AuthorizationServiceException} will be thrown.
70   * </p>
71   * 
72   * <p>
73   * In practical terms users will typically setup a number of
74   * <code>BasicAclEntryVoter</code>s. Each will have a different {@link
75   * #processDomainObjectClass}, {@link #processConfigAttribute} and {@link
76   * #requirePermission} combination. For example, a small application might
77   * employ the following instances of <code>BasicAclEntryVoter</code>:
78   * 
79   * <ul>
80   * <li>
81   * Process domain object class <code>BankAccount</code>, configuration
82   * attribute <code>VOTE_ACL_BANK_ACCONT_READ</code>, require permission
83   * <code>SimpleAclEntry.READ</code>
84   * </li>
85   * <li>
86   * Process domain object class <code>BankAccount</code>, configuration
87   * attribute <code>VOTE_ACL_BANK_ACCOUNT_WRITE</code>, require permission list
88   * <code>SimpleAclEntry.WRITE</code> and <code>SimpleAclEntry.CREATE</code>
89   * (allowing the principal to have <b>either</b> of these two permissions
90   * </li>
91   * <li>
92   * Process domain object class <code>Customer</code>, configuration attribute
93   * <code>VOTE_ACL_CUSTOMER_READ</code>, require permission
94   * <code>SimpleAclEntry.READ</code>
95   * </li>
96   * <li>
97   * Process domain object class <code>Customer</code>, configuration attribute
98   * <code>VOTE_ACL_CUSTOMER_WRITE</code>, require permission list
99   * <code>SimpleAclEntry.WRITE</code> and <code>SimpleAclEntry.CREATE</code>
100  * </li>
101  * </ul>
102  * 
103  * Alternatively, you could have used a common superclass or interface for the
104  * {@link #processDomainObjectClass} if both <code>BankAccount</code> and
105  * <code>Customer</code> had common parents.
106  * </p>
107  * 
108  * <p>
109  * If the principal does not have sufficient permissions, the voter will vote
110  * to deny access.
111  * </p>
112  * 
113  * <p>
114  * The <code>AclManager</code> is allowed to return any implementations of
115  * <code>AclEntry</code> it wishes. However, this provider will only be able
116  * to validate against <code>AbstractBasicAclEntry</code>s, and thus a vote to
117  * deny access will be made if no <code>AclEntry</code> is of type
118  * <code>AbstractBasicAclEntry</code>.
119  * </p>
120  * 
121  * <p>
122  * All comparisons and prefixes are case sensitive.
123  * </p>
124  *
125  * @author Ben Alex
126  * @version $Id: BasicAclEntryVoter.java,v 1.4 2005/04/15 01:21:41 luke_t Exp $
127  */
128 public class BasicAclEntryVoter implements AccessDecisionVoter,
129     InitializingBean {
130     //~ Static fields/initializers =============================================
131 
132     private static final Log logger = LogFactory.getLog(BasicAclEntryVoter.class);
133 
134     //~ Instance fields ========================================================
135 
136     private AclManager aclManager;
137     private Class processDomainObjectClass;
138     private String internalMethod;
139     private String processConfigAttribute;
140     private int[] requirePermission;
141 
142     //~ Methods ================================================================
143 
144     public void setAclManager(AclManager aclManager) {
145         this.aclManager = aclManager;
146     }
147 
148     public AclManager getAclManager() {
149         return aclManager;
150     }
151 
152     public void setInternalMethod(String internalMethod) {
153         this.internalMethod = internalMethod;
154     }
155 
156     /**
157      * Optionally specifies a method of the domain object that will be used to
158      * obtain a contained domain object. That contained domain object will be
159      * used for the ACL evaluation. This is useful if a domain object contains
160      * a parent that an ACL evaluation should be targeted for, instead of the
161      * child domain object (which perhaps is being created and as such does
162      * not yet have any ACL permissions)
163      *
164      * @return <code>null</code> to use the domain object, or the name of a
165      *         method (that requires no arguments) that should be invoked to
166      *         obtain an <code>Object</code> which will be the domain object
167      *         used for ACL evaluation
168      */
169     public String getInternalMethod() {
170         return internalMethod;
171     }
172 
173     public void setProcessConfigAttribute(String processConfigAttribute) {
174         this.processConfigAttribute = processConfigAttribute;
175     }
176 
177     public String getProcessConfigAttribute() {
178         return processConfigAttribute;
179     }
180 
181     public void setProcessDomainObjectClass(Class processDomainObjectClass) {
182         this.processDomainObjectClass = processDomainObjectClass;
183     }
184 
185     public Class getProcessDomainObjectClass() {
186         return processDomainObjectClass;
187     }
188 
189     public void setRequirePermission(int[] requirePermission) {
190         this.requirePermission = requirePermission;
191     }
192 
193     public int[] getRequirePermission() {
194         return requirePermission;
195     }
196 
197     public void afterPropertiesSet() throws Exception {
198         Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory");
199         Assert.notNull(aclManager, "An aclManager is mandatory");
200         Assert.notNull(processDomainObjectClass, "A processDomainObjectClass is mandatory");
201 
202         if ((requirePermission == null) || (requirePermission.length == 0)) {
203             throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
204         }
205     }
206 
207     public boolean supports(ConfigAttribute attribute) {
208         if ((attribute.getAttribute() != null)
209             && attribute.getAttribute().startsWith(getProcessConfigAttribute())) {
210             return true;
211         } else {
212             return false;
213         }
214     }
215 
216     /**
217      * This implementation supports only
218      * <code>MethodSecurityInterceptor</code>, because it queries the
219      * presented <code>MethodInvocation</code>.
220      *
221      * @param clazz the secure object
222      *
223      * @return <code>true</code> if the secure object is
224      *         <code>MethodInvocation</code>, <code>false</code> otherwise
225      */
226     public boolean supports(Class clazz) {
227         return (MethodInvocation.class.isAssignableFrom(clazz));
228     }
229 
230     public int vote(Authentication authentication, Object object,
231         ConfigAttributeDefinition config) {
232         Iterator iter = config.getConfigAttributes();
233 
234         while (iter.hasNext()) {
235             ConfigAttribute attr = (ConfigAttribute) iter.next();
236 
237             if (this.supports(attr)) {
238                 // Need to make an access decision on this invocation
239                 // Attempt to locate the domain object instance to process
240                 Object domainObject = getDomainObjectInstance(object);
241 
242                 // If domain object is null, vote to abstain
243                 if (domainObject == null) {
244                     return AccessDecisionVoter.ACCESS_ABSTAIN;
245                 }
246 
247                 // Evaluate if we are required to use an inner domain object
248                 if ((internalMethod != null) && !"".equals(internalMethod)) {
249                     try {
250                         Class clazz = domainObject.getClass();
251                         Method method = clazz.getMethod(internalMethod, null);
252                         domainObject = method.invoke(domainObject, null);
253                     } catch (NoSuchMethodException nsme) {
254                         throw new AuthorizationServiceException(
255                             "Object of class '" + domainObject.getClass()
256                             + "' does not provide the requested internalMethod: "
257                             + internalMethod);
258                     } catch (IllegalAccessException iae) {
259                         if (logger.isDebugEnabled()) {
260                             logger.debug("IllegalAccessException", iae);
261 
262                             if (iae.getCause() != null) {
263                                 logger.debug("Cause: "
264                                     + iae.getCause().getMessage(),
265                                     iae.getCause());
266                             }
267                         }
268 
269                         throw new AuthorizationServiceException(
270                             "Problem invoking internalMethod: "
271                             + internalMethod + " for object: " + domainObject);
272                     } catch (InvocationTargetException ite) {
273                         if (logger.isDebugEnabled()) {
274                             logger.debug("InvocationTargetException", ite);
275 
276                             if (ite.getCause() != null) {
277                                 logger.debug("Cause: "
278                                     + ite.getCause().getMessage(),
279                                     ite.getCause());
280                             }
281                         }
282 
283                         throw new AuthorizationServiceException(
284                             "Problem invoking internalMethod: "
285                             + internalMethod + " for object: " + domainObject);
286                     }
287                 }
288 
289                 // Obtain the ACLs applicable to the domain object
290                 AclEntry[] acls = aclManager.getAcls(domainObject,
291                         authentication);
292 
293                 // If principal has no permissions for domain object, deny
294                 if ((acls == null) || (acls.length == 0)) {
295                     return AccessDecisionVoter.ACCESS_DENIED;
296                 }
297 
298                 // Principal has some permissions for domain object, check them
299                 for (int i = 0; i < acls.length; i++) {
300                     // Locate processable AclEntrys
301                     if (acls[i] instanceof AbstractBasicAclEntry) {
302                         AbstractBasicAclEntry processableAcl = (AbstractBasicAclEntry) acls[i];
303 
304                         // See if principal has any of the required permissions
305                         for (int y = 0; y < requirePermission.length; y++) {
306                             if (processableAcl.isPermitted(requirePermission[y])) {
307                                 return AccessDecisionVoter.ACCESS_GRANTED;
308                             }
309                         }
310                     }
311                 }
312 
313                 // No permissions match
314                 return AccessDecisionVoter.ACCESS_DENIED;
315             }
316         }
317 
318         // No configuration attribute matched, so abstain
319         return AccessDecisionVoter.ACCESS_ABSTAIN;
320     }
321 
322     private Object getDomainObjectInstance(Object secureObject) {
323         MethodInvocation invocation = (MethodInvocation) secureObject;
324 
325         // Check if this MethodInvocation provides the required argument
326         Method method = invocation.getMethod();
327         Class[] params = method.getParameterTypes();
328 
329         for (int i = 0; i < params.length; i++) {
330             if (processDomainObjectClass.isAssignableFrom(params[i])) {
331                 return invocation.getArguments()[i];
332             }
333         }
334 
335         throw new AuthorizationServiceException("MethodInvocation: "
336             + invocation + " did not provide any argument of type: "
337             + processDomainObjectClass);
338     }
339 }