View Javadoc

1   /* Copyright 2004, 2005 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 org.acegisecurity.afterinvocation;
17  
18  import java.util.Iterator;
19  
20  import org.acegisecurity.AccessDeniedException;
21  import org.acegisecurity.AcegiMessageSource;
22  import org.acegisecurity.Authentication;
23  import org.acegisecurity.ConfigAttribute;
24  import org.acegisecurity.ConfigAttributeDefinition;
25  import org.acegisecurity.acl.AclEntry;
26  import org.acegisecurity.acl.AclManager;
27  import org.acegisecurity.acl.basic.BasicAclEntry;
28  import org.acegisecurity.acl.basic.SimpleAclEntry;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.springframework.beans.factory.InitializingBean;
32  import org.springframework.context.MessageSource;
33  import org.springframework.context.MessageSourceAware;
34  import org.springframework.context.support.MessageSourceAccessor;
35  import org.springframework.util.Assert;
36  
37  
38  /***
39   * <p>
40   * Given a domain object instance returned from a secure object invocation,
41   * ensures the principal has appropriate permission as defined by the {@link
42   * AclManager}.
43   * </p>
44   * 
45   * <p>
46   * The <code>AclManager</code> is used to retrieve the access control list
47   * (ACL) permissions associated with a domain object instance for the current
48   * <code>Authentication</code> object. This class is designed to process
49   * {@link AclEntry}s that are subclasses of {@link
50   * org.acegisecurity.acl.basic.BasicAclEntry} only. Generally these are
51   * obtained by using the {@link org.acegisecurity.acl.basic.BasicAclProvider}.
52   * </p>
53   * 
54   * <p>
55   * This after invocation provider will fire if any  {@link
56   * ConfigAttribute#getAttribute()} matches the {@link
57   * #processConfigAttribute}. The provider will then lookup the ACLs from the
58   * <code>AclManager</code> and ensure the principal is {@link
59   * org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for at least
60   * one of the {@link #requirePermission}s.
61   * </p>
62   * 
63   * <p>
64   * Often users will setup a <code>BasicAclEntryAfterInvocationProvider</code>
65   * with a {@link #processConfigAttribute} of <code>AFTER_ACL_READ</code> and a
66   * {@link #requirePermission} of  <code>SimpleAclEntry.READ</code>. These are
67   * also the defaults.
68   * </p>
69   * 
70   * <p>
71   * If the principal does not have sufficient permissions, an
72   * <code>AccessDeniedException</code> will be thrown.
73   * </p>
74   * 
75   * <p>
76   * The <code>AclManager</code> is allowed to return any implementations of
77   * <code>AclEntry</code> it wishes. However, this provider will only be able
78   * to validate against <code>BasicAclEntry</code>s, and thus access will be
79   * denied if no <code>AclEntry</code> is of type <code>BasicAclEntry</code>.
80   * </p>
81   * 
82   * <p>
83   * If the provided <code>returnObject</code> is <code>null</code>, permission
84   * will always be granted and <code>null</code> will be returned.
85   * </p>
86   * 
87   * <p>
88   * All comparisons and prefixes are case sensitive.
89   * </p>
90   */
91  public class BasicAclEntryAfterInvocationProvider
92      implements AfterInvocationProvider, InitializingBean, MessageSourceAware {
93      //~ Static fields/initializers =============================================
94  
95      protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationProvider.class);
96  
97      //~ Instance fields ========================================================
98  
99      private AclManager aclManager;
100     protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
101     private String processConfigAttribute = "AFTER_ACL_READ";
102     private int[] requirePermission = {SimpleAclEntry.READ};
103 
104     //~ Methods ================================================================
105 
106     public void afterPropertiesSet() throws Exception {
107         Assert.notNull(processConfigAttribute,
108             "A processConfigAttribute is mandatory");
109         Assert.notNull(aclManager, "An aclManager is mandatory");
110         Assert.notNull(messages, "A message source must be set");
111 
112         if ((requirePermission == null) || (requirePermission.length == 0)) {
113             throw new IllegalArgumentException(
114                 "One or more requirePermission entries is mandatory");
115         }
116     }
117 
118     public Object decide(Authentication authentication, Object object,
119         ConfigAttributeDefinition config, Object returnedObject)
120         throws AccessDeniedException {
121         Iterator iter = config.getConfigAttributes();
122 
123         while (iter.hasNext()) {
124             ConfigAttribute attr = (ConfigAttribute) iter.next();
125 
126             if (this.supports(attr)) {
127                 // Need to make an access decision on this invocation
128                 if (returnedObject == null) {
129                     // AclManager interface contract prohibits nulls
130                     // As they have permission to null/nothing, grant access
131                     if (logger.isDebugEnabled()) {
132                         logger.debug("Return object is null, skipping");
133                     }
134 
135                     return null;
136                 }
137 
138                 AclEntry[] acls = aclManager.getAcls(returnedObject,
139                         authentication);
140 
141                 if ((acls == null) || (acls.length == 0)) {
142                     throw new AccessDeniedException(messages.getMessage(
143                             "BasicAclEntryAfterInvocationProvider.noPermission",
144                             new Object[] {authentication.getName(), returnedObject},
145                             "Authentication {0} has NO permissions at all to the domain object {1}"));
146                 }
147 
148                 for (int i = 0; i < acls.length; i++) {
149                     // Locate processable AclEntrys
150                     if (acls[i] instanceof BasicAclEntry) {
151                         BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
152 
153                         // See if principal has any of the required permissions
154                         for (int y = 0; y < requirePermission.length; y++) {
155                             if (processableAcl.isPermitted(requirePermission[y])) {
156                                 if (logger.isDebugEnabled()) {
157                                     logger.debug(
158                                         "Principal DOES have permission to return object: "
159                                         + returnedObject + " due to ACL: "
160                                         + processableAcl.toString());
161                                 }
162 
163                                 return returnedObject;
164                             }
165                         }
166                     }
167                 }
168 
169                 // No permissions match
170                 throw new AccessDeniedException(messages.getMessage(
171                         "BasicAclEntryAfterInvocationProvider.insufficientPermission",
172                         new Object[] {authentication.getName(), returnedObject},
173                         "Authentication {0} has ACL permissions to the domain object, but not the required ACL permission to the domain object {1}"));
174             }
175         }
176 
177         return returnedObject;
178     }
179 
180     public AclManager getAclManager() {
181         return aclManager;
182     }
183 
184     public String getProcessConfigAttribute() {
185         return processConfigAttribute;
186     }
187 
188     public int[] getRequirePermission() {
189         return requirePermission;
190     }
191 
192     public void setAclManager(AclManager aclManager) {
193         this.aclManager = aclManager;
194     }
195 
196     public void setMessageSource(MessageSource messageSource) {
197         this.messages = new MessageSourceAccessor(messageSource);
198     }
199 
200     public void setProcessConfigAttribute(String processConfigAttribute) {
201         this.processConfigAttribute = processConfigAttribute;
202     }
203 
204     public void setRequirePermission(int[] requirePermission) {
205         this.requirePermission = requirePermission;
206     }
207 
208     public boolean supports(ConfigAttribute attribute) {
209         if ((attribute.getAttribute() != null)
210             && attribute.getAttribute().equals(getProcessConfigAttribute())) {
211             return true;
212         } else {
213             return false;
214         }
215     }
216 
217     /***
218      * This implementation supports any type of class, because it does not
219      * query the presented secure object.
220      *
221      * @param clazz the secure object
222      *
223      * @return always <code>true</code>
224      */
225     public boolean supports(Class clazz) {
226         return true;
227     }
228 }