View Javadoc

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