Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
HttpMethodPermissionFilter |
|
| 2.111111111111111;2.111 | ||||
HttpMethodPermissionFilter$HttpMethodAction |
|
| 2.111111111111111;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("user:read")</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("user:create")</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 | } |