Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/securityfilter/filter/SecurityFilter.java


1   /*
2    * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.21 2003/07/07 13:12:57 maxcooper Exp $
3    * $Revision: 1.21 $
4    * $Date: 2003/07/07 13:12:57 $
5    *
6    * ====================================================================
7    * The SecurityFilter Software License, Version 1.1
8    *
9    * (this license is derived and fully compatible with the Apache Software
10   * License - see http://www.apache.org/LICENSE.txt)
11   *
12   * Copyright (c) 2002 SecurityFilter.org. All rights reserved.
13   *
14   * Redistribution and use in source and binary forms, with or without
15   * modification, are permitted provided that the following conditions
16   * are met:
17   *
18   * 1. Redistributions of source code must retain the above copyright
19   *    notice, this list of conditions and the following disclaimer.
20   *
21   * 2. Redistributions in binary form must reproduce the above copyright
22   *    notice, this list of conditions and the following disclaimer in
23   *    the documentation and/or other materials provided with the
24   *    distribution.
25   *
26   * 3. The end-user documentation included with the redistribution,
27   *    if any, must include the following acknowledgment:
28   *       "This product includes software developed by
29   *        SecurityFilter.org (http://www.securityfilter.org/)."
30   *    Alternately, this acknowledgment may appear in the software itself,
31   *    if and wherever such third-party acknowledgments normally appear.
32   *
33   * 4. The name "SecurityFilter" must not be used to endorse or promote
34   *    products derived from this software without prior written permission.
35   *    For written permission, please contact license@securityfilter.org .
36   *
37   * 5. Products derived from this software may not be called "SecurityFilter",
38   *    nor may "SecurityFilter" appear in their name, without prior written
39   *    permission of SecurityFilter.org.
40   *
41   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44   * DISCLAIMED.  IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR
45   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52   * SUCH DAMAGE.
53   * ====================================================================
54   */
55  
56  package org.securityfilter.filter;
57  
58  import org.securityfilter.authenticator.*;
59  import org.securityfilter.config.*;
60  import org.securityfilter.realm.SecurityRealmInterface;
61  
62  import javax.servlet.*;
63  import javax.servlet.http.*;
64  import java.io.IOException;
65  import java.net.URL;
66  import java.security.Principal;
67  import java.util.*;
68  
69  /**
70   * SecurityFilter provides authentication and authorization services.
71   *
72   * @author Max Cooper (max@maxcooper.com)
73   * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net)
74   * @author Torgeir Veimo (torgeir@pobox.com)
75   * @version $Revision: 1.21 $ $Date: 2003/07/07 13:12:57 $
76   */
77  public class SecurityFilter implements Filter {
78     public static final String CONFIG_FILE_KEY = "config";
79     public static final String DEFAULT_CONFIG_FILE = "/WEB-INF/securityfilter-config.xml";
80     public static final String VALIDATE_KEY = "validate";
81  
82     public static final String TRUE = "true";
83  
84     public static final String ALREADY_PROCESSED = SecurityFilter.class.getName() + ".ALREADY_PROCESSED";
85  
86     public static final String SAVED_REQUEST_URL = SecurityFilter.class.getName() + ".SAVED_REQUEST_URL";
87     public static final String SAVED_REQUEST = SecurityFilter.class.getName() + ".SAVED_REQUEST";
88  
89     protected FilterConfig config;
90     protected SecurityRealmInterface realm;
91     protected List patternList;
92     protected URLPatternFactory patternFactory;
93     protected Authenticator authenticator;
94  
95     /**
96      * Perform filtering operation, and optionally pass the request down the chain.
97      *
98      * @param request the current request
99      * @param response the current response
100     * @param chain request handler chain
101     * @exception IOException
102     * @exception ServletException
103     */
104    public void doFilter(
105       ServletRequest request,
106       ServletResponse response,
107       FilterChain chain
108    ) throws IOException, ServletException {
109 
110       HttpServletRequest hReq = (HttpServletRequest) request;
111       HttpServletResponse hRes = (HttpServletResponse) response;
112       SecurityRequestWrapper wrappedRequest;
113 
114       // if the request has already been processed by the filter, pass it through unchecked
115       if (!TRUE.equals(request.getAttribute(ALREADY_PROCESSED))) {
116          // set an attribute on this request to indicate that it has already been processed
117          request.setAttribute(ALREADY_PROCESSED, TRUE);
118 
119          // get a URLPatternMatcher to use for this thread
120          URLPatternMatcher patternMatcher = patternFactory.createURLPatternMatcher();
121 
122          // get saved request, if any (returns null if not applicable)
123          SavedRequest savedRequest = getSavedRequest(hReq);
124 
125          // wrap request
126          wrappedRequest = new SecurityRequestWrapper(hReq, savedRequest, realm, authenticator.getAuthMethod());
127 
128          URLPattern match = null;
129          try {
130             // check if this request includes login info
131             if (authenticator.processLogin(wrappedRequest, hRes)) {
132                return;
133             }
134 
135             // match the url if the authenticator does not indicate that security should be bypassed
136             if (!authenticator.bypassSecurityForThisRequest(wrappedRequest, patternMatcher)) {
137                // check if request matches security constraint
138                match = matchPattern(wrappedRequest.getMatchableURL(), wrappedRequest.getMethod(), patternMatcher);
139             }
140          } catch (Exception e) {
141             throw new ServletException("Error matching patterns", e);
142          }
143 
144          // check security constraint, if any
145          if (match != null) {
146             // TODO: check user-data-constraint
147             // check auth constraint
148             AuthConstraint authConstraint = match.getSecurityConstraint().getAuthConstraint();
149             if (authConstraint != null) {
150                Collection roles = authConstraint.getRoles();
151                Principal principal = wrappedRequest.getUserPrincipal();
152                // if roles is empty, access will be blocked no matter who the user is, so skip the login
153                // todo: do we still need this DUMMY_TOKEN check for BASIC auth?
154                if (!roles.isEmpty() && principal == null /* && hReq.getSession().getAttribute(DUMMY_TOKEN) == null */) {
155                   // user needs to be authenticated
156                   authenticator.showLogin(hReq, hRes);
157                   return;
158                } else {
159                   boolean authorized = false;
160                   for (Iterator i = roles.iterator(); i.hasNext() && !authorized;) {
161                      String role = (String) i.next();
162                      // TODO: if *, do you need to have at least one role to be authorized?
163                      // if so, we need to iterate through the roles defined in config file or change the
164                      // realm inteface to get a list of roles for the user (both solutions are undesireable)
165                      if ("*".equals(role) || realm.isUserInRole(principal, role)) {
166                         authorized = true;
167                      }
168                   }
169                   if (!authorized) {
170                      // user does not meet role constraint
171                      hRes.sendError(HttpServletResponse.SC_FORBIDDEN);
172                      return;
173                   }
174                }
175             }
176          }
177          // send wrapped request down the chain
178          request = wrappedRequest;
179       }
180 
181       // pass the request down the filter chain
182       chain.doFilter(request, response);
183    }
184 
185    /**
186     * Initialize the SecurityFilter.
187     *
188     * @param config filter configuration object
189     */
190    public void init(FilterConfig config) throws ServletException {
191       this.config = config;
192       try {
193          // parse config file
194 
195          // config file name
196          String configFile = config.getInitParameter(CONFIG_FILE_KEY);
197          if (configFile == null) {
198             configFile = DEFAULT_CONFIG_FILE;
199          }
200          URL configURL = config.getServletContext().getResource(configFile);
201 
202          // validate config file?
203          boolean validate = TRUE.equalsIgnoreCase(config.getInitParameter(VALIDATE_KEY));
204 
205          SecurityConfig securityConfig = new SecurityConfig(validate);
206          securityConfig.loadConfig(configURL);
207 
208          // get the realm
209          realm = securityConfig.getRealm();
210 
211          // create an Authenticator
212          authenticator = AuthenticatorFactory.createAuthenticator(config, securityConfig);
213 
214          // create pattern list
215          patternFactory = new URLPatternFactory();
216          patternList = new ArrayList();
217          int order = 1;
218          List constraints = securityConfig.getSecurityConstraints();
219          for (Iterator cIter = constraints.iterator(); cIter.hasNext();) {
220             SecurityConstraint constraint = (SecurityConstraint) cIter.next();
221             for (Iterator rIter = constraint.getWebResourceCollections().iterator(); rIter.hasNext();) {
222                WebResourceCollection resourceCollection = (WebResourceCollection) rIter.next();
223                for (Iterator pIter = resourceCollection.getURLPatterns().iterator(); pIter.hasNext();) {
224                   URLPattern pattern = patternFactory.createURLPattern(
225                      (String) pIter.next(),
226                      constraint,
227                      resourceCollection,
228                      order++
229                   );
230                   patternList.add(pattern);
231                }
232             }
233          }
234          Collections.sort(patternList);
235 
236       } catch (java.io.IOException ioe) {
237          System.err.println("unable to parse input: " + ioe);
238       } catch (org.xml.sax.SAXException se) {
239          System.err.println("unable to parse input: " + se);
240       } catch (Exception e) {
241          System.err.println("invalid regular expression pattern: " + e);
242       }
243    }
244 
245    /**
246     * Destroy the filter, releasing resources.
247     */
248    public void destroy() {
249    }
250 
251    /**
252     * Find a match for the requested pattern & method, if any.
253     *
254     * @param pattern the pattern to match
255     * @param httpMethod the HTTP Method to match
256     * @param matcher the thread-local URLPatternMatcher object
257     * @return the matching URLPattern object, or null if there is no match.
258     */
259    protected URLPattern matchPattern(String pattern, String httpMethod, URLPatternMatcher matcher) throws Exception {
260       // PERFORMANCE IMPROVEMENT OPPORTUNITY: cahce pattern matches
261       Iterator i = patternList.iterator();
262       while (i.hasNext()) {
263          URLPattern urlPattern = (URLPattern) i.next();
264          if (matcher.match(pattern, httpMethod, urlPattern)) {
265             return urlPattern;
266          }
267       }
268       return null;
269    }
270 
271    /**
272     * If this request matches the one we saved, return the SavedRequest and remove it from the session.
273     *
274     * @param request the current request
275     * @return usually null, but when the request matches the posted URL that initiated the login sequence a
276     * SavedRequest object is returned.
277     */
278    protected SavedRequest getSavedRequest(HttpServletRequest request) {
279       HttpSession session = request.getSession();
280       String savedURL = (String) session.getAttribute(SecurityFilter.SAVED_REQUEST_URL);
281       if (savedURL != null && savedURL.equals(getSaveableURL(request))) {
282          // this is a request for the request that caused the login,
283          // get the SavedRequest from the session
284          SavedRequest saved = (SavedRequest) session.getAttribute(SecurityFilter.SAVED_REQUEST);
285          // remove the saved request info from the session
286          session.removeAttribute(SecurityFilter.SAVED_REQUEST_URL);
287          session.removeAttribute(SecurityFilter.SAVED_REQUEST);
288          // and return the SavedRequest
289          return saved;
290       } else {
291          return null;
292       }
293    }
294 
295    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
296    // The following methods are provided as static utilities for use by SecurityFilter and other classes.             //
297    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
298 
299    /**
300     * Get the URL to continue to after successful login. This may be the SAVED_REQUEST_URL if the authorization
301     * sequence was initiated by the filter, or the default URL (as specified in the config file) if a login
302     * request was spontaneously submitted.
303     *
304     * @param request the current request
305     */
306    public static String getContinueToURL(HttpServletRequest request) {
307       return (String) request.getSession().getAttribute(SAVED_REQUEST_URL);
308    }
309 
310    /**
311     * Save request information to re-use when the user is successfully authenticated.
312     *
313     * @param request the current request
314     */
315    public static void saveRequestInformation(HttpServletRequest request) {
316       HttpSession session = request.getSession();
317       session.setAttribute(SecurityFilter.SAVED_REQUEST_URL, getSaveableURL(request));
318       session.setAttribute(SecurityFilter.SAVED_REQUEST, new SavedRequest(request));
319    }
320 
321    /**
322     * Return a URL suitable for saving or matching against a saved URL.<p>
323     *
324     * This is the whole URL, plus the query string.
325     *
326     * @param request the request to construct a saveable URL for
327     */
328    private static String getSaveableURL(HttpServletRequest request) {
329       StringBuffer saveableURL = null;
330       try {
331          saveableURL = request.getRequestURL();
332       } catch (NoSuchMethodError e) {
333          saveableURL = getRequestURL(request);
334       }
335       // fix the protocol
336       fixProtocol(saveableURL, request);
337       // add the query string, if any
338       String queryString = request.getQueryString();
339       if (queryString != null) {
340          saveableURL.append("?" + queryString);
341       }
342       return saveableURL.toString();
343    }
344 
345    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
346    // The following methods are provided for compatibility with various app servers.                                  //
347    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
348 
349    /**
350     * Set the filter configuration, included for WebLogic 6 compatibility.
351     *
352     * @param config filter configuration object
353     */
354    public void setFilterConfig(FilterConfig config) throws ServletException {
355       init(config);
356    }
357 
358    /**
359     * Get the filter config object, included for WebLogic 6 compatibility.
360     */
361    public FilterConfig getFilterConfig() {
362       return config;
363    }
364 
365    /**
366     * Get the requestURL.
367     * This method is called when the app server fails to implement HttpServletRequest.getRequestURL().
368     * Orion 1.5.2 is one such server.
369     */
370    private static StringBuffer getRequestURL(HttpServletRequest request) {
371       String protocol = request.getProtocol();
372       int port = request.getServerPort();
373       String portString = ":" + port;
374 
375       // todo: this needs to be tested to see if it still an issue; remove it if it is not needed
376       // Set the portString to the empty string if the requrest came in on the default port.
377       // This will keep Netscape from dropping the session, which happens when the port is added where it wasn't before.
378       // This is not perfect, but most requests on the default ports will not be made with an explicit port number.
379       if (protocol.equals("HTTP/1.1")) {
380          if (!request.isSecure()) {
381             if (port == 80) {
382                portString = "";
383             }
384          } else {
385             if (port == 443) {
386                portString = "";
387             }
388          }
389       }
390 
391       // construct the saveable URL string
392       return new StringBuffer(protocol + request.getServerName() + portString + request.getRequestURI());
393    }
394 
395    /**
396     * Fix the protocol portion of an absolute url. Often, the protocol will be http: even for https: requests.
397     *
398     * todo: needs testing to make sure this is proper in all circumstances
399     *
400     * @param url
401     * @param request
402     */
403    private static void fixProtocol(StringBuffer url, HttpServletRequest request) {
404       // fix protocol, if needed (since HTTP is the same regardless of whether it runs on TCP or on SSL/TCP)
405       if (
406          request.getProtocol().equals("HTTP/1.1")
407          && request.isSecure()
408          && url.toString().startsWith("http://")
409       ) {
410          url.replace(0, 4, "https");
411       }
412    }
413 }
414 
415 // ------------------------------------------------------------------------
416 // EOF