--- test/org/apache/catalina/valves/TestRemoteIpValve.java (revision 905127) +++ test/org/apache/catalina/valves/TestRemoteIpValve.java (working copy) @@ -38,6 +38,9 @@ static class RemoteAddrAndHostTrackerValve extends ValveBase { private String remoteAddr; private String remoteHost; + private String scheme; + private boolean secure; + private int serverPort; public String getRemoteAddr() { return remoteAddr; @@ -47,10 +50,25 @@ return remoteHost; } + public String getScheme() { + return scheme; + } + + public int getServerPort() { + return serverPort; + } + + public boolean isSecure() { + return secure; + } + @Override public void invoke(Request request, Response response) throws IOException, ServletException { this.remoteHost = request.getRemoteHost(); this.remoteAddr = request.getRemoteAddr(); + this.scheme = request.getScheme(); + this.secure = request.isSecure(); + this.serverPort = request.getServerPort(); } } @@ -271,6 +289,262 @@ assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost); } + public void testInvokeXforwardedProtoSaysHttpsForIncomingHttpRequest() throws Exception { + + // PREPARE + RemoteIpValve remoteIpValve = new RemoteIpValve(); + remoteIpValve.setRemoteIpHeader("x-forwarded-for"); + remoteIpValve.setProtocolHeader("x-forwarded-proto"); + RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve(); + remoteIpValve.setNext(remoteAddrAndHostTrackerValve); + + Request request = new Request(); + request.setCoyoteRequest(new org.apache.coyote.Request()); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130"); + // protocol + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-proto").setString("https"); + request.setSecure(false); + request.setServerPort(8080); + request.getCoyoteRequest().scheme().setString("http"); + + // TEST + remoteIpValve.invoke(request, null); + + // VERIFY + // client ip + String actualXForwardedFor = request.getHeader("x-forwarded-for"); + assertNull("no intermediate non-trusted proxy, x-forwarded-for must be null", actualXForwardedFor); + + String actualXForwardedBy = request.getHeader("x-forwarded-by"); + assertNull("no intermediate trusted proxy", actualXForwardedBy); + + String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr(); + assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr); + + String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost(); + assertEquals("remoteHost", "140.211.11.130", actualRemoteHost); + + String actualPostInvokeRemoteAddr = request.getRemoteAddr(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr); + + String actualPostInvokeRemoteHost = request.getRemoteHost(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteHost); + + // protocol + String actualScheme = remoteAddrAndHostTrackerValve.getScheme(); + assertEquals("x-forwarded-proto says https", "https", actualScheme); + + int actualServerPort = remoteAddrAndHostTrackerValve.getServerPort(); + assertEquals("x-forwarded-proto says https", 443, actualServerPort); + + boolean actualSecure = remoteAddrAndHostTrackerValve.isSecure(); + assertEquals("x-forwarded-proto says https", true, actualSecure); + + boolean actualPostInvokeSecure = request.isSecure(); + assertEquals("postInvoke secure", false, actualPostInvokeSecure); + + int actualPostInvokeServerPort = request.getServerPort(); + assertEquals("postInvoke serverPort", 8080, actualPostInvokeServerPort); + + String actualPostInvokeScheme = request.getScheme(); + assertEquals("postInvoke scheme", "http", actualPostInvokeScheme); + } + + public void testInvokeXforwardedProtoIsNullForIncomingHttpRequest() throws Exception { + + // PREPARE + RemoteIpValve remoteIpValve = new RemoteIpValve(); + remoteIpValve.setRemoteIpHeader("x-forwarded-for"); + remoteIpValve.setProtocolHeader("x-forwarded-proto"); + RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve(); + remoteIpValve.setNext(remoteAddrAndHostTrackerValve); + + Request request = new Request(); + request.setCoyoteRequest(new org.apache.coyote.Request()); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130"); + // protocol + // null "x-forwarded-proto" + request.setSecure(false); + request.setServerPort(8080); + request.getCoyoteRequest().scheme().setString("http"); + + // TEST + remoteIpValve.invoke(request, null); + + // VERIFY + // client ip + String actualXForwardedFor = request.getHeader("x-forwarded-for"); + assertNull("no intermediate non-trusted proxy, x-forwarded-for must be null", actualXForwardedFor); + + String actualXForwardedBy = request.getHeader("x-forwarded-by"); + assertNull("no intermediate trusted proxy", actualXForwardedBy); + + String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr(); + assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr); + + String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost(); + assertEquals("remoteHost", "140.211.11.130", actualRemoteHost); + + String actualPostInvokeRemoteAddr = request.getRemoteAddr(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr); + + String actualPostInvokeRemoteHost = request.getRemoteHost(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteHost); + + // protocol + String actualScheme = remoteAddrAndHostTrackerValve.getScheme(); + assertEquals("x-forwarded-proto is null", "http", actualScheme); + + int actualServerPort = remoteAddrAndHostTrackerValve.getServerPort(); + assertEquals("x-forwarded-proto is null", 8080, actualServerPort); + + boolean actualSecure = remoteAddrAndHostTrackerValve.isSecure(); + assertEquals("x-forwarded-proto is null", false, actualSecure); + + boolean actualPostInvokeSecure = request.isSecure(); + assertEquals("postInvoke secure", false, actualPostInvokeSecure); + + int actualPostInvokeServerPort = request.getServerPort(); + assertEquals("postInvoke serverPort", 8080, actualPostInvokeServerPort); + + String actualPostInvokeScheme = request.getScheme(); + assertEquals("postInvoke scheme", "http", actualPostInvokeScheme); + } + + public void testInvokeXforwardedProtoSaysHttpForIncomingHttpsRequest() throws Exception { + + // PREPARE + RemoteIpValve remoteIpValve = new RemoteIpValve(); + remoteIpValve.setRemoteIpHeader("x-forwarded-for"); + remoteIpValve.setProtocolHeader("x-forwarded-proto"); + RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve(); + remoteIpValve.setNext(remoteAddrAndHostTrackerValve); + + Request request = new Request(); + request.setCoyoteRequest(new org.apache.coyote.Request()); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130"); + // protocol + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-proto").setString("http"); + request.setSecure(true); + request.setServerPort(8443); + request.getCoyoteRequest().scheme().setString("https"); + + // TEST + remoteIpValve.invoke(request, null); + + // VERIFY + // client ip + String actualXForwardedFor = request.getHeader("x-forwarded-for"); + assertNull("no intermediate non-trusted proxy, x-forwarded-for must be null", actualXForwardedFor); + + String actualXForwardedBy = request.getHeader("x-forwarded-by"); + assertNull("no intermediate trusted proxy", actualXForwardedBy); + + String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr(); + assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr); + + String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost(); + assertEquals("remoteHost", "140.211.11.130", actualRemoteHost); + + String actualPostInvokeRemoteAddr = request.getRemoteAddr(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr); + + String actualPostInvokeRemoteHost = request.getRemoteHost(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteHost); + + // protocol + String actualScheme = remoteAddrAndHostTrackerValve.getScheme(); + assertEquals("x-forwarded-proto says http", "http", actualScheme); + + int actualServerPort = remoteAddrAndHostTrackerValve.getServerPort(); + assertEquals("x-forwarded-proto says http", 80, actualServerPort); + + boolean actualSecure = remoteAddrAndHostTrackerValve.isSecure(); + assertEquals("x-forwarded-proto says http", false, actualSecure); + + boolean actualPostInvokeSecure = request.isSecure(); + assertEquals("postInvoke secure", true, actualPostInvokeSecure); + + int actualPostInvokeServerPort = request.getServerPort(); + assertEquals("postInvoke serverPort", 8443, actualPostInvokeServerPort); + + String actualPostInvokeScheme = request.getScheme(); + assertEquals("postInvoke scheme", "https", actualPostInvokeScheme); + } + + public void testInvokeXforwardedProtoIsNullForIncomingHttpsRequest() throws Exception { + + // PREPARE + RemoteIpValve remoteIpValve = new RemoteIpValve(); + remoteIpValve.setRemoteIpHeader("x-forwarded-for"); + remoteIpValve.setProtocolHeader("x-forwarded-proto"); + RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve(); + remoteIpValve.setNext(remoteAddrAndHostTrackerValve); + + Request request = new Request(); + request.setCoyoteRequest(new org.apache.coyote.Request()); + // client ip + request.setRemoteAddr("192.168.0.10"); + request.setRemoteHost("192.168.0.10"); + request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130"); + // protocol + // Don't declare "x-forwarded-proto" + request.setSecure(true); + request.setServerPort(8443); + request.getCoyoteRequest().scheme().setString("https"); + + // TEST + remoteIpValve.invoke(request, null); + + // VERIFY + // client ip + String actualXForwardedFor = request.getHeader("x-forwarded-for"); + assertNull("no intermediate non-trusted proxy, x-forwarded-for must be null", actualXForwardedFor); + + String actualXForwardedBy = request.getHeader("x-forwarded-by"); + assertNull("no intermediate trusted proxy", actualXForwardedBy); + + String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr(); + assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr); + + String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost(); + assertEquals("remoteHost", "140.211.11.130", actualRemoteHost); + + String actualPostInvokeRemoteAddr = request.getRemoteAddr(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr); + + String actualPostInvokeRemoteHost = request.getRemoteHost(); + assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteHost); + + // protocol + String actualScheme = remoteAddrAndHostTrackerValve.getScheme(); + assertEquals("x-forwarded-proto is null", "https", actualScheme); + + int actualServerPort = remoteAddrAndHostTrackerValve.getServerPort(); + assertEquals("x-forwarded-proto is null", 8443, actualServerPort); + + boolean actualSecure = remoteAddrAndHostTrackerValve.isSecure(); + assertEquals("x-forwarded-proto is null", true, actualSecure); + + boolean actualPostInvokeSecure = request.isSecure(); + assertEquals("postInvoke secure", true, actualPostInvokeSecure); + + int actualPostInvokeServerPort = request.getServerPort(); + assertEquals("postInvoke serverPort", 8443, actualPostInvokeServerPort); + + String actualPostInvokeScheme = request.getScheme(); + assertEquals("postInvoke scheme", "https", actualPostInvokeScheme); + } + public void testInvokeNotAllowedRemoteAddr() throws Exception { // PREPARE RemoteIpValve remoteIpValve = new RemoteIpValve(); --- java/org/apache/catalina/valves/RemoteIpValve.java (revision 905127) +++ java/org/apache/catalina/valves/RemoteIpValve.java (working copy) @@ -26,6 +26,7 @@ import java.util.regex.PatternSyntaxException; import javax.servlet.ServletException; +import javax.servlet.ServletRequest; import org.apache.tomcat.util.res.StringManager; import org.apache.catalina.connector.Request; @@ -126,6 +127,19 @@ * https * * + * httpServerPort + * Value returned by {@link ServletRequest#getServerPort()} when the protocolHeader indicates http protocol + * N/A + * integer + * 80 + * + * + * httpsServerPort + * Value returned by {@link ServletRequest#getServerPort()} when the protocolHeader indicates https protocol + * N/A + * integer + * 443 + * * *

*

@@ -415,6 +429,11 @@ } /** + * @see #setHttpServerPort(int) + */ + private int httpServerPort = 80; + + /** * @see #setHttpsServerPort(int) */ private int httpsServerPort = 443; @@ -456,6 +475,10 @@ return httpsServerPort; } + public int getHttpServerPort() { + return httpServerPort; + } + /** * Return descriptive information about this Valve implementation. */ @@ -507,7 +530,7 @@ public String getRemoteIpHeader() { return remoteIpHeader; } - + /** * @see #setTrustedProxies(String) * @return comma delimited list of trusted proxies @@ -580,12 +603,21 @@ if (protocolHeader != null) { String protocolHeaderValue = request.getHeader(protocolHeader); - if (protocolHeaderValue != null && protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) { + if (protocolHeaderValue == null) { + // don't modify the secure,scheme and serverPort attributes + // of the request + } else if (protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) { request.setSecure(true); // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0 request.getCoyoteRequest().scheme().setString("https"); request.setServerPort(httpsServerPort); + } else { + request.setSecure(false); + // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0 + request.getCoyoteRequest().scheme().setString("http"); + + request.setServerPort(httpServerPort); } } @@ -613,6 +645,18 @@ /** *

+ * Server Port value if the {@link #protocolHeader} is not null and does not indicate HTTP + *

+ *

+ * Default value : 80 + *

+ */ + public void setHttpServerPort(int httpServerPort) { + this.httpServerPort = httpServerPort; + } + + /** + *

* Server Port value if the {@link #protocolHeader} indicates HTTPS *

*

--- java/org/apache/catalina/valves/mbeans-descriptors.xml (revision 905127) +++ java/org/apache/catalina/valves/mbeans-descriptors.xml (working copy) @@ -365,7 +365,17 @@ description="Comma delimited list of internal proxies" type="java.lang.String" writeable="false" /> - + + + + + - --- webapps/docs/config/valve.xml (revision 905127) +++ webapps/docs/config/valve.xml (working copy) @@ -622,9 +622,10 @@ via a request headers (e.g. "X-Forwarded-For").

Another feature of this valve is to replace the apparent scheme - (http/https) and server port with the scheme presented by a proxy or a load - balancer via a request header (e.g. "X-Forwarded-Proto").

- + (http/https), server port and request.secure with the scheme presented + by a proxy or a load balancer via a request header + (e.g. "X-Forwarded-Proto").

+

This Valve may be used at the Engine, Host or Context level as required. Normally, this Valve would be used at the Engine level.

@@ -690,6 +691,20 @@ used.

+ +

Value returned by ServletRequest.getServerPort() + when the protocolHeader indicates http + protocol. If not specified, the default of 80 is + used.

+
+ + +

Value returned by ServletRequest.getServerPort() + when the protocolHeader indicates https + protocol. If not specified, the default of 443 is + used.

+
+