Added support for <user-data-constraint>, specifically <transport-guarantee>.
authorchris_schultz <chris_schultz>
Wed, 7 Nov 2007 17:22:38 +0000 (17:22 +0000)
committerchris_schultz <chris_schultz>
Wed, 7 Nov 2007 17:22:38 +0000 (17:22 +0000)
src/share/org/securityfilter/config/SecurityConfig.java
src/share/org/securityfilter/config/SecurityConstraint.java
src/share/org/securityfilter/config/UserDataConstraint.java [new file with mode: 0644]
src/share/org/securityfilter/filter/SecurityFilter.java
src/test/org/securityfilter/test/config/UserDataConfigTest.java [new file with mode: 0644]
src/test/org/securityfilter/test/http/form/TransportGuaranteeTest.java [new file with mode: 0644]
web/example/WEB-INF/securityfilter-config.xml
web/example/regularPage.jsp [new file with mode: 0644]

index 38f1db5..8f2c413 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/config/SecurityConfig.java,v 1.17 2004/01/26 10:54:38 maxcooper Exp $
- * $Revision: 1.17 $
- * $Date: 2004/01/26 10:54:38 $
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/config/SecurityConfig.java,v 1.18 2007/11/07 17:22:38 chris_schultz Exp $
+ * $Revision: 1.18 $
+ * $Date: 2007/11/07 17:22:38 $
  *
  * ====================================================================
  * The SecurityFilter Software License, Version 1.1
@@ -72,7 +72,7 @@ import java.util.*;
  * @author Max Cooper (max@maxcooper.com)
  * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net)
  * @author David Reed (dreed10@neo.rr.com)
- * @version $Revision: 1.17 $ $Date: 2004/01/26 10:54:38 $
+ * @version $Revision: 1.18 $ $Date: 2007/11/07 17:22:38 $
  */
 public class SecurityConfig {
 
@@ -267,6 +267,12 @@ public class SecurityConfig {
       return persistentLoginManager;
    }
 
+    public void loadConfig(URL configURL)
+        throws IOException, SAXException
+    {
+        loadConfig(new InputSource(configURL.openStream()));
+    }
+
    /**
     * Loads configuration from the specifued configURL.
     *
@@ -275,8 +281,9 @@ public class SecurityConfig {
     * @exception IOException if an input/output error occurs
     * @exception SAXException if the file has invalid xml syntax
     */
-   public void loadConfig(URL configURL) throws IOException, SAXException {
-
+    public void loadConfig(InputSource input)
+        throws IOException, SAXException
+    {
       securityConstraints = new ArrayList();
 
       Digester digester = new Digester();
@@ -351,6 +358,22 @@ public class SecurityConfig {
          0
       );
 
+      // user-data-constraint
+      digester.addObjectCreate(
+         "securityfilter-config/security-constraint/user-data-constraint",
+         "org.securityfilter.config.UserDataConstraint"
+      );
+      digester.addSetNext(
+         "securityfilter-config/security-constraint/user-data-constraint",
+         "setUserDataConstraint",
+         "org.securityfilter.config.UserDataConstraint"
+      );
+      digester.addCallMethod(
+         "securityfilter-config/security-constraint/user-data-constraint/transport-guarantee",
+         "setTransportGuarantee",
+         0
+      );
+
       // web-resource-collection
       digester.addObjectCreate(
          "securityfilter-config/security-constraint/web-resource-collection",
@@ -372,7 +395,6 @@ public class SecurityConfig {
          0
       );
 
-      InputSource input = new InputSource(configURL.openStream());
       digester.parse(input);
    }
 
index 8968581..31222a7 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/config/SecurityConstraint.java,v 1.6 2004/01/26 09:29:56 maxcooper Exp $
- * $Revision: 1.6 $
- * $Date: 2004/01/26 09:29:56 $
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/config/SecurityConstraint.java,v 1.7 2007/11/07 17:22:38 chris_schultz Exp $
+ * $Revision: 1.7 $
+ * $Date: 2007/11/07 17:22:38 $
  *
  * ====================================================================
  * The SecurityFilter Software License, Version 1.1
@@ -58,15 +58,19 @@ package org.securityfilter.config;
 import java.util.*;
 
 /**
- * SecurityConstraint
+ * SecurityConstraint models the &lt;security-constraint&gt; element
+ * in a web application's deployment descriptor.
  *
  * @author Max Cooper (max@maxcooper.com)
  * @author Torgeir Veimo (torgeir@pobox.com)
- * @version $Revision: 1.6 $ $Date: 2004/01/26 09:29:56 $
+ * @author Chris Schultz (chris@christopherschultz.net)
+ *
+ * @version $Revision: 1.7 $ $Date: 2007/11/07 17:22:38 $
  */
 public class SecurityConstraint {
    private List resourceCollections;
    private AuthConstraint authConstraint = null;
+    private UserDataConstraint userDataConstraint;
 
    /**
     * Constructor
@@ -111,6 +115,27 @@ public class SecurityConstraint {
    public AuthConstraint getAuthConstraint() {
       return authConstraint;
    }
+
+   /**
+    * Sets the UserDataConstraint for this SecurityConstraint.
+    *
+    * @param userDataConstraint The UserDataConstraint.
+    */
+    public void setUserDataConstraint(UserDataConstraint userDataConstraint)
+    {
+        this.userDataConstraint = userDataConstraint;
+    }
+
+   /**
+    * Gets the UserDataConstraint for this SecurityConstraint.
+    *
+    * @return The UserDataConstraint for this SecurityConstraint, or
+    *         <code>null</code> if none has been set.
+    */
+    public UserDataConstraint getUserDataConstraint()
+    {
+        return userDataConstraint;
+    }
 }
 
 // ------------------------------------------------------------------------
diff --git a/src/share/org/securityfilter/config/UserDataConstraint.java b/src/share/org/securityfilter/config/UserDataConstraint.java
new file mode 100644 (file)
index 0000000..c2c49e8
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/config/UserDataConstraint.java,v 1.1 2007/11/07 17:22:38 chris_schultz Exp $
+ * $Revision: 1.1 $
+ * $Date: 2007/11/07 17:22:38 $
+ *
+ * ====================================================================
+ * 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) 2007 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.config;
+
+/**
+ * UserDataConstraint models the &lt;user-data-constraint&gt; element
+ * in a web application's deployment descriptor.
+ *
+ * <pre>
+ * &lt;user-data-constraint&gt;
+ *    &lt;description^gt;This is the user data constraint.&lt;/description&gt;
+ *    &lt;transport-guarantee&gt;
+ *        <i><code>NONE</code>
+ *        or <code>INTEGRAL</code>
+ *        or <code>CONFIDENTIAL</code></i>
+ *    &lt;/transport-guarantee^gt;
+ * &gt;/user-data-constraint&gt;
+ * </pre>
+ * 
+ * @author Chris Schultz (chris@christopherschultz.net)
+ * @version $Revision: 1.1 $ $Date: 2007/11/07 17:22:38 $
+ */
+public class UserDataConstraint
+{
+    /**
+     * Constant for transport guarantee that indicates no guarantees.
+     *
+     * @see #setTransportGuarantee(String)
+     */
+    public static final String TRANSPORT_GUARANTEE_NONE = "NONE";
+
+    /**
+     * Constant for transport guarantee that indicates data sent between
+     * the client and server are sent in such a way that they cannot be changed
+     * in transit.
+     *
+     * @see #setTransportGuarantee(String)
+     */
+    public static final String TRANSPORT_GUARANTEE_INTEGRAL = "INTEGRAL";
+
+    /**
+     * Constant for transport guarantee that indicates data sent between
+     * the client and server are sent in such a way that they cannot be
+     * observed by third-parties while in transit.
+     *
+     * @see #setTransportGuarantee(String)
+     */
+    public static final String TRANSPORT_GUARANTEE_CONFIDENTIAL = "CONFIDENTIAL";
+
+    /**
+     * The transport-guarantee for this UserDataConstraint.
+     */
+    private String _transportGuarantee = TRANSPORT_GUARANTEE_NONE;
+
+    public UserDataConstraint()
+    {
+    }
+
+    /**
+     * Sets the transport-guarantee required by this UserDataConstraint.
+     *
+     * @param guarantee Valid values (case sensitive) are <code>NONE</code>,
+     *                  <code>INTEGRAL</code>, and <code>CONFIDENTIAL</code>.
+     *
+     * @throws IllegalArgumentException If <code>guarantee</code> is neither
+     *         <code>NONE</code> nor <code>INTEGRAL</code>
+     *         nor <code>CONFIDENTIAL</code>.
+     *
+     * @see #getTransportGuarantee()
+     * @see #TRANSPORT_GUARANTEE_NONE
+     * @see #TRANSPORT_GUARANTEE_INTEGRAL
+     * @see #TRANSPORT_GUARANTEE_CONFIDENTIAL
+     */
+    public void setTransportGuarantee(String guarantee)
+        throws IllegalArgumentException
+    {
+        if(null == guarantee)
+        {
+            _transportGuarantee = null;
+        }
+        else
+        {
+            guarantee = guarantee.trim();
+
+            if(!(TRANSPORT_GUARANTEE_NONE.equals(guarantee)
+                 || TRANSPORT_GUARANTEE_INTEGRAL.equals(guarantee)
+                 || TRANSPORT_GUARANTEE_CONFIDENTIAL.equals(guarantee)))
+                throw new IllegalArgumentException("Unknown transport guarantee: " + guarantee);
+
+            _transportGuarantee = guarantee;
+        }
+    }
+
+    /**
+     * Returns the transport guarantee for this UserDataConstraint.
+     *
+     * @see #setTransportGuarantee
+     */
+    public String getTransportGuarantee()
+    {
+        return _transportGuarantee;
+    }
+}
+
+// ----------------------------------------------------------------------------
+// EOF
index dbb5e4f..31ef9e2 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.24 2006/02/14 09:28:27 maxcooper Exp $
- * $Revision: 1.24 $
- * $Date: 2006/02/14 09:28:27 $
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.25 2007/11/07 17:22:38 chris_schultz Exp $
+ * $Revision: 1.25 $
+ * $Date: 2007/11/07 17:22:38 $
  *
  * ====================================================================
  * The SecurityFilter Software License, Version 1.1
@@ -72,12 +72,13 @@ 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.24 $ $Date: 2006/02/14 09:28:27 $
+ * @version $Revision: 1.25 $ $Date: 2007/11/07 17:22:38 $
  */
 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 SSL_PORT_INIT_PARAMETER_KEY = "ssl-redirect-port";
 
    public static final String TRUE = "true";
 
@@ -92,6 +93,11 @@ public class SecurityFilter implements Filter {
    protected URLPatternFactory patternFactory;
    protected Authenticator authenticator;
 
+    /**
+     * The port to be used when upgrading connections to HTTPS.
+     */
+    protected int sslPort = 443;
+
    /**
     * Perform filtering operation, and optionally pass the request down the chain.
     *
@@ -150,7 +156,44 @@ public class SecurityFilter implements Filter {
 
          // check security constraint, if any
          if (match != null) {
-            // TODO: check user-data-constraint
+
+             // Check user-data-constraint (transport-guarantee)
+             UserDataConstraint userDataConstraint
+                 = match.getSecurityConstraint().getUserDataConstraint();
+             if(null != userDataConstraint)
+             {
+                 String tg = userDataConstraint.getTransportGuarantee();
+                 if((tg.equals(UserDataConstraint.TRANSPORT_GUARANTEE_INTEGRAL)
+                     || tg.equals(UserDataConstraint.TRANSPORT_GUARANTEE_CONFIDENTIAL))
+                    && !request.isSecure())
+                 {
+                     // Servlet Specification Note:
+                     //
+                     // The Servlet Specification does not specify what ought
+                     // to be done when the connection must be "upgraded"
+                     // in order to satisfy the transport-guarantee.
+                     //
+                     // This implementation matches that of the Apache Tomcat
+                     // servlet container (as of version 5.5).
+
+                     // Switch from HTTP to HTTPS via redirection.
+                     if(0 <= sslPort)
+                     {
+                         String url = getSecureURL(wrappedRequest);
+
+                         hRes.sendRedirect(hRes.encodeRedirectURL(url));
+                     }
+                     else
+                     {
+                         // SSL port set to a negative: disable redirection.
+                         hRes.sendError(HttpServletResponse.SC_FORBIDDEN,
+                                        hReq.getRequestURI());
+                     }
+
+                     return;
+                 }
+             }
+
             // check auth constraint
             AuthConstraint authConstraint = match.getSecurityConstraint().getAuthConstraint();
             if (authConstraint != null) {
@@ -195,6 +238,39 @@ public class SecurityFilter implements Filter {
     */
    public void init(FilterConfig config) throws ServletException {
       this.config = config;
+
+      String sslPortString = config.getInitParameter(SSL_PORT_INIT_PARAMETER_KEY);
+      if(null != sslPortString)
+      {
+          try
+          {
+              this.sslPort = Integer.parseInt(sslPortString);
+
+              if(this.sslPort > 65535)
+              {
+                  System.err.println("ERROR: Invalid "
+                                     + SSL_PORT_INIT_PARAMETER_KEY
+                                     + ": " + sslPortString);
+                  System.err.println("WARN: SSL port redirection is disabled.");
+                  this.sslPort = -1;
+              }
+              else if(this.sslPort < 0)
+              {
+                  System.err.println("INFO: SSL port redirection is disabled (was set to " + this.sslPort + ")");
+              }
+          }
+          catch (NumberFormatException nfe)
+          {
+              System.err.println("ERROR: Invalid "
+                                 + SSL_PORT_INIT_PARAMETER_KEY
+                                 + ": " + sslPortString);
+              System.err.println("WARN: SSL port redirection is disabled.");
+              nfe.printStackTrace();
+
+              this.sslPort = -1;
+          }
+      }
+
       try {
          // parse config file
 
@@ -263,8 +339,8 @@ public class SecurityFilter implements Filter {
     * @param matcher the thread-local URLPatternMatcher object
     * @return the matching URLPattern object, or null if there is no match.
     */
-   protected URLPattern matchPattern(String pattern, String httpMethod, URLPatternMatcher matcher) throws Exception {
-      // PERFORMANCE IMPROVEMENT OPPORTUNITY: cahce pattern matches
+   protected URLPattern matchPattern(String pattern, String httpMethod, URLPatternMatcher matcher) {
+      // PERFORMANCE IMPROVEMENT OPPORTUNITY: cache pattern matches
       Iterator i = patternList.iterator();
       while (i.hasNext()) {
          URLPattern urlPattern = (URLPattern) i.next();
@@ -356,6 +432,29 @@ public class SecurityFilter implements Filter {
       return saveableURL.toString();
    }
 
+    protected String getSecureURL(HttpServletRequest request)
+    {
+        StringBuffer url = new StringBuffer();
+        url.append("https://")
+            .append(request.getServerName())
+            ;
+
+        if(443 != sslPort)
+            url.append(':')
+                .append(sslPort)
+                ;
+
+        url.append(request.getRequestURI());
+
+        String queryString = request.getQueryString();
+        if(null != queryString)
+            url.append('?')
+                .append(queryString)
+                ;
+
+        return url.toString();
+    }
+
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // The following methods are provided for compatibility with various app servers.                                  //
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/test/org/securityfilter/test/config/UserDataConfigTest.java b/src/test/org/securityfilter/test/config/UserDataConfigTest.java
new file mode 100644 (file)
index 0000000..4f2690f
--- /dev/null
@@ -0,0 +1,448 @@
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/test/org/securityfilter/test/config/UserDataConfigTest.java,v 1.1 2007/11/07 17:22:38 chris_schultz Exp $
+ * $Revision: 1.1 $
+ * $Date: 2007/11/07 17:22:38 $
+ *
+ * ====================================================================
+ * 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) 2007 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.test.config;
+
+import java.io.StringReader;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationHandler;
+import java.net.MalformedURLException;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.xml.sax.InputSource;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import org.securityfilter.filter.SecurityFilter;
+import org.securityfilter.config.SecurityConfig;
+import org.securityfilter.config.SecurityConstraint;
+import org.securityfilter.config.UserDataConstraint;
+
+import javax.servlet.FilterConfig;
+import java.util.Enumeration;
+import javax.servlet.ServletContext;
+
+/**
+ * UserDataConfigTests - tests to see that the transport guarantee
+ * configuration has been loaded correctly.
+ *
+ * @author Chris Schultz (chris@christopherschultz.net)
+ * @version $Revision: 1.1 $ $Date: 2007/11/07 17:22:38 $
+ */
+public class UserDataConfigTest
+    extends TestCase
+{
+    public UserDataConfigTest(String name)
+    {
+        super(name);
+    }
+
+    public void testNoUserDataConstraint()
+        throws Exception
+    {
+        String config =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+            + "\n"
+            + "<!DOCTYPE securityfilter-config PUBLIC\n"
+            + "   \""
+            + "-//SecurityFilter.org//DTD Security Filter Configuration 2.0//EN"
+            + "\"\n"
+            + "   \""
+            + "http://www.securityfilter.org/dtd/securityfilter-config_2_0.dtd"
+            + "\">"
+
+            + "<securityfilter-config>"
+            + "\n"
+            + "   <security-constraint>"
+            + "      <web-resource-collection>"
+            + "         <web-resource-name>Secure Page</web-resource-name>"
+            + "         <url-pattern>/securePage.jsp</url-pattern>"
+            + "      </web-resource-collection>"
+            + "      <auth-constraint>"
+            + "         <role-name>inthisrole</role-name>"
+            + "      </auth-constraint>"
+            + "   </security-constraint>"
+            + "\n"
+            + "   <login-config>"
+            + "      <auth-method>BASIC</auth-method>"
+            + "   </login-config>"
+            + "\n"
+            + "   <realm className=\"org.securityfilter.realm.catalina.CatalinaRealmAdapter\">"
+            + "   </realm>"
+            + "\n"
+            + "</securityfilter-config>"
+            ;
+
+        SecurityConfig sc = new SecurityConfig(true);
+
+        sc.loadConfig(new InputSource(new StringReader(config)));
+
+        List constraints = sc.getSecurityConstraints();
+
+        Assert.assertNotNull("Should have some security constraints",
+                             constraints);
+
+        Assert.assertEquals("Should have 1 security constraint.",
+                            1,
+                            constraints.size());
+
+        SecurityConstraint constraint
+            = (SecurityConstraint)constraints.get(0);
+
+        Assert.assertNull("Should not have a UserDataConstraint",
+                             constraint.getUserDataConstraint());
+    }
+
+    public void testTransportGuaranteeNone()
+        throws Exception
+    {
+        String config =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+            + "\n"
+            + "<!DOCTYPE securityfilter-config PUBLIC\n"
+            + "   \""
+            + "-//SecurityFilter.org//DTD Security Filter Configuration 2.0//EN"
+            + "\"\n"
+            + "   \""
+            + "http://www.securityfilter.org/dtd/securityfilter-config_2_0.dtd"
+            + "\">"
+
+            + "<securityfilter-config>"
+            + "\n"
+            + "   <security-constraint>"
+            + "      <web-resource-collection>"
+            + "         <web-resource-name>Secure Page</web-resource-name>"
+            + "         <url-pattern>/securePage.jsp</url-pattern>"
+            + "      </web-resource-collection>"
+            + "      <auth-constraint>"
+            + "         <role-name>inthisrole</role-name>"
+            + "      </auth-constraint>"
+            + "      <user-data-constraint>"
+            + "         <description>The user data constraint</description>"
+            + "         <transport-guarantee>NONE</transport-guarantee>"
+            + "      </user-data-constraint>"
+            + "   </security-constraint>"
+            + "\n"
+            + "   <login-config>"
+            + "      <auth-method>BASIC</auth-method>"
+            + "   </login-config>"
+            + "\n"
+            + "   <realm className=\"org.securityfilter.realm.catalina.CatalinaRealmAdapter\">"
+            + "   </realm>"
+            + "\n"
+            + "</securityfilter-config>"
+            ;
+
+        SecurityConfig sc = new SecurityConfig(true);
+
+        sc.loadConfig(new InputSource(new StringReader(config)));
+
+        List constraints = sc.getSecurityConstraints();
+
+        Assert.assertNotNull("Should have some security constraints",
+                             constraints);
+
+        Assert.assertEquals("Should have 1 security constraint.",
+                            1,
+                            constraints.size());
+
+        SecurityConstraint constraint
+            = (SecurityConstraint)constraints.get(0);
+
+        Assert.assertNotNull("Should have a UserDataConstraint",
+                             constraint.getUserDataConstraint());
+
+        Assert.assertEquals("Incorrect transport-guarantee",
+                            UserDataConstraint.TRANSPORT_GUARANTEE_NONE,
+                            constraint.getUserDataConstraint()
+                            .getTransportGuarantee());
+    }
+
+    public void testTransportGuaranteeNoneExtraSpaces()
+        throws Exception
+    {
+        String config =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+            + "\n"
+            + "<!DOCTYPE securityfilter-config PUBLIC\n"
+            + "   \""
+            + "-//SecurityFilter.org//DTD Security Filter Configuration 2.0//EN"
+            + "\"\n"
+            + "   \""
+            + "http://www.securityfilter.org/dtd/securityfilter-config_2_0.dtd"
+            + "\">"
+
+            + "<securityfilter-config>"
+            + "\n"
+            + "   <security-constraint>"
+            + "      <web-resource-collection>"
+            + "         <web-resource-name>Secure Page</web-resource-name>"
+            + "         <url-pattern>/securePage.jsp</url-pattern>"
+            + "      </web-resource-collection>"
+            + "      <auth-constraint>"
+            + "         <role-name>inthisrole</role-name>"
+            + "      </auth-constraint>"
+            + "      <user-data-constraint>"
+            + "         <description>The user data constraint</description>"
+            + "         <transport-guarantee>  NONE"
+            + "              </transport-guarantee>"
+            + "      </user-data-constraint>"
+            + "   </security-constraint>"
+            + "\n"
+            + "   <login-config>"
+            + "      <auth-method>BASIC</auth-method>"
+            + "   </login-config>"
+            + "\n"
+            + "   <realm className=\"org.securityfilter.realm.catalina.CatalinaRealmAdapter\">"
+            + "   </realm>"
+            + "\n"
+            + "</securityfilter-config>"
+            ;
+
+        SecurityConfig sc = new SecurityConfig(true);
+
+        sc.loadConfig(new InputSource(new StringReader(config)));
+
+        List constraints = sc.getSecurityConstraints();
+
+        Assert.assertNotNull("Should have some security constraints",
+                             constraints);
+
+        Assert.assertEquals("Should have 1 security constraint.",
+                            1,
+                            constraints.size());
+
+        SecurityConstraint constraint
+            = (SecurityConstraint)constraints.get(0);
+
+        Assert.assertNotNull("Should have a UserDataConstraint",
+                             constraint.getUserDataConstraint());
+
+        Assert.assertEquals("Incorrect transport-guarantee",
+                            UserDataConstraint.TRANSPORT_GUARANTEE_NONE,
+                            constraint.getUserDataConstraint()
+                            .getTransportGuarantee());
+    }
+
+    public void _testInvalidTransportGuarantee()
+        throws Exception
+    {
+        String config =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+            + "\n"
+            + "<!DOCTYPE securityfilter-config PUBLIC\n"
+            + "   \""
+            + "-//SecurityFilter.org//DTD Security Filter Configuration 2.0//EN"
+            + "\"\n"
+            + "   \""
+            + "http://www.securityfilter.org/dtd/securityfilter-config_2_0.dtd"
+            + "\">"
+
+            + "<securityfilter-config>"
+            + "\n"
+            + "   <security-constraint>"
+            + "      <web-resource-collection>"
+            + "         <web-resource-name>Secure Page</web-resource-name>"
+            + "         <url-pattern>/securePage.jsp</url-pattern>"
+            + "      </web-resource-collection>"
+            + "      <auth-constraint>"
+            + "         <role-name>inthisrole</role-name>"
+            + "      </auth-constraint>"
+            + "      <user-data-constraint>"
+            + "         <description>The user data constraint</description>"
+            + "         <transport-guarantee>INVALID</transport-guarantee>"
+            + "      </user-data-constraint>"
+            + "   </security-constraint>"
+            + "\n"
+            + "   <login-config>"
+            + "      <auth-method>BASIC</auth-method>"
+            + "   </login-config>"
+            + "\n"
+            + "   <realm className=\"org.securityfilter.realm.catalina.CatalinaRealmAdapter\">"
+            + "   </realm>"
+            + "\n"
+            + "</securityfilter-config>"
+            ;
+
+        SecurityConfig sc = new SecurityConfig(true);
+
+        try
+        {
+            sc.loadConfig(new InputSource(new StringReader(config)));
+
+            Assert.fail("INVALID transport guarantee should have failed.");
+        }
+        catch (org.xml.sax.SAXParseException spe)
+        {
+            // Expected behavior
+        }
+    }
+
+    //
+    // Make sure that the getSecureURL method is working.
+    //
+
+    private String getSecureURL(String url)
+        throws MalformedURLException, javax.servlet.ServletException
+    {
+        // TODO: This method is /horrible/. We should be using mock objects
+        // instead of monkeying-around with Proxies and stuff.
+        final java.net.URL theUrl = new java.net.URL(url);
+
+        InvocationHandler handler = new InvocationHandler() {
+                public Object invoke(Object o,
+                                     Method m,
+                                     Object[] args)
+                {
+                    if("getServerName".equals(m.getName()))
+                    {
+                        return theUrl.getHost();
+                    }
+                    else if("getRequestURI".equals(m.getName()))
+                    {
+                        return theUrl.getPath();
+                    }
+                    else if("getQueryString".equals(m.getName()))
+                    {
+                        return theUrl.getQuery();
+                    }
+                    else
+                        throw new IllegalStateException("Unexpected call to: "+ m);
+                }
+            }
+            ;
+
+        HttpServletRequest request = (HttpServletRequest)Proxy
+            .newProxyInstance(this.getClass().getClassLoader(),
+                              new Class[] { HttpServletRequest.class },
+                              handler);
+
+        return new SecurityFilter()
+            {
+                public String getSecureURL(HttpServletRequest request)
+                {
+                    return super.getSecureURL(request);
+                }
+            }.getSecureURL(request);
+    }
+
+    public void testGetSecureURL()
+        throws Exception
+    {
+        String url = "http://www.foo.com/path/resource?query=string&foo=bar";
+
+        String expected = url.replace("http://", "https://");
+
+        Assert.assertEquals(expected, getSecureURL(url));
+    }
+
+    public void testGetSecureURL_AlreadySecure()
+        throws Exception
+    {
+        String url = "https://www.foo.com/path/resource?query=string&foo=bar";
+
+        String expected = url;
+
+        Assert.assertEquals(expected, getSecureURL(url));
+    }
+
+    public void testGetSecureURL_Port()
+        throws Exception
+    {
+        String url = "http://www.foo.com:42/path/resource?query=string&foo=bar";
+
+        String expected = url.replace("http://", "https://")
+            .replace(":42", "");
+
+        Assert.assertEquals(expected, getSecureURL(url));
+    }
+
+    public void testGetSecureURL_NoQueryString()
+        throws Exception
+    {
+        String url = "http://www.foo.com:42/path/resource";
+
+        String expected = url.replace("http://", "https://")
+            .replace(":42", "");
+
+        Assert.assertEquals(expected, getSecureURL(url));
+    }
+
+    public void testGetSecureURL_NoSlash()
+        throws Exception
+    {
+        String url = "http://www.foo.com:42";
+
+        String expected = url.replace("http://", "https://")
+            .replace(":42", "");
+
+        Assert.assertEquals(expected, getSecureURL(url));
+    }
+
+    public void testGetSecureURL_NoPortNoSlash()
+        throws Exception
+    {
+        String url = "http://www.foo.com";
+
+        String expected = url.replace("http://", "https://");
+
+        Assert.assertEquals(expected, getSecureURL(url));
+    }
+}
diff --git a/src/test/org/securityfilter/test/http/form/TransportGuaranteeTest.java b/src/test/org/securityfilter/test/http/form/TransportGuaranteeTest.java
new file mode 100644 (file)
index 0000000..042183d
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/test/org/securityfilter/test/http/form/TransportGuaranteeTest.java,v 1.1 2007/11/07 17:22:39 chris_schultz Exp $
+ * $Revision: 1.1 $
+ * $Date: 2007/11/07 17:22:39 $
+ *
+ * ====================================================================
+ * 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) 2007 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.test.http.form;
+
+import com.meterware.httpunit.*;
+import junit.framework.Assert;
+import org.securityfilter.example.Constants;
+import org.securityfilter.test.http.TestBase;
+import org.securityfilter.authenticator.FormAuthenticator;
+
+/**
+ * ForwardAfterLoginTest - test forward-afterlogin behavior.
+ *
+ * @author Chris Schultz (chris@christopherschultz.net)
+ * @version $Revision: 1.1 $ $Date: 2007/11/07 17:22:39 $
+ */
+public class TransportGuaranteeTest
+    extends TestBase
+{
+    public TransportGuaranteeTest(String name) {
+        super(name);
+    }
+
+    public void testNoSSLUpgrade()
+        throws Exception
+    {
+        // request the login page
+        WebConversation session = new WebConversation();
+        // Disable automatic redirection so we can detect it ourselves.
+        session.getClientProperties().setAutoRedirect(false);
+        WebRequest request = new GetMethodWebRequest(baseUrl + "/regularPage.jsp");
+        WebResponse response = session.getResponse(request);
+
+
+        String location = response.getHeaderField("Location");
+
+        Assert.assertNull(location);
+    }
+
+    public void testIntegralRequirement()
+        throws Exception
+    {
+        // request the login page
+        WebConversation session = new WebConversation();
+        // Disable automatic redirection so we can detect it ourselves.
+        session.getClientProperties().setAutoRedirect(false);
+        WebRequest request = new GetMethodWebRequest(baseUrl + "/integral.jsp");
+        WebResponse response = session.getResponse(request);
+
+        String location = response.getHeaderField("Location");
+
+        Assert.assertNotNull(location);
+
+        // Remove any ";jsessionid" parameter.
+        if(0 <= location.indexOf(";jsessionid="))
+            location = location.replaceAll(";jsessionid=[a-fA-F0-9]+", "");
+
+        // Check for correct redirect (fully-qualified URL)
+        String url = baseUrl.replace("http://", "https://").replaceAll(":[0-9]+", "");
+
+        Assert.assertEquals(url + "/integral.jsp",
+                            location);
+    }
+
+    public void testConfidentialRequirement()
+        throws Exception
+    {
+        // request the login page
+        WebConversation session = new WebConversation();
+        // Disable automatic redirection so we can detect it ourselves.
+        session.getClientProperties().setAutoRedirect(false);
+        WebRequest request = new GetMethodWebRequest(baseUrl + "/confidential.html");
+        WebResponse response = session.getResponse(request);
+
+        String location = response.getHeaderField("Location");
+
+        Assert.assertNotNull(location);
+
+        // Remove any ";jsessionid" parameter.
+        if(0 <= location.indexOf(";jsessionid="))
+            location = location.replaceAll(";jsessionid=[a-fA-F0-9]+", "");
+
+        // Check for correct redirect (fully-qualified URL)
+        String url = baseUrl.replace("http://", "https://").replaceAll(":[0-9]+", "");
+
+        Assert.assertEquals(url + "/confidential.html",
+                            location);
+    }
+}
index bfeb602..863847e 100644 (file)
       </auth-constraint>
    </security-constraint>
 
+   <!-- Configuration for transport-guarantee tests -->
+   <security-constraint>
+      <web-resource-collection>
+         <web-resource-name>Regular Page</web-resource-name>
+         <url-pattern>/regularPage.jsp</url-pattern>
+      </web-resource-collection>
+      <user-data-constraint>
+         <description>No transport guarantee</description>
+         <transport-guarantee>NONE</transport-guarantee>
+      </user-data-constraint>
+   </security-constraint>
+
+   <security-constraint>
+      <web-resource-collection>
+         <web-resource-name>Integral</web-resource-name>
+         <url-pattern>/integral.jsp</url-pattern>
+      </web-resource-collection>
+      <user-data-constraint>
+         <description>INTEGRAL transport guarantee</description>
+         <transport-guarantee>INTEGRAL</transport-guarantee>
+      </user-data-constraint>
+   </security-constraint>
+
+   <security-constraint>
+      <web-resource-collection>
+         <web-resource-name>Confidential</web-resource-name>
+         <url-pattern>/confidential.html</url-pattern>
+      </web-resource-collection>
+      <user-data-constraint>
+         <description>CONFIDENTIAL transport guarantee</description>
+         <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+      </user-data-constraint>
+   </security-constraint>
+
    <login-config>
       <auth-method>FORM</auth-method>
       <form-login-config>
@@ -39,4 +73,4 @@
       <realm-param name="exampleProperty" value="it works!" />
    </realm>
 
-</securityfilter-config>
\ No newline at end of file
+</securityfilter-config>
diff --git a/web/example/regularPage.jsp b/web/example/regularPage.jsp
new file mode 100644 (file)
index 0000000..e69de29