You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2013/06/17 22:17:03 UTC

svn commit: r1493918 - in /tomcat/tc7.0.x/trunk: ./ java/org/apache/catalina/authenticator/AuthenticatorBase.java test/org/apache/catalina/authenticator/TestFormAuthenticator.java webapps/docs/changelog.xml

Author: markt
Date: Mon Jun 17 20:17:03 2013
New Revision: 1493918

URL: http://svn.apache.org/r1493918
Log:
If GET is not protected but other methods are, the redirect after authentication (that normally uses GET) fails. Because it is unprotected it is not passed to the FORM authenticator for processing (where the original request would be restored).

Modified:
    tomcat/tc7.0.x/trunk/   (props changed)
    tomcat/tc7.0.x/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java
    tomcat/tc7.0.x/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java
    tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml

Propchange: tomcat/tc7.0.x/trunk/
------------------------------------------------------------------------------
  Merged /tomcat/trunk:r1493801,1493910

Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java?rev=1493918&r1=1493917&r2=1493918&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java Mon Jun 17 20:17:03 2013
@@ -454,6 +454,36 @@ public abstract class AuthenticatorBase 
             }
         }
 
+        // Special handling for form-based logins to deal with the case where
+        // a resource is protected for some HTTP methods but not protected for
+        // GET which is used after authentication when redirecting to the
+        // protected resource.
+        // TODO: This is similar to the FormAuthenticator.matchRequest() logic
+        //       Is there a way to remove the duplication?
+        Session session = request.getSessionInternal(false);
+        if (session != null) {
+            SavedRequest savedRequest =
+                    (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);
+            if (savedRequest != null) {
+                String decodedRequestURI = request.getDecodedRequestURI();
+                if (decodedRequestURI != null &&
+                        decodedRequestURI.equals(
+                                savedRequest.getDecodedRequestURI())) {
+                    if (!authenticate(request, response)) {
+                        if (log.isDebugEnabled()) {
+                            log.debug(" Failed authenticate() test");
+                        }
+                        /*
+                         * ASSERT: Authenticator already set the appropriate
+                         * HTTP status code, so we do not have to do anything
+                         * special
+                         */
+                        return;
+                    }
+                }
+            }
+        }
+
         // The Servlet may specify security constraints through annotations.
         // Ensure that they have been processed before constraints are checked
         Wrapper wrapper = (Wrapper) request.getMappingData().wrapper;

Modified: tomcat/tc7.0.x/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java?rev=1493918&r1=1493917&r2=1493918&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java (original)
+++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java Mon Jun 17 20:17:03 2013
@@ -17,9 +17,16 @@
 package org.apache.catalina.authenticator;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.List;
 import java.util.StringTokenizer;
 
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -27,8 +34,12 @@ import org.junit.Test;
 
 import org.apache.catalina.Context;
 import org.apache.catalina.Valve;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.deploy.SecurityCollection;
+import org.apache.catalina.deploy.SecurityConstraint;
 import org.apache.catalina.startup.SimpleHttpClient;
 import org.apache.catalina.startup.TestTomcat.MapRealm;
+import org.apache.catalina.startup.TesterServlet;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 
@@ -234,6 +245,43 @@ public class TestFormAuthenticator exten
                 FormAuthClient.LOGIN_REQUIRED, 1);
     }
 
+
+    @Test
+    public void doTestSelectedMethods() throws Exception {
+
+        FormAuthClientSelectedMethods client =
+                new FormAuthClientSelectedMethods(true, true, true);
+
+        // First request for protected resource gets the login page
+        client.doResourceRequest("PUT", true, "/test?" +
+                SelectedMethodsServlet.PARAM + "=" +
+                SelectedMethodsServlet.VALUE, null);
+        assertTrue(client.getResponseLine(), client.isResponse200());
+        assertTrue(client.isResponseBodyOK());
+        String originalSessionId = client.getSessionId();
+        client.reset();
+
+        // Second request replies to the login challenge
+        client.doResourceRequest("POST", true, "/test/j_security_check",
+                FormAuthClientBase.LOGIN_REPLY);
+        assertTrue("login failed " + client.getResponseLine(),
+                client.isResponse302());
+        assertTrue(client.isResponseBodyOK());
+        String redirectUri = client.getRedirectUri();
+        client.reset();
+
+        // Third request - the login was successful so
+        // follow the redirect to the protected resource
+        client.doResourceRequest("GET", true, redirectUri, null);
+        assertTrue(client.isResponse200());
+        assertTrue(client.isResponseBodyOK());
+        String newSessionId = client.getSessionId();
+
+        assertTrue(!originalSessionId.equals(newSessionId));
+        client.reset();
+    }
+
+
     /*
      * Choreograph the steps of the test dialogue with the server
      *  1. while not authenticated, try to access a protected resource
@@ -254,9 +302,6 @@ public class TestFormAuthenticator exten
             boolean serverWillUseCookies, boolean serverWillChangeSessid)
             throws Exception {
 
-        client = new FormAuthClient(clientShouldUseCookies,
-                serverWillUseCookies, serverWillChangeSessid);
-
         // First request for protected resource gets the login page
         client.setUseContinue(useContinue);
         client.doResourceRequest(resourceMethod, false, null, null);
@@ -345,7 +390,7 @@ public class TestFormAuthenticator exten
      * Encapsulate the logic needed to run a suitably-configured tomcat
      * instance, send it an HTTP request and process the server response
      */
-    private final class FormAuthClient extends SimpleHttpClient {
+    private abstract class FormAuthClientBase extends SimpleHttpClient {
 
         protected static final String LOGIN_PARAM_TAG = "action=";
         protected static final String LOGIN_RESOURCE = "j_security_check";
@@ -370,43 +415,7 @@ public class TestFormAuthenticator exten
         protected final String SESSION_PARAMETER_START =
             SESSION_PARAMETER_NAME + "=";
 
-        private FormAuthClient(boolean clientShouldUseCookies,
-                boolean serverShouldUseCookies,
-                boolean serverShouldChangeSessid) throws Exception {
-
-            Tomcat tomcat = getTomcatInstance();
-            File appDir = new File(getBuildDirectory(), "webapps/examples");
-            Context ctx = tomcat.addWebapp(null, "/examples",
-                    appDir.getAbsolutePath());
-            setUseCookies(clientShouldUseCookies);
-            ctx.setCookies(serverShouldUseCookies);
-
-            MapRealm realm = new MapRealm();
-            realm.addUser("tomcat", "tomcat");
-            realm.addUserRole("tomcat", "tomcat");
-            ctx.setRealm(realm);
-
-            tomcat.start();
-
-            // perhaps this does not work until tomcat has started?
-            ctx.setSessionTimeout(TIMEOUT_MINS);
-
-            // Valve pipeline is only established after tomcat starts
-            Valve[] valves = ctx.getPipeline().getValves();
-            for (Valve valve : valves) {
-                if (valve instanceof AuthenticatorBase) {
-                    ((AuthenticatorBase)valve)
-                            .setChangeSessionIdOnAuthentication(
-                                                serverShouldChangeSessid);
-                    break;
-                }
-            }
-
-            // Port only known after Tomcat starts
-            setPort(getPort());
-        }
-
-        private void doLoginRequest(String loginUri) throws Exception {
+        protected void doLoginRequest(String loginUri) throws Exception {
 
             doResourceRequest("POST", true,
                     PROTECTED_RELATIVE_PATH + loginUri, LOGIN_REPLY);
@@ -421,7 +430,7 @@ public class TestFormAuthenticator exten
          * Cookies are sent if available and supported by the test. Otherwise, the
          * caller is expected to have provided a session id as a path parameter.
          */
-        private void doResourceRequest(String method, boolean isFullQualUri,
+        protected void doResourceRequest(String method, boolean isFullQualUri,
                 String resourceUri, String requestTail) throws Exception {
 
             // build the HTTP request while assembling the uri
@@ -531,7 +540,7 @@ public class TestFormAuthenticator exten
          * Scan the server response body and extract the given
          * url, including any path elements.
          */
-        private String extractBodyUri(String paramTag, String resource) {
+        protected String extractBodyUri(String paramTag, String resource) {
             extractUriElements();
             List<String> elements = getResponseBodyUriElements();
             String fullPath = null;
@@ -559,7 +568,7 @@ public class TestFormAuthenticator exten
         /*
          * extract the session id path element (if it exists in the given url)
          */
-        private String extractPathSessionId(String url) {
+        protected String extractPathSessionId(String url) {
             String sessionId = null;
             int iStart = url.indexOf(SESSION_PARAMETER_START);
             if (iStart > -1) {
@@ -586,4 +595,163 @@ public class TestFormAuthenticator exten
             }
         }
     }
+
+
+    private class FormAuthClient extends FormAuthClientBase {
+        private FormAuthClient(boolean clientShouldUseCookies,
+                boolean serverShouldUseCookies,
+                boolean serverShouldChangeSessid) throws Exception {
+
+            Tomcat tomcat = getTomcatInstance();
+            File appDir = new File(getBuildDirectory(), "webapps/examples");
+            Context ctx = tomcat.addWebapp(null, "/examples",
+                    appDir.getAbsolutePath());
+            setUseCookies(clientShouldUseCookies);
+            ctx.setCookies(serverShouldUseCookies);
+
+            MapRealm realm = new MapRealm();
+            realm.addUser("tomcat", "tomcat");
+            realm.addUserRole("tomcat", "tomcat");
+            ctx.setRealm(realm);
+
+            tomcat.start();
+
+            // perhaps this does not work until tomcat has started?
+            ctx.setSessionTimeout(TIMEOUT_MINS);
+
+            // Valve pipeline is only established after tomcat starts
+            Valve[] valves = ctx.getPipeline().getValves();
+            for (Valve valve : valves) {
+                if (valve instanceof AuthenticatorBase) {
+                    ((AuthenticatorBase)valve)
+                            .setChangeSessionIdOnAuthentication(
+                                                serverShouldChangeSessid);
+                    break;
+                }
+            }
+
+            // Port only known after Tomcat starts
+            setPort(getPort());
+        }
+    }
+
+
+    /**
+     * Encapsulate the logic needed to run a suitably-configured Tomcat
+     * instance, send it an HTTP request and process the server response when
+     * the protected resource is only protected for some HTTP methods. The use
+     * case of particular interest is when GET and POST are not protected since
+     * those are the methods used by the login form and the redirect and if
+     * those methods are not protected the authenticator may not process the
+     * associated requests.
+     */
+    private class FormAuthClientSelectedMethods extends FormAuthClientBase {
+
+        private FormAuthClientSelectedMethods(boolean clientShouldUseCookies,
+                boolean serverShouldUseCookies,
+                boolean serverShouldChangeSessid) throws Exception {
+
+            Tomcat tomcat = getTomcatInstance();
+
+            Context ctx = tomcat.addContext(
+                    "", System.getProperty("java.io.tmpdir"));
+            Tomcat.addServlet(ctx, "SelectedMethods",
+                    new SelectedMethodsServlet());
+            ctx.addServletMapping("/test", "SelectedMethods");
+            // Login servlet just needs to respond "OK". Client will handle
+            // creating a valid response. No need for a form.
+            Tomcat.addServlet(ctx, "Login",
+                    new TesterServlet());
+            ctx.addServletMapping("/login", "Login");
+
+            // Configure the security constraints
+            SecurityConstraint constraint = new SecurityConstraint();
+            SecurityCollection collection = new SecurityCollection();
+            collection.setName("Protect PUT");
+            collection.addMethod("PUT");
+            collection.addPattern("/test");
+            constraint.addCollection(collection);
+            constraint.addAuthRole("tomcat");
+            ctx.addConstraint(constraint);
+
+            // Configure authentication
+            LoginConfig lc = new LoginConfig();
+            lc.setAuthMethod("FORM");
+            lc.setLoginPage("/login");
+            ctx.setLoginConfig(lc);
+            ctx.getPipeline().addValve(new FormAuthenticator());
+
+            setUseCookies(clientShouldUseCookies);
+            ctx.setCookies(serverShouldUseCookies);
+
+            MapRealm realm = new MapRealm();
+            realm.addUser("tomcat", "tomcat");
+            realm.addUserRole("tomcat", "tomcat");
+            ctx.setRealm(realm);
+
+            tomcat.start();
+
+            // perhaps this does not work until tomcat has started?
+            ctx.setSessionTimeout(TIMEOUT_MINS);
+
+            // Valve pipeline is only established after tomcat starts
+            Valve[] valves = ctx.getPipeline().getValves();
+            for (Valve valve : valves) {
+                if (valve instanceof AuthenticatorBase) {
+                    ((AuthenticatorBase)valve)
+                            .setChangeSessionIdOnAuthentication(
+                                                serverShouldChangeSessid);
+                    break;
+                }
+            }
+
+            // Port only known after Tomcat starts
+            setPort(getPort());
+        }
+
+        @Override
+        public boolean isResponseBodyOK() {
+            if (isResponse302()) {
+                return true;
+            }
+            assertTrue(getResponseBody(), getResponseBody().contains("OK"));
+            assertFalse(getResponseBody().contains("FAIL"));
+            return true;
+        }
+    }
+
+
+    private static final class SelectedMethodsServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+        public static final String PARAM = "TestParam";
+        public static final String VALUE = "TestValue";
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.setContentType("text/plain;charset=UTF-8");
+
+            if (VALUE.equals(req.getParameter(PARAM)) &&
+                    req.isUserInRole("tomcat")) {
+                resp.getWriter().print("OK");
+            } else {
+                resp.getWriter().print("FAIL");
+            }
+        }
+
+        @Override
+        protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            // Same as GET for this test case
+            doGet(req, resp);
+        }
+
+        @Override
+        protected void doPut(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            // Same as GET for this test case
+            doGet(req, resp);
+        }
+    }
 }

Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1493918&r1=1493917&r2=1493918&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Mon Jun 17 20:17:03 2013
@@ -64,6 +64,11 @@
         available to <code>ServletContextListener</code>s defined in one of the
         specified ways. (markt)
       </fix>
+      <fix>
+        Better handle FORM authentication when requesting a resource as an
+        unauthenticated user that is only protected for a sub-set of HTTP
+        methods that does not include GET. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Cluster">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org