Author: markt
Date: Mon Jun 17 15:00:56 2013
New Revision: 1493801

URL: http://svn.apache.org/r1493801
Log:
Servlet 3.1. If GET is not protected but other methods are, the redirect after 
authentication (that now always uses a 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/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java
    
tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java

Modified: 
tomcat/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java?rev=1493801&r1=1493800&r2=1493801&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/authenticator/AuthenticatorBase.java 
Mon Jun 17 15:00:56 2013
@@ -455,6 +455,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 = request.getMappingData().wrapper;

Modified: 
tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java?rev=1493801&r1=1493800&r2=1493801&view=diff
==============================================================================
--- 
tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java 
(original)
+++ 
tomcat/trunk/test/org/apache/catalina/authenticator/TestFormAuthenticator.java 
Mon Jun 17 15:00:56 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;
 
@@ -28,8 +35,12 @@ import org.junit.Test;
 import org.apache.catalina.Context;
 import org.apache.catalina.Valve;
 import org.apache.catalina.deploy.ApplicationListener;
+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;
 import org.apache.tomcat.websocket.server.WsListener;
@@ -247,6 +258,43 @@ public class TestFormAuthenticator exten
                 CLIENT_USE_HTTP_10);
     }
 
+
+    @Test
+    public void doTestSelectedMethods() throws Exception {
+
+        FormAuthClientSelectedMethods client =
+                new FormAuthClientSelectedMethods(true, 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.isResponse303());
+        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
@@ -271,11 +319,10 @@ public class TestFormAuthenticator exten
                 serverWillChangeSessid, true);
     }
 
-        private String doTest(String resourceMethod, String redirectMethod,
-                boolean useContinue, boolean clientShouldUseCookies,
-                boolean serverWillUseCookies, boolean serverWillChangeSessid,
-                boolean clientShouldUseHttp11)
-                throws Exception {
+    private String doTest(String resourceMethod, String redirectMethod,
+            boolean useContinue, boolean clientShouldUseCookies,
+            boolean serverWillUseCookies, boolean serverWillChangeSessid,
+            boolean clientShouldUseHttp11) throws Exception {
 
         client = new FormAuthClient(clientShouldUseCookies,
                 clientShouldUseHttp11, serverWillUseCookies,
@@ -374,7 +421,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 class FormAuthClientBase extends SimpleHttpClient {
 
         protected static final String LOGIN_PARAM_TAG = "action=";
         protected static final String LOGIN_RESOURCE = "j_security_check";
@@ -399,50 +446,9 @@ public class TestFormAuthenticator exten
         protected final String SESSION_PARAMETER_START =
             SESSION_PARAMETER_NAME + "=";
 
-        private boolean clientShouldUseHttp11;
+        protected boolean clientShouldUseHttp11;
 
-        private FormAuthClient(boolean clientShouldUseCookies,
-                boolean clientShouldUseHttp11,
-                boolean serverShouldUseCookies,
-                boolean serverShouldChangeSessid) throws Exception {
-
-            this.clientShouldUseHttp11 = clientShouldUseHttp11;
-
-            Tomcat tomcat = getTomcatInstance();
-            File appDir = new File(getBuildDirectory(), "webapps/examples");
-            Context ctx = tomcat.addWebapp(null, "/examples",
-                    appDir.getAbsolutePath());
-            setUseCookies(clientShouldUseCookies);
-            ctx.setCookies(serverShouldUseCookies);
-            ctx.addApplicationListener(new ApplicationListener(
-                    WsListener.class.getName(), false));
-
-            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);
@@ -457,7 +463,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
@@ -571,7 +577,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;
@@ -599,7 +605,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) {
@@ -626,4 +632,171 @@ public class TestFormAuthenticator exten
             }
         }
     }
+
+
+    private class FormAuthClient extends FormAuthClientBase {
+        private FormAuthClient(boolean clientShouldUseCookies,
+                boolean clientShouldUseHttp11,
+                boolean serverShouldUseCookies,
+                boolean serverShouldChangeSessid) throws Exception {
+
+            this.clientShouldUseHttp11 = clientShouldUseHttp11;
+
+            Tomcat tomcat = getTomcatInstance();
+            File appDir = new File(getBuildDirectory(), "webapps/examples");
+            Context ctx = tomcat.addWebapp(null, "/examples",
+                    appDir.getAbsolutePath());
+            setUseCookies(clientShouldUseCookies);
+            ctx.setCookies(serverShouldUseCookies);
+            ctx.addApplicationListener(new ApplicationListener(
+                    WsListener.class.getName(), false));
+
+            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 clientShouldUseHttp11,
+                boolean serverShouldUseCookies,
+                boolean serverShouldChangeSessid) throws Exception {
+
+            this.clientShouldUseHttp11 = clientShouldUseHttp11;
+
+            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 (isResponse303()) {
+                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);
+        }
+    }
 }



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

Reply via email to