Coverage Report - org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
HttpMethodPermissionFilter
73%
22/30
72%
13/18
2.111
HttpMethodPermissionFilter$HttpMethodAction
100%
13/13
N/A
2.111
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.shiro.web.filter.authz;
 20  
 
 21  
 import org.apache.shiro.util.StringUtils;
 22  
 import org.slf4j.Logger;
 23  
 import org.slf4j.LoggerFactory;
 24  
 
 25  
 import javax.servlet.ServletRequest;
 26  
 import javax.servlet.ServletResponse;
 27  
 import javax.servlet.http.HttpServletRequest;
 28  
 import java.io.IOException;
 29  
 import java.util.HashMap;
 30  
 import java.util.Map;
 31  
 
 32  
 /**
 33  
  * A filter that translates an HTTP Request's Method (eg GET, POST, etc)
 34  
  * into an corresponding action (verb) and uses that verb to construct a permission that will be checked to determine
 35  
  * access.
 36  
  * <p/>
 37  
  * This Filter is primarily provided to support REST environments where the type (Method)
 38  
  * of request translates to an action being performed on one or more resources.  This paradigm works well with Shiro's
 39  
  * concepts of using permissions for access control and can be leveraged to easily perform permission checks.
 40  
  * <p/>
 41  
  * This filter functions as follows:
 42  
  * <ol>
 43  
  * <li>The incoming HTTP request's Method (GET, POST, PUT, DELETE, etc) is discovered.</li>
 44  
  * <li>The Method is translated into a more 'application friendly' verb, such as 'create', edit', 'delete', etc.</li>
 45  
  * <li>The verb is appended to any configured permissions for the
 46  
  * {@link org.apache.shiro.web.filter.PathMatchingFilter currently matching path}.</li>
 47  
  * <li>If the current {@code Subject} {@link org.apache.shiro.subject.Subject#isPermitted(String) isPermitted} to
 48  
  * perform the resolved action, the request is allowed to continue.</li>
 49  
  * </ol>
 50  
  * <p/>
 51  
  * For example, if the following filter chain was defined, where 'rest' was the name given to a filter instance of
 52  
  * this class:
 53  
  * <pre>
 54  
  * /user/** = rest[user]</pre>
 55  
  * Then an HTTP {@code GET} request to {@code /user/1234} would translate to the constructed permission
 56  
  * {@code user:read} (GET is mapped to the 'read' action) and execute the permission check
 57  
  * <code>Subject.isPermitted(&quot;user:read&quot;)</code> in order to allow the request to continue.
 58  
  * <p/>
 59  
  * Similarly, an HTTP {@code POST} to {@code /user} would translate to the constructed permission
 60  
  * {@code user:create} (POST is mapped to the 'create' action) and execute the permission check
 61  
  * <code>Subject.isPermitted(&quot;user:create&quot;)</code> in order to allow the request to continue.
 62  
  * <p/>
 63  
  * <h3>Method To Verb Mapping</h3>
 64  
  * The following table represents the default HTTP Method-to-action verb mapping:
 65  
  * <table>
 66  
  * <tr><th>HTTP Method</th><th>Mapped Action</th><th>Example Permission</th><th>Runtime Check</th></tr>
 67  
  * <tr><td>head</td><td>read</td><td>perm1</td><td>perm1:read</td></tr>
 68  
  * <tr><td>get</td><td>read</td><td>perm2</td><td>perm2:read</td></tr>
 69  
  * <tr><td>put</td><td>update</td><td>perm3</td><td>perm3:update</td></tr>
 70  
  * <tr><td>post</td><td>create</td><td>perm4</td><td>perm4:create</td></tr>
 71  
  * <tr><td>mkcol</td><td>create</td><td>perm5</td><td>perm5:create</td></tr>
 72  
  * <tr><td>options</td><td>read</td><td>perm6</td><td>perm6:read</td></tr>
 73  
  * <tr><td>trace</td><td>read</td><td>perm7</td><td>perm7:read</td></tr>
 74  
  * </table>
 75  
  *
 76  
  * @since 1.0
 77  
  */
 78  
 public class HttpMethodPermissionFilter extends PermissionsAuthorizationFilter {
 79  
 
 80  
     /**
 81  
      * This class's private logger.
 82  
      */
 83  2
     private static final Logger log = LoggerFactory.getLogger(HttpMethodPermissionFilter.class);
 84  
 
 85  
     /**
 86  
      * Map that contains a mapping between http methods to permission actions (verbs)
 87  
      */
 88  96
     private final Map<String, String> httpMethodActions = new HashMap<String, String>();
 89  
 
 90  
     //Actions representing HTTP Method values (GET -> read, POST -> create, etc)
 91  
     private static final String CREATE_ACTION = "create";
 92  
     private static final String READ_ACTION = "read";
 93  
     private static final String UPDATE_ACTION = "update";
 94  
     private static final String DELETE_ACTION = "delete";
 95  
 
 96  
     /**
 97  
      * Enum of constants for well-defined mapping values.  Used in the Filter's constructor to perform the map instance
 98  
      * used at runtime.
 99  
      */
 100  98
     private static enum HttpMethodAction {
 101  
 
 102  2
         DELETE(DELETE_ACTION),
 103  2
         GET(READ_ACTION),
 104  2
         HEAD(READ_ACTION),
 105  2
         MKCOL(CREATE_ACTION), //webdav, but useful here
 106  2
         OPTIONS(READ_ACTION),
 107  2
         POST(CREATE_ACTION),
 108  2
         PUT(UPDATE_ACTION),
 109  2
         TRACE(READ_ACTION);
 110  
 
 111  
         private final String action;
 112  
 
 113  16
         private HttpMethodAction(String action) {
 114  16
             this.action = action;
 115  16
         }
 116  
 
 117  
         public String getAction() {
 118  768
             return this.action;
 119  
         }
 120  
     }
 121  
 
 122  
     /**
 123  
      * Creates the filter instance with default method-to-action values in the instance's
 124  
      * {@link #getHttpMethodActions() http method actions map}.
 125  
      */
 126  96
     public HttpMethodPermissionFilter() {
 127  864
         for (HttpMethodAction methodAction : HttpMethodAction.values()) {
 128  768
             httpMethodActions.put(methodAction.name().toLowerCase(), methodAction.getAction());
 129  
         }
 130  96
     }
 131  
 
 132  
     /**
 133  
      * Returns the HTTP Method name (key) to action verb (value) mapping used to resolve actions based on an
 134  
      * incoming {@code HttpServletRequest}.  All keys and values are lower-case.  The
 135  
      * default key/value pairs are defined in the top class-level JavaDoc.
 136  
      *
 137  
      * @return the HTTP Method lower-case name (key) to lower-case action verb (value) mapping
 138  
      */
 139  
     protected Map<String, String> getHttpMethodActions() {
 140  16
         return this.httpMethodActions;
 141  
     }
 142  
 
 143  
     /**
 144  
      * Determines the action (verb) attempting to be performed on the filtered resource by the current request.
 145  
      * <p/>
 146  
      * This implementation expects the incoming request to be an {@link HttpServletRequest} and returns a mapped
 147  
      * action based on the HTTP request {@link javax.servlet.http.HttpServletRequest#getMethod() method}.
 148  
      *
 149  
      * @param request to pull the method from.
 150  
      * @return The string equivalent verb of the http method.
 151  
      */
 152  
     protected String getHttpMethodAction(ServletRequest request) {
 153  0
         String method = ((HttpServletRequest) request).getMethod();
 154  0
         return getHttpMethodAction(method);
 155  
     }
 156  
 
 157  
     /**
 158  
      * Determines the corresponding application action that will be performed on the filtered resource based on the
 159  
      * specified HTTP method (GET, POST, etc).
 160  
      *
 161  
      * @param method to be translated into the verb.
 162  
      * @return The string equivalent verb of the method.
 163  
      */
 164  
     protected String getHttpMethodAction(String method) {
 165  16
         String lc = method.toLowerCase();
 166  16
         String resolved = getHttpMethodActions().get(lc);
 167  16
         return resolved != null ? resolved : method;
 168  
     }
 169  
 
 170  
     /**
 171  
      * Returns a collection of String permissions with which to perform a permission check to determine if the filter
 172  
      * will allow the request to continue.
 173  
      * <p/>
 174  
      * This implementation merely delegates to {@link #buildPermissions(String[], String)} and ignores the inbound
 175  
      * HTTP servlet request, but it can be overridden by subclasses for more complex request-specific building logic
 176  
      * if necessary.
 177  
      *
 178  
      * @param request         the inbound HTTP request - ignored in this implementation, but available to
 179  
      *                        subclasses for more complex construction building logic if necessary
 180  
      * @param configuredPerms any url-specific permissions mapped to this filter in the URL rules mappings.
 181  
      * @param action          the application-friendly action (verb) resolved based on the HTTP Method name.
 182  
      * @return a collection of String permissions with which to perform a permission check to determine if the filter
 183  
      *         will allow the request to continue.
 184  
      */
 185  
     protected String[] buildPermissions(HttpServletRequest request, String[] configuredPerms, String action) {
 186  0
         return buildPermissions(configuredPerms, action);
 187  
     }
 188  
 
 189  
     /**
 190  
      * Builds a new array of permission strings based on the original argument, appending the specified action verb
 191  
      * to each one per {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission} conventions.  The
 192  
      * built permission strings will be the ones used at runtime during the permission check that determines if filter
 193  
      * access should be allowed to continue or not.
 194  
      * <p/>
 195  
      * For example, if the {@code configuredPerms} argument contains the following 3 permission strings:
 196  
      * <p/>
 197  
      * <ol>
 198  
      * <li>permission:one</li>
 199  
      * <li>permission:two</li>
 200  
      * <li>permission:three</li>
 201  
      * </ol>
 202  
      * And the action is {@code read}, then the return value will be:
 203  
      * <ol>
 204  
      * <li>permission:one:read</li>
 205  
      * <li>permission:two:read</li>
 206  
      * <li>permission:three:read</li>
 207  
      * </ol>
 208  
      * per {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission} conventions.  Subclasses
 209  
      * are of course free to override this method or the
 210  
      * {@link #buildPermissions(javax.servlet.http.HttpServletRequest, String[], String) buildPermissions} request
 211  
      * variant for custom building logic or with different permission formats.
 212  
      *
 213  
      * @param configuredPerms list of configuredPerms to be converted.
 214  
      * @param action          the resolved action based on the request method to be appended to permission strings.
 215  
      * @return an array of permission strings with each element appended with the action.
 216  
      */
 217  
     protected String[] buildPermissions(String[] configuredPerms, String action) {
 218  16
         if (configuredPerms == null || configuredPerms.length <= 0 || !StringUtils.hasText(action)) {
 219  0
             return configuredPerms;
 220  
         }
 221  
 
 222  16
         String[] mappedPerms = new String[configuredPerms.length];
 223  
 
 224  
         // loop and append :action
 225  48
         for (int i = 0; i < configuredPerms.length; i++) {
 226  32
             mappedPerms[i] = configuredPerms[i] + ":" + action;
 227  
         }
 228  
 
 229  16
         if (log.isTraceEnabled()) {
 230  16
             StringBuilder sb = new StringBuilder();
 231  48
             for (int i = 0; i < mappedPerms.length; i++) {
 232  32
                 if (i > 0) {
 233  16
                     sb.append(", ");
 234  
                 }
 235  32
                 sb.append(mappedPerms[i]);
 236  
             }
 237  16
             log.trace("MAPPED '{}' action to permission(s) '{}'", action, sb);
 238  
         }
 239  
 
 240  16
         return mappedPerms;
 241  
     }
 242  
 
 243  
     /**
 244  
      * Resolves an 'application friendly' action verb based on the {@code HttpServletRequest}'s method, appends that
 245  
      * action to each configured permission (the {@code mappedValue} argument is a {@code String[]} array), and
 246  
      * delegates the permission check for the newly constructed permission(s) to the superclass
 247  
      * {@link PermissionsAuthorizationFilter#isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
 248  
      * implementation to perform the actual permission check.
 249  
      *
 250  
      * @param request     the inbound {@code ServletRequest}
 251  
      * @param response    the outbound {@code ServletResponse}
 252  
      * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
 253  
      * @return {@code true} if the request should proceed through the filter normally, {@code false} if the
 254  
      *         request should be processed by this filter's
 255  
      *         {@link #onAccessDenied(ServletRequest,ServletResponse,Object)} method instead.
 256  
      * @throws IOException
 257  
      */
 258  
     @Override
 259  
     public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
 260  0
         String[] perms = (String[]) mappedValue;
 261  
         // append the http action to the end of the permissions and then back to super
 262  0
         String action = getHttpMethodAction(request);
 263  0
         String[] resolvedPerms = buildPermissions(perms, action);
 264  0
         return super.isAccessAllowed(request, response, resolvedPerms);
 265  
     }
 266  
 }