1 /**
2 * Copyright 2005-2007 the original author or authors.
3 *
4 * Licensed under the Gnu General Pubic License, Version 2.0 (the
5 * "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.opensource.org/licenses/gpl-license.php
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the Gnu General Public License for more details.
14 */
15 package org.figure8.join.control;
16
17 import org.figure8.join.control.config.RoleConfig;
18 import org.figure8.join.control.config.AccessControlConfig;
19 import org.figure8.join.control.config.AccessControlRuleSet;
20 import org.figure8.join.control.config.ActionConstraintConfig;
21 import org.figure8.join.control.config.OperationConstraintConfig;
22 import org.figure8.join.services.cache.Cache;
23 import org.figure8.join.services.cache.EternalCacheKeys;
24 import org.figure8.join.services.cache.EternalCacheAccessor;
25 import org.figure8.join.businessobjects.security.Role;
26 import org.figure8.join.util.LogUtil;
27
28 import org.xml.sax.InputSource;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.digester.Digester;
31
32 import javax.servlet.Filter;
33 import javax.servlet.FilterChain;
34 import javax.servlet.FilterConfig;
35 import javax.servlet.ServletRequest;
36 import javax.servlet.ServletResponse;
37 import javax.servlet.ServletException;
38 import javax.servlet.UnavailableException;
39 import javax.servlet.ServletContext;
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpServletResponse;
42
43 import java.net.URL;
44 import java.io.InputStream;
45 import java.io.IOException;
46 import java.util.Iterator;
47 import java.util.Collection;
48 /**
49 * Servlet filter that checks is user associated with request we are
50 * processing is allowed to call the specified action or business operation
51 * with the action.
52 * @author <a href="mailto:laurent.broudoux@free.fr">Laurent Broudoux</a>
53 * @version $Revision: 1.2 $
54 */
55 public class AccessControlFilter implements Filter{
56
57
58
59 /** Get a commons logger. */
60 private static final Log log = LogUtil.getLog(AccessControlFilter.class);
61
62
63
64
65 /** The context relative name of the XML configuration file. */
66 private String config;
67 /** The filter configuration object we are associated with. */
68 private FilterConfig filterConfig;
69 /** The Access control configuration object (retrieved from servlet context). */
70 private AccessControlConfig accessConfig;
71
72
73
74
75 /** Creates a new AccessControlFilter instance. */
76 public AccessControlFilter(){
77 }
78
79
80
81
82 /**
83 * Called by the web container to indicate to a filter that is being placed
84 * into service. This method stores filterConfig into inner attribute and
85 * creates an AccessControlConfig object by "digestering" the join-access-control.xml
86 * configuration file.
87 * @param filterConfig The configuration of this filter
88 * @throws ServletException if app servlet context is not correctly initialized
89 */
90 public void init(FilterConfig filterConfig) throws ServletException{
91 this.filterConfig = filterConfig;
92
93
94 config = filterConfig.getInitParameter("config");
95 log.info("Initializing AccessControlFilter with: " + config);
96
97 InputStream input = null;
98 try{
99 accessConfig = new AccessControlConfig();
100
101 Digester digester = initDigester();
102 digester.push(accessConfig);
103
104 URL url = filterConfig.getServletContext().getResource(config);
105 InputSource is = new InputSource(url.toExternalForm());
106 input = filterConfig.getServletContext().getResourceAsStream(config);
107 is.setByteStream(input);
108
109 digester.parse(is);
110 }
111 catch (Exception e){
112
113 log.error("AccessControl configuration file cannot be parsed !");
114 log.error("Here's the detailed message: " + e.getMessage());
115 throw new UnavailableException("Access Control configuration file cannot be parsed.");
116 }
117 finally{
118
119 if (input != null){
120 try {input.close();}
121 catch (Exception ee) {
122 }
123 }
124 log.info("Filter initialized. AccessControlFilter is enable");
125 }
126
127 /**
128 * Apply the Access Control filter to rh request we are processing. This
129 * involves using the AccessControlConfig object created during initilization
130 * to check if there's constraints on the current requested action and
131 * business operation.<br/>
132 * If user is allowed to call action or business operation, <code>chain.doFilter()
133 * </code> is called. Else, user response is redierect to the page stored
134 * under "authorization.fails.url" servlet context attribute.
135 * @param request The servlet request we are processing
136 * @param response The servlet response we are creating
137 * @param chain The filter chain we are processing
138 * @exception IOException if an input/output error occurs
139 * @exception ServletException if a servlet error occurs
140 */
141 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
142 throws IOException, ServletException{
143
144
145 String path = extractPath((HttpServletRequest)request);
146 if (log.isDebugEnabled())
147 log.debug("Applying AccessControlFilter for path: " + path);
148
149 if (path != null){
150
151 ActionConstraintConfig constraint = accessConfig.findActionConstraint(path);
152
153 if (constraint != null){
154 boolean hasRole = false;
155
156 Collection roles = getRequiredRoles((HttpServletRequest)request, constraint);
157
158 if (roles != null && !roles.isEmpty()){
159
160 Cache roleCache = getRoleNameToRoleCache(filterConfig.getServletContext());
161 if (roleCache == null){
162
163 log.error("Application is in illegal state: Role cache is not present in context");
164 throw new IllegalStateException("App. is in illegal state: Role cache is not present in context");
165 }
166
167 UserContainer container = JoinAction.getUserContainer((HttpServletRequest)request);
168
169
170 Iterator iterator = roles.iterator();
171 while (iterator.hasNext() && !hasRole){
172 RoleConfig role = (RoleConfig)iterator.next();
173 Role cachedRole = (Role)roleCache.get(role.getName());
174
175 if (!role.hasResource())
176 hasRole = container.isUserInRole(cachedRole);
177 else if (!role.hasResourceParameter())
178 hasRole = container.isUserInRoleForResource(cachedRole, role.getResource());
179 else{
180 String resourceId = request.getParameter(role.getResourceParameter());
181 try{
182
183 if (resourceId != null && cachedRole.getPermissionResourceResolver() != null){
184 Object resource = cachedRole.getPermissionResourceResolver().getResource(resourceId);
185 hasRole = container.isUserInRoleForResource(cachedRole, resource);
186 }
187 }
188 catch (Throwable t){
189
190 log.warn("Throwable was caught when checking authorization for " + path);
191 log.warn("Maybe join-access-control.xml is misconfigured or cached roles out of synch ?...");
192 }
193 }
194 }
195 }
196 else{
197 if (log.isDebugEnabled())
198 log.debug("No required role for this path: allowing user to access");
199 hasRole = true;
200 }
201
202 if (!hasRole){
203 if (log.isDebugEnabled())
204 log.debug("Required role is not endorsed by user: forwarding to authorization failure page");
205
206 HttpServletRequest httpRequest = (HttpServletRequest)request;
207 HttpServletResponse httpResponse = (HttpServletResponse)response;
208 httpResponse.sendRedirect(httpRequest.getContextPath()
209 + filterConfig.getInitParameter("failurePage"));
210 }
211 }
212 }
213
214
215 chain.doFilter(request, response);
216 }
217
218 /**
219 * Called by the container to indicate to a filter that is being taken out
220 * of service. Just release handles on <b>filterConfig</b> and <b>accessConfig</b>.
221 */
222 public void destroy(){
223 this.filterConfig = null;
224 this.accessConfig = null;
225 }
226
227
228
229
230 /**
231 * Create and return a new Digester instance that has been initialized
232 * to process Join security control configuration files and configure a
233 * corresponding AccessControlConfig object (which must be pushed on to
234 * the evaluation stack before parsing begins).
235 */
236 protected Digester initDigester(){
237
238 Digester digester = new Digester();
239 digester.setValidating(false);
240 digester.setNamespaceAware(true);
241 digester.setUseContextClassLoader(true);
242 digester.addRuleSet(new AccessControlRuleSet());
243
244
245 return digester;
246 }
247
248 /**
249 * Identify and return the path component (from the request URI) that
250 * we will use to select an ActionConstraintConfig. If no such path can
251 * be identified, create an error response and return <code>null</code>.
252 * @param request The servlet request we are processing
253 */
254 protected String extractPath(HttpServletRequest request){
255
256 String path = request.getPathInfo();
257 if ((path != null) && (path.length() > 0))
258 return (path);
259
260
261 path = request.getServletPath();
262 int slash = path.lastIndexOf("/");
263 int period = path.lastIndexOf(".");
264 if ((period >= 0) && (period > slash))
265 path = path.substring(0, period);
266
267 return path;
268 }
269
270 /**
271 * Extract a collection of required roles from the ActionContraintConfig.
272 * Check if action constraint specifies constraints for the business operation
273 * requested.
274 * @param request The servlet request we are processing.
275 * @param constraint The security control constraints related to requested action.
276 * @return Collection of <code>org.figure8.join.security.RoleConfig</code>
277 */
278 protected Collection getRequiredRoles(HttpServletRequest request, ActionConstraintConfig constraint){
279
280 OperationConstraintConfig opConstraint = constraint.findOperationConstraint(request.getParameter("op"));
281 if (opConstraint != null)
282 return opConstraint.getRoles();
283
284
285 return constraint.getRoles();
286 }
287
288
289
290
291 /** Get the cache for roles or null if it is not available */
292 private Cache getRoleNameToRoleCache(ServletContext ctx){
293
294 EternalCacheAccessor cacheAccessor = (EternalCacheAccessor)ctx.getAttribute(EternalCacheAccessor.CONTEXT_KEY);
295
296 if (cacheAccessor != null){
297 return cacheAccessor.getCache(EternalCacheKeys.ROLE_NAME_TO_ROLE_KEY);
298 }
299 return null;
300 }
301 }