Limit Dashboard to GSA Administrators Also only create sessions when required. Users are prompted with a form for authentication. When they submit the form, we try logging in to the GSA's administrative APIs with their credentials. Depending on if it succeeds, they are permitted to view the Dashboard. If success, then the positive result is stored in their session.
diff --git a/build.xml b/build.xml index 529bcca..c12ae95 100644 --- a/build.xml +++ b/build.xml
@@ -23,11 +23,24 @@ <property name="jersey-core.jar" location="${lib.dir}/jersey-core-1.3.jar"/> <property name="jsr311-api.jar" location="${lib.dir}/jsr311-api-1.1.1.jar"/> <property name="json-simple.jar" location="${lib.dir}/json_simple-1.1.jar"/> + <property name="gdata-core.jar" location="${lib.dir}/gdata-core-1.0.jar"/> + <property name="gdata-gsa.jar" location="${lib.dir}/gdata-gsa-1.0.jar"/> <property name="cobertura.dir" value="${basedir}/../cobertura/"/> + <path id="adaptorlib.build.classpath"> + <pathelement location="${opencsv.jar}"/> + <pathelement location="${j-calais.jar}"/> + <pathelement location="${json-simple.jar}"/> + <pathelement location="${gdata-core.jar}"/> + <pathelement location="${gdata-gsa.jar}"/> + <path refid="secmgr.build.classpath"/> + </path> + <path id="adaptorlib.run.classpath"> <pathelement location="${resource.dir}"/> <pathelement location="${json-simple.jar}"/> + <pathelement location="${gdata-core.jar}"/> + <pathelement location="${gdata-gsa.jar}"/> <path refid="secmgr.run.classpath"/> <path refid="opensaml.run.classpath"/> </path> @@ -106,10 +119,7 @@ <javac srcdir="${src.dir}" destdir="${build-src.dir}" debug="true" includeantruntime="false" encoding="utf-8"> <compilerarg value="-Xlint:unchecked"/> - <classpath location="${opencsv.jar}"/> - <classpath location="${j-calais.jar}"/> - <classpath location="${json-simple.jar}"/> - <classpath refid="secmgr.build.classpath"/> + <classpath refid="adaptorlib.build.classpath"/> <include name="adaptorlib/**"/> </javac> @@ -132,6 +142,8 @@ <jar destfile="${dist.dir}/adaptor.jar" basedir="${build-src.dir}" excludes="adaptorlib/examples/**,adaptorlib/prebuilt/**"> <zipfileset includes="**/*.class" src="${json-simple.jar}"/> + <zipfileset includes="**/*.class" src="${gdata-core.jar}"/> + <zipfileset includes="**/*.class" src="${gdata-gsa.jar}"/> <fileset dir="${resource.dir}"/> </jar> <jar destfile="${dist.dir}/adaptor-prebuilt.jar" basedir="${build-src.dir}" @@ -155,10 +167,7 @@ <target name="javadoc" description="Build JavaDocs"> <javadoc sourcepath="${src.dir}" destdir="${javadoc.dir}" overview="${src.dir}/overview.html" packagenames="adaptorlib,adaptorlib.**"> - <classpath location="${opencsv.jar}"/> - <classpath location="${j-calais.jar}"/> - <classpath location="${json-simple.jar}"/> - <classpath refid="secmgr.build.classpath"/> + <classpath refid="adaptorlib.build.classpath"/> <link href="http://download.oracle.com/javase/6/docs/jre/api/net/httpserver/spec/"/> <link href="http://download.oracle.com/javase/6/docs/api/"/> <arg value="-quiet"/> @@ -223,18 +232,11 @@ <junit printsummary="yes" haltonfailure="yes" forkmode="once" fork="true"> <sysproperty key="net.sourceforge.cobertura.datafile" file="${build-instrument.dir}/cobertura.ser"/> + <classpath refid="adaptorlib.run.classpath"/> <classpath refid="cobertura.classpath"/> <classpath location="${junit.jar}"/> <classpath location="${opencsv.jar}"/> <classpath location="${j-calais.jar}"/> - <classpath location="${guava.jar}"/> - <classpath location="${jackson-core-asl.jar}"/> - <classpath location="${jackson-jaxrs.jar}"/> - <classpath location="${jackson-mapper-asl.jar}"/> - <classpath location="${jersey-client.jar}"/> - <classpath location="${jersey-core.jar}"/> - <classpath location="${jsr311-api.jar}"/> - <classpath location="${json-simple.jar}"/> <classpath location="${build-instrument.dir}"/> <classpath location="${build-src.dir}"/> <classpath location="${build-test.dir}"/>
diff --git a/lib/gdata-core-1.0.jar b/lib/gdata-core-1.0.jar new file mode 100644 index 0000000..c395e73 --- /dev/null +++ b/lib/gdata-core-1.0.jar Binary files differ
diff --git a/lib/gdata-gsa-1.0.jar b/lib/gdata-gsa-1.0.jar new file mode 100644 index 0000000..5594e05 --- /dev/null +++ b/lib/gdata-gsa-1.0.jar Binary files differ
diff --git a/resources/adaptorlib/static/login-invalid.html b/resources/adaptorlib/static/login-invalid.html new file mode 100644 index 0000000..8c12dc7 --- /dev/null +++ b/resources/adaptorlib/static/login-invalid.html
@@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Dashboard Login</title> + <style type="text/css"> + #invalid { + color: red; + } + </style> +</head> +<body> + <p>You must login to continue to the Dashboard. Please login with the + administrator username and password used for your GSA.</p> + + <p id='invalid'>Invalid username or password.</p> + + <form action="" method="POST"> + <table style="border: none"> + <tr> + <td><label for="username">Username</label></td> + <td><input type="text" name="username" id="username"></td> + </tr> + <tr> + <td><label for="password">Password</label></td> + <td><input type="password" name="password" id="password"></td> + </tr> + <tr><td></td><td><input type="submit" value="Submit"/></td></tr> + </table> + </form> +</body>
diff --git a/resources/adaptorlib/static/login.html b/resources/adaptorlib/static/login.html new file mode 100644 index 0000000..2a865e1 --- /dev/null +++ b/resources/adaptorlib/static/login.html
@@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Dashboard Login</title> +</head> +<body> + <p>You must login to continue to the Dashboard. Please login with the + administrator username and password used for your GSA.</p> + + <form action="" method="POST"> + <table style="border: none"> + <tr> + <td><label for="username">Username</label></td> + <td><input type="text" name="username" id="username"></td> + </tr> + <tr> + <td><label for="password">Password</label></td> + <td><input type="password" name="password" id="password"></td> + </tr> + <tr><td></td><td><input type="submit" value="Submit"/></td></tr> + </table> + </form> +</body>
diff --git a/src/adaptorlib/AbstractHandler.java b/src/adaptorlib/AbstractHandler.java index 9a65773..c46e8f6 100644 --- a/src/adaptorlib/AbstractHandler.java +++ b/src/adaptorlib/AbstractHandler.java
@@ -183,12 +183,13 @@ } /** - * Redirect client to {@code location}. + * Redirect client to {@code location}. The client should retrieve the + * referred location via GET, independent of the method of this request. */ public void sendRedirect(HttpExchange ex, URI location) throws IOException { ex.getResponseHeaders().set("Location", location.toString()); - respond(ex, 307 /* Temporary redirect */, null, null); + respond(ex, HttpURLConnection.HTTP_SEE_OTHER, null, null); } /**
diff --git a/src/adaptorlib/AdministratorSecurityHandler.java b/src/adaptorlib/AdministratorSecurityHandler.java new file mode 100644 index 0000000..b5ee362 --- /dev/null +++ b/src/adaptorlib/AdministratorSecurityHandler.java
@@ -0,0 +1,157 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package adaptorlib; + +import com.google.enterprise.apis.client.GsaClient; +import com.google.gdata.util.AuthenticationException; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import java.io.*; +import java.net.*; +import java.nio.charset.Charset; + +/** + * Require GSA-Administrator authentication before allowing access to wrapped + * handler. + */ +public class AdministratorSecurityHandler extends AbstractHandler { + /** Key used to store the fact the user has been authenticated. */ + private static final String SESSION_ATTR_NAME = "dashboard-authned"; + private static final String LOGIN_PAGE = "static/login.html"; + private static final String LOGIN_INVALID_PAGE = "static/login-invalid.html"; + + /** Wrapped handler, for when the user is authenticated. */ + private final HttpHandler handler; + /** Manager that handles keeping track of authenticated users. */ + private final SessionManager<HttpExchange> sessionManager; + private final String gsaHostname; + private final int gsaPort; + + public AdministratorSecurityHandler(String fallbackHostname, + Charset defaultEncoding, HttpHandler handler, + SessionManager<HttpExchange> sessionManager, String gsaHostname, + int gsaPort) { + super(fallbackHostname, defaultEncoding); + this.handler = handler; + this.sessionManager = sessionManager; + this.gsaHostname = gsaHostname; + this.gsaPort = gsaPort; + } + + @Override + public void meteredHandle(HttpExchange ex) throws IOException { + String pageToDisplay = LOGIN_PAGE; + + if ("POST".equals(ex.getRequestMethod())) { + AuthzStatus authn = validUsernameAndPassword(ex); + if (authn == AuthzStatus.PERMIT) { + // Need the client to access the page via GET since the only reason the + // request method was POST was because of submitting our login form. + sendRedirect(ex, getRequestUri(ex)); + return; + } else if (authn == AuthzStatus.DENY) { + pageToDisplay = LOGIN_INVALID_PAGE; + } + } + + // Send login page. + InputStream is = this.getClass().getResourceAsStream(pageToDisplay); + if (is == null) { + cannedRespond(ex, HttpURLConnection.HTTP_INTERNAL_ERROR, "text/plain", + "Could not load login page"); + return; + } + byte[] page; + try { + page = IOHelper.readInputStreamToByteArray(is); + } finally { + is.close(); + } + respond(ex, HttpURLConnection.HTTP_FORBIDDEN, "text/html", page); + } + + /** + * Check POST data to see if the user can be authenticated by the GSA. This + * abuses the {@code AuthzStatus} class, using it for Authn, but it was + * convenient. + * + * @return {@code PERMIT} if user authenticated, {@code DENY} if invalid + * credentials, and {@code INDETERMINATE} otherwise + */ + private AuthzStatus validUsernameAndPassword(HttpExchange ex) + throws IOException { + // Check to see if they provided a username and password. + String username = null; + String password = null; + try { + String request; + { + byte[] bytes + = IOHelper.readInputStreamToByteArray(ex.getRequestBody()); + request = new String(bytes, "US-ASCII"); + } + for (String pair : request.split("&")) { + String[] splitPair = pair.split("=", 2); + if (splitPair.length != 2) { + continue; + } + splitPair[0] = URLDecoder.decode(splitPair[0], "UTF-8"); + splitPair[1] = URLDecoder.decode(splitPair[1], "UTF-8"); + if ("username".equals(splitPair[0])) { + username = splitPair[1]; + } else if ("password".equals(splitPair[0])) { + password = splitPair[1]; + } + if (username != null && password != null) { + break; + } + } + } catch (Exception e) { + // Assume that they were POSTing to a different page, since they didn't + // provide the expected input. + username = null; + password = null; + } + if (username == null || password == null) { + // Must not have been from our login page. + return AuthzStatus.INDETERMINATE; + } + + // Check to see if provided username and password are valid. + try { + new GsaClient(gsaHostname, gsaPort, username, password); + } catch (AuthenticationException e) { + return AuthzStatus.DENY; + } + + // We have a winner. Store in the session that they are a valid user. + Session session = sessionManager.getSession(ex); + session.setAttribute(SESSION_ATTR_NAME, true); + return AuthzStatus.PERMIT; + } + + @Override + public void handle(HttpExchange ex) throws IOException { + // Perform fast-path checking here to prevent double-logging most requsets. + Session session = sessionManager.getSession(ex, false); + if (session != null && session.getAttribute(SESSION_ATTR_NAME) != null) { + handler.handle(ex); + return; + } + super.handle(ex); + } +}
diff --git a/src/adaptorlib/DocumentHandler.java b/src/adaptorlib/DocumentHandler.java index be2f203..22d5b0d 100644 --- a/src/adaptorlib/DocumentHandler.java +++ b/src/adaptorlib/DocumentHandler.java
@@ -125,9 +125,10 @@ String principal = null; Set<String> groups = Collections.emptySet(); - if (sessionManager != null) { - AuthnState authnState = (AuthnState) sessionManager.getSession(ex) - .getAttribute(AuthnState.SESSION_ATTR_NAME); + Session session = sessionManager.getSession(ex, false); + if (session != null) { + AuthnState authnState + = (AuthnState) session.getAttribute(AuthnState.SESSION_ATTR_NAME); if (authnState != null && authnState.isAuthenticated()) { principal = authnState.getPrincipal(); groups = authnState.getGroups();
diff --git a/src/adaptorlib/GsaCommunicationHandler.java b/src/adaptorlib/GsaCommunicationHandler.java index 39d5859..6cfc608 100644 --- a/src/adaptorlib/GsaCommunicationHandler.java +++ b/src/adaptorlib/GsaCommunicationHandler.java
@@ -15,6 +15,7 @@ package adaptorlib; import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsParameters; @@ -103,16 +104,14 @@ // useful during testing. port = server.getAddress().getPort(); config.setValue("server.port", "" + port); - server.createContext("/dashboard", new DashboardHandler(config, journal)); - server.createContext("/rpc", createRpcHandler()); - SessionManager<HttpExchange> sessionManager = null; - AuthnHandler authnHandler = null; - if (secure) { - sessionManager = new SessionManager<HttpExchange>( + SessionManager<HttpExchange> sessionManager + = new SessionManager<HttpExchange>( new SessionManager.HttpExchangeCookieAccess(), 30 * 60 * 1000 /* session lifetime: 30 minutes */, 5 * 60 * 1000 /* max cleanup frequency: 5 minutes */); + AuthnHandler authnHandler = null; + if (secure) { server.createContext("/samlassertionconsumer", new SamlAssertionConsumerHandler(config.getServerHostname(), config.getGsaCharacterEncoding(), sessionManager)); @@ -129,6 +128,13 @@ config.getServerAddResolvedGsaHostnameToGsaIps(), config.getGsaHostname(), config.getServerGsaIps(), authnHandler, sessionManager)); + + server.createContext("/dashboard", + createAdminSecurityHandler(new DashboardHandler(config, journal), + config, sessionManager, secure)); + server.createContext("/rpc", + createAdminSecurityHandler(createRpcHandler(), config, + sessionManager, secure)); server.setExecutor(Executors.newCachedThreadPool()); server.start(); log.info("GSA host name: " + config.getGsaHostname()); @@ -164,6 +170,14 @@ } } + private AdministratorSecurityHandler createAdminSecurityHandler( + HttpHandler handler, Config config, + SessionManager<HttpExchange> sessionManager, boolean secure) { + return new AdministratorSecurityHandler(config.getServerHostname(), + config.getGsaCharacterEncoding(), handler, sessionManager, + config.getGsaHostname(), secure ? 8443 : 8000); + } + private synchronized RpcHandler createRpcHandler() { RpcHandler rpcHandler = new RpcHandler(config.getServerHostname(), config.getGsaCharacterEncoding(), this);
diff --git a/src/adaptorlib/SessionManager.java b/src/adaptorlib/SessionManager.java index d004202..b502a7c 100644 --- a/src/adaptorlib/SessionManager.java +++ b/src/adaptorlib/SessionManager.java
@@ -61,10 +61,14 @@ } public Session getSession(E cookieState) { + return getSession(cookieState, true); + } + + public Session getSession(E cookieState, boolean create) { String value = cookieAccess.getCookie(cookieState, COOKIE_NAME); if (value == null) { - // No pre-existing session found. Create a new one. - return createSession(cookieState); + // No pre-existing session found. + return create ? createSession(cookieState) : null; } synchronized (this) {
diff --git a/test/adaptorlib/DocumentHandlerTest.java b/test/adaptorlib/DocumentHandlerTest.java index f1336b3..e5d9d1e 100644 --- a/test/adaptorlib/DocumentHandlerTest.java +++ b/test/adaptorlib/DocumentHandlerTest.java
@@ -16,6 +16,8 @@ import static org.junit.Assert.*; +import com.sun.net.httpserver.HttpExchange; + import org.junit.*; import org.junit.rules.ExpectedException; @@ -31,19 +33,18 @@ @Rule public ExpectedException thrown = ExpectedException.none(); - private DocumentHandler handler = new DocumentHandler("localhost", - Charset.forName("UTF-8"), new MockDocIdDecoder(), - new Journal(new MockTimeProvider()), new MockAdaptor(), false, - "localhost", new String[0], null, null); + private DocumentHandler handler = createDefaultHandlerForAdaptor( + new MockAdaptor()); private MockHttpExchange ex = new MockHttpExchange("http", "GET", "/", new MockHttpContext(handler, "/")); @Test public void testSecurity() throws Exception { - handler = new DocumentHandler("localhost", Charset.forName("UTF-8"), + DocumentHandler handler = new DocumentHandler( + "localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), new PrivateMockAdaptor(), false, "localhost", new String[0], null, - null); + createSessionManager()); handler.handle(ex); assertEquals(403, ex.getResponseCode()); } @@ -51,10 +52,11 @@ @Test public void testSecurityFromGsa() throws Exception { // 127.0.0.3 is the address hard-coded in MockHttpExchange - handler = new DocumentHandler("localhost", Charset.forName("UTF-8"), + DocumentHandler handler = new DocumentHandler( + "localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), new PrivateMockAdaptor(), false, "localhost", - new String[] {"127.0.0.3", " "}, null, null); + new String[] {"127.0.0.3", " "}, null, createSessionManager()); handler.handle(ex); assertEquals(200, ex.getResponseCode()); assertArrayEquals(new byte[] {1, 2, 3}, ex.getResponseBytes()); @@ -63,10 +65,11 @@ @Test public void testSecurityFromGsaNoWhitelist() throws Exception { // 127.0.0.3 is the address hard-coded in MockHttpExchange - handler = new DocumentHandler("localhost", Charset.forName("UTF-8"), + DocumentHandler handler = new DocumentHandler( + "localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), new PrivateMockAdaptor(), false, "127.0.0.3", - new String[0], null, null); + new String[0], null, createSessionManager()); handler.handle(ex); assertEquals(403, ex.getResponseCode()); } @@ -74,10 +77,11 @@ @Test public void testSecurityFromGsaAutoAddWhitelist() throws Exception { // 127.0.0.3 is the address hard-coded in MockHttpExchange - handler = new DocumentHandler("localhost", Charset.forName("UTF-8"), + DocumentHandler handler = new DocumentHandler( + "localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), new PrivateMockAdaptor(), true, "127.0.0.3", - new String[0], null, null); + new String[0], null, createSessionManager()); handler.handle(ex); assertEquals(200, ex.getResponseCode()); assertArrayEquals(new byte[] {1, 2, 3}, ex.getResponseBytes()); @@ -86,19 +90,21 @@ @Test public void testNoSuchHost() throws Exception { thrown.expect(RuntimeException.class); - handler = new DocumentHandler("localhost", Charset.forName("UTF-8"), + DocumentHandler handler = new DocumentHandler( + "localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), new PrivateMockAdaptor(), false, "localhost", - new String[] {"-no-such-host-"}, null, null); + new String[] {"-no-such-host-"}, null, createSessionManager()); } @Test public void testNoSuchGsaHost() throws Exception { thrown.expect(RuntimeException.class); - handler = new DocumentHandler("localhost", Charset.forName("UTF-8"), + DocumentHandler handler = new DocumentHandler( + "localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), new PrivateMockAdaptor(), true, "-no-such-host-", - new String[0], null, null); + new String[0], null, createSessionManager()); } @Test @@ -134,7 +140,7 @@ return null; } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(403, ex.getResponseCode()); } @@ -148,7 +154,7 @@ throw new FileNotFoundException(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(404, ex.getResponseCode()); } @@ -162,7 +168,7 @@ throw new IOException(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(500, ex.getResponseCode()); } @@ -176,7 +182,7 @@ throw new RuntimeException(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(500, ex.getResponseCode()); } @@ -189,7 +195,7 @@ throws IOException { } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(500, ex.getResponseCode()); } @@ -203,7 +209,7 @@ response.respondNotModified(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(304, ex.getResponseCode()); } @@ -218,7 +224,7 @@ response.getOutputStream(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(500, ex.getResponseCode()); } @@ -233,7 +239,7 @@ response.respondNotModified(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(500, ex.getResponseCode()); } @@ -248,7 +254,7 @@ response.getOutputStream(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(200, ex.getResponseCode()); } @@ -263,7 +269,7 @@ response.setContentType("text/plain"); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(500, ex.getResponseCode()); } @@ -278,7 +284,7 @@ response.setMetadata(Metadata.EMPTY); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); handler.handle(ex); assertEquals(500, ex.getResponseCode()); } @@ -303,14 +309,14 @@ response.getOutputStream(); } }; - handler = createDefaultHandlerForAdaptor(adaptor); + DocumentHandler handler = createDefaultHandlerForAdaptor(adaptor); ex.getRequestHeaders().set("If-Modified-Since", "Thu, 1 Jan 1970 00:00:01 GMT"); handler.handle(ex); assertEquals(304, ex.getResponseCode()); - ex = new MockHttpExchange("http", "GET", "/", - new MockHttpContext(handler, "/")); + MockHttpExchange ex = new MockHttpExchange("http", "GET", "/", + new MockHttpContext(handler, "/")); ex.getRequestHeaders().set("If-Modified-Since", "Thu, 1 Jan 1970 00:00:00 GMT"); handler.handle(ex); @@ -336,9 +342,11 @@ response.getOutputStream(); } }; - handler = new DocumentHandler("localhost", Charset.forName("UTF-8"), + DocumentHandler handler = new DocumentHandler( + "localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), - adaptor, false, "localhost", new String[0], null, null); + adaptor, false, "localhost", new String[0], null, + createSessionManager()); handler.handle(ex); assertEquals(200, ex.getResponseCode()); assertNull(ex.getResponseHeaders().getFirst("X-Gsa-External-Metadata")); @@ -347,7 +355,13 @@ private DocumentHandler createDefaultHandlerForAdaptor(Adaptor adaptor) { return new DocumentHandler("localhost", Charset.forName("UTF-8"), new MockDocIdDecoder(), new Journal(new MockTimeProvider()), - adaptor, false, "localhost", new String[0], null, null); + adaptor, false, "localhost", new String[0], null, + createSessionManager()); + } + + private SessionManager<HttpExchange> createSessionManager() { + return new SessionManager<HttpExchange>( + new SessionManager.HttpExchangeCookieAccess(), 1000, 1000); } @Test