refactored BASIC and FORM authentication functionality out of SecurityFilter and...
authormaxcooper <maxcooper>
Mon, 7 Jul 2003 13:12:56 +0000 (13:12 +0000)
committermaxcooper <maxcooper>
Mon, 7 Jul 2003 13:12:56 +0000 (13:12 +0000)
FORM based authentication is tested and functional, will test BASIC and make any needed fixes soon

src/share/org/securityfilter/authenticator/Authenticator.java [new file with mode: 0644]
src/share/org/securityfilter/authenticator/AuthenticatorFactory.java [new file with mode: 0644]
src/share/org/securityfilter/authenticator/BasicAuthenticator.java [new file with mode: 0644]
src/share/org/securityfilter/authenticator/FormAuthenticator.java [new file with mode: 0644]
src/share/org/securityfilter/filter/SecurityFilter.java
src/share/org/securityfilter/filter/SecurityRequestWrapper.java

diff --git a/src/share/org/securityfilter/authenticator/Authenticator.java b/src/share/org/securityfilter/authenticator/Authenticator.java
new file mode 100644 (file)
index 0000000..05b7150
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/Authenticator.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $
+ * $Revision: 1.1 $
+ * $Date: 2003/07/07 13:12:56 $
+ *
+ * ====================================================================
+ * The SecurityFilter Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2002 SecurityFilter.org. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        SecurityFilter.org (http://www.securityfilter.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The name "SecurityFilter" must not be used to endorse or promote
+ *    products derived from this software without prior written permission.
+ *    For written permission, please contact license@securityfilter.org .
+ *
+ * 5. Products derived from this software may not be called "SecurityFilter",
+ *    nor may "SecurityFilter" appear in their name, without prior written
+ *    permission of SecurityFilter.org.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.securityfilter.authenticator;
+
+import org.securityfilter.config.SecurityConfig;
+import org.securityfilter.filter.*;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.http.*;
+import java.io.IOException;
+
+/**
+ * Authenticator - interface for a SecurityFilter authenticator module. Implementations correspond to an implementation
+ * method, such as FORM or BASIC (others are possible).
+ *
+ * @author Max Cooper (max@maxcooper.com)
+ * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $
+ */
+public interface Authenticator {
+
+   /**
+    * Initialize this Authenticator.
+    *
+    * @param filterConfig
+    * @param securityConfig
+    */
+   public void init(FilterConfig filterConfig, SecurityConfig securityConfig) throws Exception;
+
+   /**
+    * Get the auth method string for this authentication scheme.
+    *
+    * @return the auth method string for this Authenticator
+    */
+   public String getAuthMethod();
+
+   /**
+    * Process any login information that was included in the request, if any.
+    * Returns true if SecurityFilter should abort further processing after the method completes (for example, if a
+    * redirect was sent as part of the login processing).
+    *
+    * @param request
+    * @param response
+    * @return true if the filter should return after this method ends, false otherwise
+    */
+   public boolean processLogin(SecurityRequestWrapper request, HttpServletResponse response) throws Exception;
+
+   /**
+    * Show the login interface.
+    *
+    * @param request
+    * @param response
+    */
+   public void showLogin(HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+   /**
+    * Return true if security checks should be bypassed for this request.
+    *
+    * Example: for FORM based authentication, the login and error pages should always be viewable without being
+    * authenticated, even if they would otherwise be blocked by a security constraint.
+    *
+    * @param request
+    * @return true if security should be bypassed, false otherwise
+    */
+   public boolean bypassSecurityForThisRequest(
+      SecurityRequestWrapper request,
+      URLPatternMatcher patternMatcher
+   ) throws Exception;
+}
+
+// ------------------------------------------------------------------------
+// EOF
diff --git a/src/share/org/securityfilter/authenticator/AuthenticatorFactory.java b/src/share/org/securityfilter/authenticator/AuthenticatorFactory.java
new file mode 100644 (file)
index 0000000..1c2f814
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/AuthenticatorFactory.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $
+ * $Revision: 1.1 $
+ * $Date: 2003/07/07 13:12:56 $
+ *
+ * ====================================================================
+ * The SecurityFilter Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2002 SecurityFilter.org. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        SecurityFilter.org (http://www.securityfilter.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The name "SecurityFilter" must not be used to endorse or promote
+ *    products derived from this software without prior written permission.
+ *    For written permission, please contact license@securityfilter.org .
+ *
+ * 5. Products derived from this software may not be called "SecurityFilter",
+ *    nor may "SecurityFilter" appear in their name, without prior written
+ *    permission of SecurityFilter.org.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.securityfilter.authenticator;
+
+import org.securityfilter.config.SecurityConfig;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * AuthenticatorFactory - this class will create Authenticator instance.
+ *
+ * It is designed to be easy to extend to add more Authenticator implementations, or to allow custom Authenticators to
+ * be specified in the config file and created here in this factory class.
+ *
+ * @author Max Cooper (max@maxcooper.com)
+ * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $
+ */
+public class AuthenticatorFactory {
+
+   /**
+    * Create an Authenticator based on the specified configuration information.
+    *
+    * @param filterConfig
+    * @param securityConfig
+    * @return
+    * @throws Exception
+    */
+   public static Authenticator createAuthenticator(
+      FilterConfig filterConfig,
+      SecurityConfig securityConfig
+   ) throws Exception {
+      Authenticator authenticator = null;
+
+      String authMethod = securityConfig.getAuthMethod();
+      if (HttpServletRequest.FORM_AUTH.equals(authMethod)) {
+         // FORM
+         authenticator = new FormAuthenticator();
+      } else if (HttpServletRequest.BASIC_AUTH.equals(authMethod)) {
+         // BASIC
+         authenticator = new BasicAuthenticator();
+      } else {
+         throw new Exception("No Authenticator available for auth method: " + authMethod);
+      }
+      authenticator.init(filterConfig, securityConfig);
+      return authenticator;
+   }
+}
+
+// ------------------------------------------------------------------------
+// EOF
diff --git a/src/share/org/securityfilter/authenticator/BasicAuthenticator.java b/src/share/org/securityfilter/authenticator/BasicAuthenticator.java
new file mode 100644 (file)
index 0000000..7eb43fe
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/BasicAuthenticator.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $
+ * $Revision: 1.1 $
+ * $Date: 2003/07/07 13:12:56 $
+ *
+ * ====================================================================
+ * The SecurityFilter Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2002 SecurityFilter.org. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        SecurityFilter.org (http://www.securityfilter.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The name "SecurityFilter" must not be used to endorse or promote
+ *    products derived from this software without prior written permission.
+ *    For written permission, please contact license@securityfilter.org .
+ *
+ * 5. Products derived from this software may not be called "SecurityFilter",
+ *    nor may "SecurityFilter" appear in their name, without prior written
+ *    permission of SecurityFilter.org.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.securityfilter.authenticator;
+
+import org.apache.commons.codec.binary.Base64;
+import org.securityfilter.config.SecurityConfig;
+import org.securityfilter.filter.*;
+import org.securityfilter.realm.SecurityRealmInterface;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.http.*;
+import java.io.IOException;
+import java.security.Principal;
+
+/**
+ * BasicAuthenticator - authenticator implementation for the BASIC auth method.
+ *
+ * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net)
+ * @author Max Cooper (max@maxcooper.com)
+ * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $
+ */
+public class BasicAuthenticator implements Authenticator {
+   public static final String BASIC_WINDOW_SHOWN = "basic_window_shown";
+   public static final String LOGIN_ATTEMPTS = "loginAttempts";
+   protected static final String DUMMY_TOKEN = "dummyToken";
+
+   public static final Base64 base64Helper = new Base64();
+
+   protected String tooManyIncorrectLogins;
+
+   protected SecurityRealmInterface realm;
+
+   /**
+    * Initialize this Authenticator.
+    *
+    * @param filterConfig
+    * @param securityConfig
+    */
+   public void init(FilterConfig filterConfig, SecurityConfig securityConfig) throws Exception {
+      realm = securityConfig.getRealm();
+      tooManyIncorrectLogins = "Sorry you are having problems logging in, please try again";
+   }
+
+   /**
+    * Returns BASIC as the authentication method.
+    *
+    * @return BASIC
+    */
+   public String getAuthMethod() {
+      return HttpServletRequest.BASIC_AUTH;
+   }
+
+   /**
+    * Process any login information that was included in the request, if any.
+    * Returns true if SecurityFilter should abort further processing after the method completes (for example, if a
+    * redirect was sent as part of the login processing).
+    *
+    * @param request
+    * @param response
+    * @return true if the filter should return after this method ends, false otherwise
+    */
+   public boolean processLogin(SecurityRequestWrapper request, HttpServletResponse response) throws Exception {
+      if (basicAuthentication(request)) {
+         request.getSession().removeAttribute(BASIC_WINDOW_SHOWN);
+         String username = parseUsername(request.getHeader("Authorization"));
+         String password = parsePassword(request.getHeader("Authorization"));
+         Principal principal = realm.authenticate(username, password);
+         if (principal != null) {
+            // login successful
+            // invalidate old session if the user was already authenticated
+            // NOTE: we may want to check if the user re-authenticated as the same user, currently
+            // the session will be invalidated even if the user authenticates as the same user.
+            request.setUserPrincipal(principal);
+            String continueToURL = SecurityFilter.getContinueToURL(request);
+            request.getSession().setAttribute(DUMMY_TOKEN, DUMMY_TOKEN);
+            // This is the url that the user was initially accessing before being prompted for login.
+            response.sendRedirect(response.encodeRedirectURL(continueToURL));
+         } else {
+            // login failed
+            // show the basic authentication window again.
+            showLogin(request.getCurrentRequest(), response);
+         }
+      }
+      return false;
+   }
+
+   /**
+    * Returns true if this request includes BASIC auth info.
+    *
+    * @param request
+    * @return
+    */
+   private boolean basicAuthentication(HttpServletRequest request) {
+      return (
+         request.getSession().getAttribute(BASIC_WINDOW_SHOWN) != null
+         && request.getHeader("Authorization") != null
+      );
+   }
+
+   /**
+    * Parse the username out of the BASIC authorization header string.
+    * @param authorization
+    * @return
+    */
+   private String parseUsername(String authorization) {
+      String unencoded = decodeBasicAuthorizationString(authorization);
+      if (unencoded == null) {
+         return null;
+      } else {
+         int colon = unencoded.indexOf(':');
+         if (colon < 0) {
+            return null;
+         } else {
+            return unencoded.substring(0, colon).trim();
+         }
+      }
+   }
+
+   /**
+    * Parse the password out of the BASIC authorization header string.
+    * @param authorization
+    * @return
+    */
+   private String parsePassword(String authorization) {
+      String unencoded = decodeBasicAuthorizationString(authorization);
+      if (unencoded == null) {
+         return null;
+      } else {
+         int colon = unencoded.indexOf(':');
+         if (colon < 0) {
+            return (null);
+         } else {
+            return unencoded.substring(colon + 1).trim();
+         }
+      }
+   }
+
+   /**
+    * Decode the BASIC authorization string.
+    *
+    * @param authorization
+    * @return
+    */
+   private String decodeBasicAuthorizationString(String authorization) {
+      if (authorization == null || !authorization.toLowerCase().startsWith("basic ")) {
+         return null;
+      } else {
+         authorization = authorization.substring(6).trim();
+         // Decode and parse the authorization credentials
+         return new String(base64Helper.decodeBase64(authorization.getBytes()));
+      }
+   }
+
+   /**
+    * Show the login page.
+    *
+    * @param request the current request
+    * @param response the current response
+    */
+   public void showLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
+      // save this request
+      SecurityFilter.saveRequestInformation(request);
+
+      // redirect to login page
+      request.getSession().setAttribute(BASIC_WINDOW_SHOWN, "shown");
+      int loginAttempts = 1;
+      if (request.getSession().getAttribute(LOGIN_ATTEMPTS) != null) {
+         loginAttempts = ((Integer) request.getSession().getAttribute(LOGIN_ATTEMPTS)).intValue();
+         loginAttempts += 1;
+      }
+      // todo: we can put some useful message here, perhaps a internationlizable format of message.
+      String loginAttemptMessage = "Login attempt number " + loginAttempts;
+      String logo;
+      if (loginAttempts <= 3) {
+         String realm = String.valueOf(Math.random());
+         if (loginAttempts < 2) {
+            logo = "Basic Auth with Security Filter";
+         } else {
+            logo = loginAttemptMessage;
+         }
+         String blankLine = "                                                                   ";
+         logo = blankLine + blankLine + blankLine + logo + blankLine + blankLine;
+         System.err.println("response.setHeader \"WWW-Authenticate\", \"BASIC realm=\"" + logo + realm + "\"");
+         response.setHeader("WWW-Authenticate", "BASIC realm=\"" + realm + logo + "\"");
+         response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+         request.getSession().setAttribute(LOGIN_ATTEMPTS, new Integer(loginAttempts));
+      } else {
+         request.getSession().removeAttribute(LOGIN_ATTEMPTS);
+         response.sendError(HttpServletResponse.SC_UNAUTHORIZED, tooManyIncorrectLogins);
+      }
+   }
+
+   /**
+    * All requests should be subject to security checking for BASIC authentication.
+    *
+    * @param request
+    * @return always false -- check all requests
+    */
+   public boolean bypassSecurityForThisRequest(SecurityRequestWrapper request, URLPatternMatcher patternMatcher) {
+      return false;
+   }
+}
+
+// ------------------------------------------------------------------------
+// EOF
diff --git a/src/share/org/securityfilter/authenticator/FormAuthenticator.java b/src/share/org/securityfilter/authenticator/FormAuthenticator.java
new file mode 100644 (file)
index 0000000..7b4204a
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/FormAuthenticator.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $
+ * $Revision: 1.1 $
+ * $Date: 2003/07/07 13:12:56 $
+ *
+ * ====================================================================
+ * The SecurityFilter Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2002 SecurityFilter.org. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        SecurityFilter.org (http://www.securityfilter.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The name "SecurityFilter" must not be used to endorse or promote
+ *    products derived from this software without prior written permission.
+ *    For written permission, please contact license@securityfilter.org .
+ *
+ * 5. Products derived from this software may not be called "SecurityFilter",
+ *    nor may "SecurityFilter" appear in their name, without prior written
+ *    permission of SecurityFilter.org.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.securityfilter.authenticator;
+
+import org.securityfilter.config.SecurityConfig;
+import org.securityfilter.filter.*;
+import org.securityfilter.realm.SecurityRealmInterface;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.http.*;
+import java.io.IOException;
+import java.security.Principal;
+
+/**
+ * FormAuthenticator - authenticator implementation for the FORM auth method.
+ *
+ * @author Max Cooper (max@maxcooper.com)
+ * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $
+ */
+public class FormAuthenticator implements Authenticator {
+
+   public static final String LOGIN_SUBMIT_PATTERN_KEY = "loginSubmitPattern";
+   public static final String DEFAULT_LOGIN_SUBMIT_PATTERN = "/j_security_check";
+   protected String loginSubmitPattern;
+
+   protected static final String FORM_USERNAME = "j_username";
+   protected static final String FORM_PASSWORD = "j_password";
+
+   protected String loginPage;
+   protected URLPattern loginPagePattern;
+
+   protected String errorPage;
+   protected URLPattern errorPagePattern;
+
+   protected String defaultPage;
+
+   protected SecurityRealmInterface realm;
+
+   /**
+    * Initilize this Authenticator.
+    *
+    * @param filterConfig
+    * @param securityConfig
+    */
+   public void init(FilterConfig filterConfig, SecurityConfig securityConfig) throws Exception {
+
+      realm = securityConfig.getRealm();
+
+      // login submit pattern
+      loginSubmitPattern = filterConfig.getInitParameter(LOGIN_SUBMIT_PATTERN_KEY);
+      if (loginSubmitPattern == null) {
+         loginSubmitPattern = DEFAULT_LOGIN_SUBMIT_PATTERN;
+      }
+
+      // default page
+      defaultPage = securityConfig.getDefaultPage();
+
+      URLPatternFactory patternFactory = new URLPatternFactory();
+
+      // login page
+      loginPage = securityConfig.getLoginPage();
+      loginPagePattern = patternFactory.createURLPattern(loginPage, null, null, 0);
+
+      // error page
+      errorPage = securityConfig.getErrorPage();
+      errorPagePattern = patternFactory.createURLPattern(errorPage, null, null, 0);
+   }
+
+   /**
+    * Returns FORM as the authentication method.
+    *
+    * @return FORM
+    */
+   public String getAuthMethod() {
+      return HttpServletRequest.FORM_AUTH;
+   }
+
+   /**
+    * Process any login information that was included in the request, if any.
+    * Returns true if SecurityFilter should abort further processing after the method completes (for example, if a
+    * redirect was sent as part of the login processing).
+    *
+    * @param request
+    * @param response
+    * @return true if the filter should return after this method ends, false otherwise
+    */
+   public boolean processLogin(SecurityRequestWrapper request, HttpServletResponse response) throws Exception {
+      if (request.getMatchableURL().endsWith(loginSubmitPattern)) {
+         String username = request.getParameter(FORM_USERNAME);
+         String password = request.getParameter(FORM_PASSWORD);
+         Principal principal = realm.authenticate(username, password);
+         if (principal != null) {
+            // login successful
+            // invalidate old session if the user was already authenticated
+            // NOTE: we may want to check if the user re-authenticated as the same user, currently
+            // the session will be invalidated even if the user authenticates as the same user.
+            request.setUserPrincipal(principal);
+            String continueToURL = getContinueToURL(request);
+            // This is the url that the user was initially accessing before being prompted for login.
+            response.sendRedirect(response.encodeRedirectURL(continueToURL));
+         } else {
+            // login failed
+            // set response status and forward to error page
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            request.getRequestDispatcher(errorPage).forward(request, response);
+         }
+         return true;
+      }
+      return false;
+   }
+
+   /**
+    * Show the login page.
+    *
+    * @param request the current request
+    * @param response the current response
+    */
+   public void showLogin(
+      HttpServletRequest request,
+      HttpServletResponse response
+   ) throws IOException {
+      // save this request
+      SecurityFilter.saveRequestInformation(request);
+
+      // redirect to login page
+      response.sendRedirect(request.getContextPath() + loginPage);
+      return;
+   }
+
+   /**
+    * FormAuthenticator has a special case where the user should be sent to a default page if the user
+    * spontaneously submits a login request.
+    *
+    * @param request
+    * @return a URL to send the user to after logging in
+    */
+   private String getContinueToURL(HttpServletRequest request) {
+      String savedURL = SecurityFilter.getContinueToURL(request);
+      if (savedURL != null) {
+         return savedURL;
+      } else {
+         return request.getContextPath() + defaultPage;
+      }
+   }
+
+   /**
+    * The login and error pages should be viewable, even if they would otherwise be blocked by a security constraint.
+    *
+    * @param request
+    * @return
+    */
+   public boolean bypassSecurityForThisRequest(
+      SecurityRequestWrapper request,
+      URLPatternMatcher patternMatcher
+   ) throws Exception {
+      String requestURL = request.getMatchableURL();
+      return patternMatcher.match(requestURL, loginPagePattern) || patternMatcher.match(requestURL, errorPagePattern);
+   }
+}
+
+// ------------------------------------------------------------------------
+// EOF
index 9e0ae99..11a80e8 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.20 2003/07/07 04:18:29 maxcooper Exp $
- * $Revision: 1.20 $
- * $Date: 2003/07/07 04:18:29 $
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.21 2003/07/07 13:12:57 maxcooper Exp $
+ * $Revision: 1.21 $
+ * $Date: 2003/07/07 13:12:57 $
  *
  * ====================================================================
  * The SecurityFilter Software License, Version 1.1
 
 package org.securityfilter.filter;
 
-import org.apache.commons.codec.binary.Base64;
-import org.securityfilter.config.AuthConstraint;
-import org.securityfilter.config.SecurityConfig;
-import org.securityfilter.config.SecurityConstraint;
-import org.securityfilter.config.WebResourceCollection;
+import org.securityfilter.authenticator.*;
+import org.securityfilter.config.*;
 import org.securityfilter.realm.SecurityRealmInterface;
 
 import javax.servlet.*;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
+import javax.servlet.http.*;
 import java.io.IOException;
 import java.net.URL;
 import java.security.Principal;
@@ -77,47 +72,25 @@ import java.util.*;
  * @author Max Cooper (max@maxcooper.com)
  * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net)
  * @author Torgeir Veimo (torgeir@pobox.com)
- * @version $Revision: 1.20 $ $Date: 2003/07/07 04:18:29 $
+ * @version $Revision: 1.21 $ $Date: 2003/07/07 13:12:57 $
  */
 public class SecurityFilter implements Filter {
    public static final String CONFIG_FILE_KEY = "config";
    public static final String DEFAULT_CONFIG_FILE = "/WEB-INF/securityfilter-config.xml";
-
    public static final String VALIDATE_KEY = "validate";
 
-   public static final String LOGIN_SUBMIT_PATTERN_KEY = "loginSubmitPattern";
-   public static final String DEFAULT_LOGIN_SUBMIT_PATTERN = "/j_security_check";
-   protected String loginSubmitPattern;
-
    public static final String TRUE = "true";
 
-   public static final String SAVED_REQUEST_URL = SecurityFilter.class.getName() + ".SAVED_REQUEST_URL";
-   public static final String SAVED_REQUEST = SecurityFilter.class.getName() + ".SAVED_REQUEST";
    public static final String ALREADY_PROCESSED = SecurityFilter.class.getName() + ".ALREADY_PROCESSED";
 
-   public static final String BASIC_WINDOW_SHOWN = "basic_window_shown";
-   public static final String LOGIN_ATTEMPTS = "loginAttempts";
-
-   protected static final String DUMMY_TOKEN = "dummyToken";
-
-   protected static final String FORM_USERNAME = "j_username";
-   protected static final String FORM_PASSWORD = "j_password";
-
-   public static final Base64 base64Helper = new Base64();
+   public static final String SAVED_REQUEST_URL = SecurityFilter.class.getName() + ".SAVED_REQUEST_URL";
+   public static final String SAVED_REQUEST = SecurityFilter.class.getName() + ".SAVED_REQUEST";
 
    protected FilterConfig config;
    protected SecurityRealmInterface realm;
-   protected String loginPage;
-   protected URLPattern loginPagePattern;
-   protected String errorPage;
-   protected URLPattern errorPagePattern;
-   protected String defaultPage;
-   protected URLPatternFactory patternFactory;
    protected List patternList;
-
-   protected String authMethod;
-   protected boolean basic = false;
-   protected String tooManyIncorrectLogins;
+   protected URLPatternFactory patternFactory;
+   protected Authenticator authenticator;
 
    /**
     * Perform filtering operation, and optionally pass the request down the chain.
@@ -146,37 +119,23 @@ public class SecurityFilter implements Filter {
          // get a URLPatternMatcher to use for this thread
          URLPatternMatcher patternMatcher = patternFactory.createURLPatternMatcher();
 
-         // get the part of the URL to check for matches
-         String requestURL = getMatchableURL(hReq);
-
          // get saved request, if any (returns null if not applicable)
          SavedRequest savedRequest = getSavedRequest(hReq);
 
          // wrap request
-         wrappedRequest = new SecurityRequestWrapper(hReq, realm, savedRequest);
+         wrappedRequest = new SecurityRequestWrapper(hReq, savedRequest, realm, authenticator.getAuthMethod());
 
          URLPattern match = null;
          try {
-            // check if this is a login form submittal
-            if (basicAuthentication(hReq)) {
-               hReq.getSession().removeAttribute(BASIC_WINDOW_SHOWN);
-               processLogin(wrappedRequest, hRes);
-               return;
-            } else if (requestURL.endsWith(loginSubmitPattern)) {
-               processLogin(wrappedRequest, hRes);
+            // check if this request includes login info
+            if (authenticator.processLogin(wrappedRequest, hRes)) {
                return;
             }
 
-            // only check the request for a security constraint match if it doesn't
-            // match the login page or error page patterns -- this allows requests for the
-            // login page and error pages to be viewed even when their URLs would otherwise
-            // be subject to a security constraint
-            if (
-               !patternMatcher.match(requestURL, loginPagePattern)
-               && !patternMatcher.match(requestURL, errorPagePattern)
-            ) {
+            // match the url if the authenticator does not indicate that security should be bypassed
+            if (!authenticator.bypassSecurityForThisRequest(wrappedRequest, patternMatcher)) {
                // check if request matches security constraint
-               match = matchPattern(requestURL, wrappedRequest.getMethod(), patternMatcher);
+               match = matchPattern(wrappedRequest.getMatchableURL(), wrappedRequest.getMethod(), patternMatcher);
             }
          } catch (Exception e) {
             throw new ServletException("Error matching patterns", e);
@@ -191,9 +150,10 @@ public class SecurityFilter implements Filter {
                Collection roles = authConstraint.getRoles();
                Principal principal = wrappedRequest.getUserPrincipal();
                // if roles is empty, access will be blocked no matter who the user is, so skip the login
-               if (!roles.isEmpty() && principal == null && hReq.getSession().getAttribute(DUMMY_TOKEN) == null) {
+               // todo: do we still need this DUMMY_TOKEN check for BASIC auth?
+               if (!roles.isEmpty() && principal == null /* && hReq.getSession().getAttribute(DUMMY_TOKEN) == null */) {
                   // user needs to be authenticated
-                  showLogin(hReq, hRes);
+                  authenticator.showLogin(hReq, hRes);
                   return;
                } else {
                   boolean authorized = false;
@@ -222,14 +182,6 @@ public class SecurityFilter implements Filter {
       chain.doFilter(request, response);
    }
 
-   private boolean basicAuthentication(HttpServletRequest hReq) {
-      return (
-         authMethod.equalsIgnoreCase("basic")
-         && hReq.getSession().getAttribute(BASIC_WINDOW_SHOWN) != null
-         && hReq.getHeader("Authorization") != null
-      );
-   }
-
    /**
     * Initialize the SecurityFilter.
     *
@@ -238,41 +190,29 @@ public class SecurityFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {
       this.config = config;
       try {
-         patternFactory = new URLPatternFactory();
-
          // parse config file
+
          // config file name
          String configFile = config.getInitParameter(CONFIG_FILE_KEY);
          if (configFile == null) {
             configFile = DEFAULT_CONFIG_FILE;
          }
          URL configURL = config.getServletContext().getResource(configFile);
+
          // validate config file?
-         String validate = config.getInitParameter(VALIDATE_KEY);
-         // login submit pattern
-         loginSubmitPattern = config.getInitParameter(LOGIN_SUBMIT_PATTERN_KEY);
-         if (loginSubmitPattern == null) {
-            loginSubmitPattern = DEFAULT_LOGIN_SUBMIT_PATTERN;
-         }
-         SecurityConfig securityConfig = new SecurityConfig(TRUE.equalsIgnoreCase(validate));
+         boolean validate = TRUE.equalsIgnoreCase(config.getInitParameter(VALIDATE_KEY));
+
+         SecurityConfig securityConfig = new SecurityConfig(validate);
          securityConfig.loadConfig(configURL);
 
-         // get config values
+         // get the realm
          realm = securityConfig.getRealm();
-         defaultPage = securityConfig.getDefaultPage();
-
-         // get login and error page patterns
-         loginPage = securityConfig.getLoginPage();
-         loginPagePattern = patternFactory.createURLPattern(loginPage, null, null, 0);
-         errorPage = securityConfig.getErrorPage();
-         errorPagePattern = patternFactory.createURLPattern(errorPage, null, null, 0);
-         authMethod = securityConfig.getAuthMethod();
-         // todo: support DIGEST and CERT authentication schemes.
-         if (authMethod.equalsIgnoreCase(HttpServletRequest.BASIC_AUTH)) {
-            basic = true;
-         }
+
+         // create an Authenticator
+         authenticator = AuthenticatorFactory.createAuthenticator(config, securityConfig);
 
          // create pattern list
+         patternFactory = new URLPatternFactory();
          patternList = new ArrayList();
          int order = 1;
          List constraints = securityConfig.getSecurityConstraints();
@@ -309,22 +249,6 @@ public class SecurityFilter implements Filter {
    }
 
    /**
-    * Set the filter configuration, included for WebLogic 6 compatibility.
-    *
-    * @param config filter configuration object
-    */
-   public void setFilterConfig(FilterConfig config) throws ServletException {
-      init(config);
-   }
-
-   /**
-    * Get the filter config object, included for WebLogic 6 compatibility.
-    */
-   public FilterConfig getFilterConfig() {
-      return config;
-   }
-
-   /**
     * Find a match for the requested pattern & method, if any.
     *
     * @param pattern the pattern to match
@@ -345,131 +269,6 @@ public class SecurityFilter implements Filter {
    }
 
    /**
-    * Show the login page.
-    *
-    * @param request the current request
-    * @param response the current response
-    * @exception IOException
-    * @exception ServletException
-    */
-   protected void showLogin(
-      HttpServletRequest request,
-      HttpServletResponse response
-   ) throws IOException, ServletException {
-      // save this request
-      saveRequestInformation(request);
-
-      // redirect to login page
-      if (basic) {
-         request.getSession().setAttribute(BASIC_WINDOW_SHOWN, "shown");
-         int loginAttempts = 1;
-         if (request.getSession().getAttribute(LOGIN_ATTEMPTS) != null) {
-            loginAttempts = ((Integer) request.getSession().getAttribute(LOGIN_ATTEMPTS)).intValue();
-            loginAttempts += 1;
-         }
-         // todo: we can put some useful message here, perhaps a internationlizable format of message.
-         tooManyIncorrectLogins = "Sorry you are having problems logging in, please try again";
-         String loginAttemptMessage = "Login attempt number " + loginAttempts;
-         String logo;
-         if (loginAttempts <= 3) {
-            String realm = String.valueOf(Math.random());
-            if (loginAttempts < 2) {
-               logo = "Basic Auth with Security Filter";
-            } else {
-               logo = loginAttemptMessage;
-            }
-            String blankLine = "                                                                   ";
-            logo = blankLine + blankLine + blankLine + logo + blankLine + blankLine;
-            System.err.println("response.setHeader \"WWW-Authenticate\", \"BASIC realm=\"" + logo + realm + "\"");
-            response.setHeader("WWW-Authenticate", "BASIC realm=\"" + realm + logo + "\"");
-            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-            request.getSession().setAttribute(LOGIN_ATTEMPTS, new Integer(loginAttempts));
-         } else {
-            request.getSession().removeAttribute(LOGIN_ATTEMPTS);
-            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, tooManyIncorrectLogins);
-         }
-      } else {
-         response.sendRedirect(request.getContextPath() + loginPage);
-         return;
-      }
-   }
-
-   /**
-    * Process a login form submittal.
-    *
-    * @param request the current request
-    * @param response the current response
-    * @exception IOException
-    * @exception ServletException
-    */
-   protected void processLogin(
-      SecurityRequestWrapper request,
-      HttpServletResponse response
-   ) throws IOException, ServletException {
-      String username;
-      String password;
-      if (basic) {
-         username = parseUsername(request.getHeader("Authorization"));
-         password = parsePassword(request.getHeader("Authorization"));
-      } else {
-         username = request.getParameter(FORM_USERNAME);
-         password = request.getParameter(FORM_PASSWORD);
-      }
-      Principal principal = realm.authenticate(username, password);
-      if (principal != null) {
-         // login successful
-         // invalidate old session if the user was already authenticated
-         // NOTE: we may want to check if the user re-authenticated as the same user, currently
-         // the session will be invalidated even if the user authenticates as the same user.
-         request.setUserPrincipal(principal);
-         String continueToURL = getContinueToURL(request);
-         if (basic) {
-            // what does this do?
-            request.getSession().setAttribute(DUMMY_TOKEN, DUMMY_TOKEN);
-         }
-         // This is the url that the user was initially accessing before being prompted for login.
-         response.sendRedirect(response.encodeRedirectURL(continueToURL));
-      } else {
-         // login failed
-         if (!basic) {
-            // set response status and forward to error page
-            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-            request.getRequestDispatcher(errorPage).forward(request, response);
-         } else {
-            // show the basic authentication window again.
-            showLogin(request.getCurrentRequest(), response);
-         }
-      }
-   }
-
-   /**
-    * Get the URL to continue to after successful login. This may be the SAVED_REQUEST_URL if the authorization
-    * sequence was initiated by the filter, or the default URL (as specified in the config file) if a login
-    * request was spontaneously submitted.
-    *
-    * @param request the current request
-    */
-   protected String getContinueToURL(HttpServletRequest request) {
-      String savedURL = (String) request.getSession().getAttribute(SAVED_REQUEST_URL);
-      if (savedURL != null) {
-         return savedURL;
-      } else {
-         return request.getContextPath() + defaultPage;
-      }
-   }
-
-   /**
-    * Save request information to re-use when the user is successfully authenticated.
-    *
-    * @param request the current request
-    */
-   protected void saveRequestInformation(HttpServletRequest request) {
-      HttpSession session = request.getSession();
-      session.setAttribute(SecurityFilter.SAVED_REQUEST_URL, getSaveableURL(request));
-      session.setAttribute(SecurityFilter.SAVED_REQUEST, new SavedRequest(request));
-   }
-
-   /**
     * If this request matches the one we saved, return the SavedRequest and remove it from the session.
     *
     * @param request the current request
@@ -493,23 +292,30 @@ public class SecurityFilter implements Filter {
       }
    }
 
+   /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   // The following methods are provided as static utilities for use by SecurityFilter and other classes.             //
+   /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
    /**
-    * Return a URL that can be matched against security URL patterns.<p>
+    * Get the URL to continue to after successful login. This may be the SAVED_REQUEST_URL if the authorization
+    * sequence was initiated by the filter, or the default URL (as specified in the config file) if a login
+    * request was spontaneously submitted.
     *
-    * This is the part after the contextPath, with the pathInfo, but without the query string.<br>
-    * http://server:8080/contextPath/someURL.jsp?param=value becomes /someURL.jsp
+    * @param request the current request
+    */
+   public static String getContinueToURL(HttpServletRequest request) {
+      return (String) request.getSession().getAttribute(SAVED_REQUEST_URL);
+   }
+
+   /**
+    * Save request information to re-use when the user is successfully authenticated.
     *
-    * @param request the request to construct a matchable URL for
+    * @param request the current request
     */
-   private String getMatchableURL(HttpServletRequest request) {
-      // extract the servlet path portion that needs to be checked
-      String matchableURL = request.getServletPath();
-      // add the pathInfo, as it needs to be part of the URL we check
-      String pathInfo = request.getPathInfo();
-      if (pathInfo != null) {
-         matchableURL = matchableURL + pathInfo;
-      }
-      return matchableURL;
+   public static void saveRequestInformation(HttpServletRequest request) {
+      HttpSession session = request.getSession();
+      session.setAttribute(SecurityFilter.SAVED_REQUEST_URL, getSaveableURL(request));
+      session.setAttribute(SecurityFilter.SAVED_REQUEST, new SavedRequest(request));
    }
 
    /**
@@ -519,32 +325,15 @@ public class SecurityFilter implements Filter {
     *
     * @param request the request to construct a saveable URL for
     */
-   private String getSaveableURL(HttpServletRequest request) {
-      String url = null;
+   private static String getSaveableURL(HttpServletRequest request) {
       StringBuffer saveableURL = null;
-      String protocol = request.getProtocol();
       try {
          saveableURL = request.getRequestURL();
       } catch (NoSuchMethodError e) {
-         // this is done to support app servers like orion 1.5.2
-         // which have not implemented the servlet 2.3 specification but have implemented the final draft of 2.3 spec.
-         if (protocol.equals("HTTP/1.1")) { // todo: provide support for ftp, webdav protocol among others.
-            if (request.isSecure()) {
-               protocol = "https://";
-            } else {
-               protocol = "http://";
-            }
-         }
-         url = protocol + request.getServerName() + ":" + request.getServerPort() + request.getRequestURI();
-      }
-      if (saveableURL == null) {
-         saveableURL = new StringBuffer(url);
-      } else {
-         // since HTTP is the same regardless of whether it runs on TCP or on SSL/TCP
-         if (protocol.equals("HTTP/1.1") && request.isSecure() && saveableURL.toString().startsWith("http://")) {
-            saveableURL.replace(0, 4, "https"); // todo: this needs to be tested extensively.
-         }
+         saveableURL = getRequestURL(request);
       }
+      // fix the protocol
+      fixProtocol(saveableURL, request);
       // add the query string, if any
       String queryString = request.getQueryString();
       if (queryString != null) {
@@ -553,57 +342,72 @@ public class SecurityFilter implements Filter {
       return saveableURL.toString();
    }
 
+   /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   // The following methods are provided for compatibility with various app servers.                                  //
+   /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
    /**
-    * Parse the username out of the BASIC authorization header string.
-    * @param authorization
-    * @return
+    * Set the filter configuration, included for WebLogic 6 compatibility.
+    *
+    * @param config filter configuration object
     */
-   private String parseUsername(String authorization) {
-      String unencoded = decodeBasicAuthorizationString(authorization);
-      if (unencoded == null) {
-         return null;
-      } else {
-         int colon = unencoded.indexOf(':');
-         if (colon < 0) {
-            return null;
-         } else {
-            return unencoded.substring(0, colon).trim();
-         }
-      }
+   public void setFilterConfig(FilterConfig config) throws ServletException {
+      init(config);
    }
 
    /**
-    * Parse the password out of the BASIC authorization header string.
-    * @param authorization
-    * @return
+    * Get the filter config object, included for WebLogic 6 compatibility.
     */
-   private String parsePassword(String authorization) {
-      String unencoded = decodeBasicAuthorizationString(authorization);
-      if (unencoded == null) {
-         return null;
-      } else {
-         int colon = unencoded.indexOf(':');
-         if (colon < 0) {
-            return (null);
+   public FilterConfig getFilterConfig() {
+      return config;
+   }
+
+   /**
+    * Get the requestURL.
+    * This method is called when the app server fails to implement HttpServletRequest.getRequestURL().
+    * Orion 1.5.2 is one such server.
+    */
+   private static StringBuffer getRequestURL(HttpServletRequest request) {
+      String protocol = request.getProtocol();
+      int port = request.getServerPort();
+      String portString = ":" + port;
+
+      // todo: this needs to be tested to see if it still an issue; remove it if it is not needed
+      // Set the portString to the empty string if the requrest came in on the default port.
+      // This will keep Netscape from dropping the session, which happens when the port is added where it wasn't before.
+      // This is not perfect, but most requests on the default ports will not be made with an explicit port number.
+      if (protocol.equals("HTTP/1.1")) {
+         if (!request.isSecure()) {
+            if (port == 80) {
+               portString = "";
+            }
          } else {
-            return unencoded.substring(colon + 1).trim();
+            if (port == 443) {
+               portString = "";
+            }
          }
       }
+
+      // construct the saveable URL string
+      return new StringBuffer(protocol + request.getServerName() + portString + request.getRequestURI());
    }
 
    /**
-    * Decode the BASIC authorization string.
+    * Fix the protocol portion of an absolute url. Often, the protocol will be http: even for https: requests.
+    *
+    * todo: needs testing to make sure this is proper in all circumstances
     *
-    * @param authorization
-    * @return
+    * @param url
+    * @param request
     */
-   private String decodeBasicAuthorizationString(String authorization) {
-      if (authorization == null || !authorization.toLowerCase().startsWith("basic ")) {
-         return null;
-      } else {
-         authorization = authorization.substring(6).trim();
-         // Decode and parse the authorization credentials
-         return new String(base64Helper.decodeBase64(authorization.getBytes()));
+   private static void fixProtocol(StringBuffer url, HttpServletRequest request) {
+      // fix protocol, if needed (since HTTP is the same regardless of whether it runs on TCP or on SSL/TCP)
+      if (
+         request.getProtocol().equals("HTTP/1.1")
+         && request.isSecure()
+         && url.toString().startsWith("http://")
+      ) {
+         url.replace(0, 4, "https");
       }
    }
 }
index 1803ed1..77077ef 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityRequestWrapper.java,v 1.8 2003/07/07 04:16:40 maxcooper Exp $
- * $Revision: 1.8 $
- * $Date: 2003/07/07 04:16:40 $
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityRequestWrapper.java,v 1.9 2003/07/07 13:12:57 maxcooper Exp $
+ * $Revision: 1.9 $
+ * $Date: 2003/07/07 13:12:57 $
  *
  * ====================================================================
  * The SecurityFilter Software License, Version 1.1
@@ -57,16 +57,11 @@ package org.securityfilter.filter;
 
 import org.securityfilter.realm.SecurityRealmInterface;
 
-import javax.servlet.ServletInputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.*;
+import javax.servlet.http.*;
 import java.io.IOException;
 import java.security.Principal;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 /**
  * SecurityRequestWrapper
@@ -74,7 +69,7 @@ import java.util.Map;
  * @author Max Cooper (max@maxcooper.com)
  * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net)
  * @author Torgeir Veimo (torgeir@pobox.com)
- * @version $Revision: 1.8 $ $Date: 2003/07/07 04:16:40 $
+ * @version $Revision: 1.9 $ $Date: 2003/07/07 13:12:57 $
  */
 public class SecurityRequestWrapper extends HttpServletRequestWrapper {
    public static final String PRINCIPAL_SESSION_KEY = SecurityRequestWrapper.class.getName() + ".PRINCIPAL";
@@ -82,6 +77,8 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper {
    private HttpServletRequest currentRequest;
    private SecurityRealmInterface realm;
    private SavedRequest savedRequest;
+   private String authType;
+   private String matchableURL;
 
        /**
     * Construct a new SecurityRequestWrapper.
@@ -91,11 +88,18 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper {
     * @param savedRequest SavedRequest (usually null, unless this is the request
     * that invoked the authorization sequence)
     */
-   public SecurityRequestWrapper(HttpServletRequest request, SecurityRealmInterface realm, SavedRequest savedRequest) {
+   public SecurityRequestWrapper(
+      HttpServletRequest request,
+      SavedRequest savedRequest,
+      SecurityRealmInterface realm,
+      String authType
+   ) {
       super(request);
       this.currentRequest = request;
       this.realm = realm;
       this.savedRequest = savedRequest;
+      this.authType = authType;
+      initMatchableURL();
    }
 
        /**
@@ -231,13 +235,11 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper {
    }
 
    /**
-    * Returns FORM_AUTH if the user has been authenticated, null otherwise.
-    *
-    * todo: add BASIC support
+    * Returns the auth type (e.g. FORM, BASIC, etc.).
     */
    public String getAuthType() {
       if (getUserPrincipal() != null) {
-         return HttpServletRequest.FORM_AUTH;
+         return authType;
       } else {
          return null;
       }
@@ -254,6 +256,29 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper {
          return super.getMethod();
       }
    }
+
+   /**
+    * Get a URL that can be matched against security URL patterns.
+    *
+    * This is the part after the contextPath, with the pathInfo, but without the query string.
+    * http://server:8080/contextPath/someURL.jsp?param=value becomes /someURL.jsp
+    */
+   public String getMatchableURL() {
+      return matchableURL;
+   }
+
+   /**
+    * Initilize the matchableURL.
+    */
+   private void initMatchableURL() {
+      // extract the servlet path portion that needs to be checked
+      matchableURL = currentRequest.getServletPath();
+      // add the pathInfo, as it needs to be part of the URL we check
+      String pathInfo = currentRequest.getPathInfo();
+      if (pathInfo != null) {
+         matchableURL = matchableURL + pathInfo;
+      }
+   }
 }
 
 // ------------------------------------------------------------------------