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 }