/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.catalina.valves; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.SessionCookieConfig; import javax.servlet.http.Cookie; import org.junit.Test; import org.apache.catalina.Context; import org.apache.catalina.Valve; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.core.StandardPipeline; import org.easymock.EasyMock; import org.easymock.IMocksControl; public class TestLoadBalancerDrainingValve { static class MockResponse extends Response { private List cookies; @Override public boolean isCommitted() { return false; } @Override public void addCookie(Cookie cookie) { if(null == cookies) cookies = new ArrayList<>(1); cookies.add(cookie); } @Override public List getCookies() { return cookies; } } static class CookieConfig implements SessionCookieConfig { private String name; private String domain; private String path; private String comment; private boolean httpOnly; private boolean secure; private int maxAge; @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public String getDomain() { return domain; } @Override public void setDomain(String domain) { this.domain = domain; } @Override public String getPath() { return path; } @Override public void setPath(String path) { this.path = path; } @Override public String getComment() { return comment; } @Override public void setComment(String comment) { this.comment = comment; } @Override public boolean isHttpOnly() { return httpOnly; } @Override public void setHttpOnly(boolean httpOnly) { this.httpOnly = httpOnly; } @Override public boolean isSecure() { return secure; } @Override public void setSecure(boolean secure) { this.secure = secure; } @Override public int getMaxAge() { return maxAge; } @Override public void setMaxAge(int maxAge) { this.maxAge = maxAge; } } // A Cookie subclass that knows how to compare itself to other Cookie objects static class MyCookie extends Cookie { private static final long serialVersionUID = 1L; public MyCookie(String name, String value) { super(name, value); } @Override public boolean equals(Object o) { if(!(o instanceof MyCookie)) { return false; } MyCookie mc = (MyCookie)o; return mc.getName().equals(this.getName()) && mc.getPath().equals(this.getPath()) && mc.getValue().equals(this.getValue()) && mc.getMaxAge() == this.getMaxAge(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getMaxAge(); result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); result = prime * result + ((getPath() == null) ? 0 : getPath().hashCode()); result = prime * result + ((getValue() == null) ? 0 : getValue().hashCode()); return result; } @Override public String toString() { return "Cookie { name=" + getName() + ", value=" + getValue() + ", path=" + getPath() + ", maxAge=" + getMaxAge() + " }"; } } @Test public void testNormalRequest() throws Exception { runValve("ACT", true, true, false, null); } @Test public void testDisabledValidSession() throws Exception { runValve("DIS", true, true, false, null); } @Test public void testDisabledInvalidSession() throws Exception { runValve("DIS", false, false, false, "foo=bar"); } @Test public void testDisabledInvalidSessionWithIgnore() throws Exception { runValve("DIS", false, true, true, "foo=bar"); } private void runValve(String jkActivation, boolean validSessionId, boolean expectInvokeNext, boolean enableIgnore, String queryString) throws Exception { IMocksControl control = EasyMock.createControl(); ServletContext servletContext = control.createMock(ServletContext.class); Context ctx = control.createMock(Context.class); Request request = control.createMock(Request.class); Response response = control.createMock(Response.class); String sessionCookieName = "JSESSIONID"; String sessionId = "cafebabe"; String requestURI = "/test/path"; SessionCookieConfig cookieConfig = new CookieConfig(); cookieConfig.setDomain("example.com"); cookieConfig.setName(sessionCookieName); cookieConfig.setPath("/"); // Valve.init requires all of this stuff EasyMock.expect(ctx.getMBeanKeyProperties()).andStubReturn(""); EasyMock.expect(ctx.getName()).andStubReturn(""); EasyMock.expect(ctx.getPipeline()).andStubReturn(new StandardPipeline()); EasyMock.expect(ctx.getDomain()).andStubReturn("foo"); EasyMock.expect(ctx.getLogger()).andStubReturn(org.apache.juli.logging.LogFactory.getLog(LoadBalancerDrainingValve.class)); EasyMock.expect(ctx.getServletContext()).andStubReturn(servletContext); // Set up the actual test EasyMock.expect(request.getAttribute(LoadBalancerDrainingValve.ATTRIBUTE_KEY_JK_LB_ACTIVATION)).andStubReturn(jkActivation); EasyMock.expect(Boolean.valueOf(request.isRequestedSessionIdValid())).andStubReturn(Boolean.valueOf(validSessionId)); ArrayList cookies = new ArrayList<>(); if(enableIgnore) { cookies.add(new Cookie("ignore", "true")); } if(!validSessionId) { MyCookie cookie = new MyCookie(cookieConfig.getName(), sessionId); cookie.setPath(cookieConfig.getPath()); cookie.setValue(sessionId); cookies.add(cookie); EasyMock.expect(request.getRequestedSessionId()).andStubReturn(sessionId); EasyMock.expect(request.getRequestURI()).andStubReturn(requestURI); EasyMock.expect(request.getCookies()).andStubReturn(cookies.toArray(new Cookie[cookies.size()])); EasyMock.expect(request.getContext()).andStubReturn(ctx); EasyMock.expect(ctx.getSessionCookieName()).andStubReturn(sessionCookieName); EasyMock.expect(servletContext.getSessionCookieConfig()).andStubReturn(cookieConfig); EasyMock.expect(request.getQueryString()).andStubReturn(queryString); EasyMock.expect(ctx.getSessionCookiePath()).andStubReturn("/"); if (!enableIgnore) { EasyMock.expect(Boolean.valueOf(ctx.getSessionCookiePathUsesTrailingSlash())).andStubReturn(Boolean.TRUE); EasyMock.expect(request.getQueryString()).andStubReturn(queryString); // Response will have cookie deleted MyCookie expectedCookie = new MyCookie(cookieConfig.getName(), ""); expectedCookie.setPath(cookieConfig.getPath()); expectedCookie.setMaxAge(0); // These two lines just mean EasyMock.expect(response.addCookie) but for a void method response.addCookie(expectedCookie); EasyMock.expect(ctx.getSessionCookieName()).andReturn(sessionCookieName); // Indirect call String expectedRequestURI = requestURI; if(null != queryString) expectedRequestURI = expectedRequestURI + '?' + queryString; response.setHeader("Location", expectedRequestURI); response.setStatus(307); } } Valve next = control.createMock(Valve.class); if(expectInvokeNext) { // Expect the "next" Valve to fire // Next 2 lines are basically EasyMock.expect(next.invoke(req,res)) but for a void method next.invoke(request, response); EasyMock.expectLastCall(); } // Get set to actually test control.replay(); LoadBalancerDrainingValve valve = new LoadBalancerDrainingValve(); valve.setContainer(ctx); valve.init(); valve.setNext(next); valve.setIgnoreCookieName("ignore"); valve.setIgnoreCookieValue("true"); valve.invoke(request, response); control.verify(); } }