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.taglibs.authz;
17  
18  import org.acegisecurity.Authentication;
19  import org.acegisecurity.acl.AclEntry;
20  import org.acegisecurity.acl.AclManager;
21  import org.acegisecurity.acl.basic.BasicAclEntry;
22  import org.acegisecurity.context.SecurityContextHolder;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  import org.springframework.context.ApplicationContext;
28  
29  import org.springframework.web.context.support.WebApplicationContextUtils;
30  import org.springframework.web.util.ExpressionEvaluationUtils;
31  
32  import java.util.HashSet;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.StringTokenizer;
36  
37  import javax.servlet.ServletContext;
38  import javax.servlet.jsp.JspException;
39  import javax.servlet.jsp.PageContext;
40  import javax.servlet.jsp.tagext.Tag;
41  import javax.servlet.jsp.tagext.TagSupport;
42  
43  
44  /***
45   * An implementation of {@link javax.servlet.jsp.tagext.Tag} that allows its
46   * body through if some authorizations are granted to the request's principal.
47   * 
48   * <P>
49   * Only works with permissions that are subclasses of {@link
50   * org.acegisecurity.acl.basic.BasicAclEntry}.
51   * </p>
52   * 
53   * <p>
54   * One or more comma separate integer permissions are specified via the
55   * <code>hasPermission</code> attribute. The tag will include its body if
56   * <b>any</b> of the integer permissions have been granted to the current
57   * <code>Authentication</code> (obtained from the <code>SecurityContextHolder</code>).
58   * </p>
59   * 
60   * <p>
61   * For this class to operate it must be able to access the application context
62   * via the <code>WebApplicationContextUtils</code> and locate an {@link
63   * AclManager}. Application contexts have no need to have more than one
64   * <code>AclManager</code> (as a provider-based implementation can be used so
65   * that it locates a provider that is authoritative for the given domain
66   * object instance), so the first <code>AclManager</code> located will be
67   * used.
68   * </p>
69   *
70   * @author Ben Alex
71   * @version $Id: AclTag.java,v 1.8 2005/11/17 00:56:29 benalex Exp $
72   */
73  public class AclTag extends TagSupport {
74      //~ Static fields/initializers =============================================
75  
76      protected static final Log logger = LogFactory.getLog(AclTag.class);
77  
78      //~ Instance fields ========================================================
79  
80      private Object domainObject;
81      private String hasPermission = "";
82  
83      //~ Methods ================================================================
84  
85      public void setDomainObject(Object domainObject) {
86          this.domainObject = domainObject;
87      }
88  
89      public Object getDomainObject() {
90          return domainObject;
91      }
92  
93      public void setHasPermission(String hasPermission) {
94          this.hasPermission = hasPermission;
95      }
96  
97      public String getHasPermission() {
98          return hasPermission;
99      }
100 
101     public int doStartTag() throws JspException {
102         if ((null == hasPermission) || "".equals(hasPermission)) {
103             return Tag.SKIP_BODY;
104         }
105 
106         final String evaledPermissionsString = ExpressionEvaluationUtils
107             .evaluateString("hasPermission", hasPermission, pageContext);
108 
109         Integer[] requiredIntegers = null;
110 
111         try {
112             requiredIntegers = parseIntegersString(evaledPermissionsString);
113         } catch (NumberFormatException nfe) {
114             throw new JspException(nfe);
115         }
116 
117         Object resolvedDomainObject = null;
118 
119         if (domainObject instanceof String) {
120             resolvedDomainObject = ExpressionEvaluationUtils.evaluate("domainObject",
121                     (String) domainObject, Object.class, pageContext);
122         } else {
123             resolvedDomainObject = domainObject;
124         }
125 
126         if (resolvedDomainObject == null) {
127             if (logger.isDebugEnabled()) {
128                 logger.debug(
129                     "domainObject resolved to null, so including tag body");
130             }
131 
132             // Of course they have access to a null object!
133             return Tag.EVAL_BODY_INCLUDE;
134         }
135 
136         if (SecurityContextHolder.getContext().getAuthentication() == null) {
137             if (logger.isDebugEnabled()) {
138                 logger.debug(
139                     "SecurityContextHolder did not return a non-null Authentication object, so skipping tag body");
140             }
141 
142             return Tag.SKIP_BODY;
143         }
144 
145         Authentication auth = SecurityContextHolder.getContext()
146                                                    .getAuthentication();
147 
148         ApplicationContext context = getContext(pageContext);
149         Map beans = context.getBeansOfType(AclManager.class, false, false);
150 
151         if (beans.size() == 0) {
152             throw new JspException(
153                 "No AclManager would found the application context: "
154                 + context.toString());
155         }
156 
157         String beanName = (String) beans.keySet().iterator().next();
158         AclManager aclManager = (AclManager) context.getBean(beanName);
159 
160         // Obtain aclEntrys applying to the current Authentication object
161         AclEntry[] acls = aclManager.getAcls(resolvedDomainObject, auth);
162 
163         if (logger.isDebugEnabled()) {
164             logger.debug("Authentication: '" + auth + "' has: "
165                 + ((acls == null) ? 0 : acls.length)
166                 + " AclEntrys for domain object: '" + resolvedDomainObject
167                 + "' from AclManager: '" + aclManager.toString() + "'");
168         }
169 
170         if ((acls == null) || (acls.length == 0)) {
171             return Tag.SKIP_BODY;
172         }
173 
174         for (int i = 0; i < acls.length; i++) {
175             // Locate processable AclEntrys
176             if (acls[i] instanceof BasicAclEntry) {
177             	BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
178 
179                 // See if principal has any of the required permissions
180                 for (int y = 0; y < requiredIntegers.length; y++) {
181                     if (processableAcl.isPermitted(
182                             requiredIntegers[y].intValue())) {
183                         if (logger.isDebugEnabled()) {
184                             logger.debug(
185                                 "Including tag body as found permission: "
186                                 + requiredIntegers[y] + " due to AclEntry: '"
187                                 + processableAcl + "'");
188                         }
189 
190                         return Tag.EVAL_BODY_INCLUDE;
191                     }
192                 }
193             }
194         }
195 
196         if (logger.isDebugEnabled()) {
197             logger.debug("No permission, so skipping tag body");
198         }
199 
200         return Tag.SKIP_BODY;
201     }
202 
203     /***
204      * Allows test cases to override where application context obtained from.
205      *
206      * @param pageContext so the <code>ServletContext</code> can be accessed as
207      *        required by Spring's <code>WebApplicationContextUtils</code>
208      *
209      * @return the Spring application context (never <code>null</code>)
210      */
211     protected ApplicationContext getContext(PageContext pageContext) {
212         ServletContext servletContext = pageContext.getServletContext();
213 
214         return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
215     }
216 
217     private Integer[] parseIntegersString(String integersString)
218         throws NumberFormatException {
219         final Set integers = new HashSet();
220         final StringTokenizer tokenizer;
221         tokenizer = new StringTokenizer(integersString, ",", false);
222 
223         while (tokenizer.hasMoreTokens()) {
224             String integer = tokenizer.nextToken();
225             integers.add(new Integer(integer));
226         }
227 
228         return (Integer[]) integers.toArray(new Integer[] {});
229     }
230 }