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