This is an automated email from the ASF dual-hosted git repository. michaelo pushed a commit to branch BZ-62496/tomcat-9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit c64803e1fa4f2ac74b45eaa39e59a52cc4684a2f Author: Michael Osipov <micha...@apache.org> AuthorDate: Wed Jul 31 13:39:35 2019 +0200 BZ 62496: Add possibility write remote user/auth type to response header --- .../catalina/authenticator/AuthenticatorBase.java | 41 +++++ .../authenticator/TestAuthInfoResponseHeaders.java | 169 +++++++++++++++++++++ webapps/docs/config/valve.xml | 52 +++++++ 3 files changed, 262 insertions(+) diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java b/java/org/apache/catalina/authenticator/AuthenticatorBase.java index 92cb4e2..3928b54 100644 --- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java +++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java @@ -53,9 +53,11 @@ import org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl; import org.apache.catalina.authenticator.jaspic.MessageInfoImpl; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; +import org.apache.catalina.filters.RemoteIpFilter; import org.apache.catalina.realm.GenericPrincipal; import org.apache.catalina.util.SessionIdGeneratorBase; import org.apache.catalina.util.StandardSessionIdGenerator; +import org.apache.catalina.valves.RemoteIpValve; import org.apache.catalina.valves.ValveBase; import org.apache.coyote.ActionCode; import org.apache.juli.logging.Log; @@ -214,6 +216,19 @@ public abstract class AuthenticatorBase extends ValveBase */ protected String jaspicCallbackHandlerClass = null; + /** + * Should the auth information (remote user and auth type) be returned as response + * headers for a forwarded/proxied request? When the {@link RemoteIpValve} or + * {@link RemoteIpFilter} mark a forwarded request with the + * {@link Globals#REQUEST_FORWARDED_ATTRIBUTE} this authenticator can return the + * values of {@link HttpServletRequest#getRemoteUser()} and + * {@link HttpServletRequest#getAuthType()} as reponse headers {@code Remote-User} + * and {@code Auth-Type} to a reverse proxy. This is useful, e.g., for access log + * consistency or other decisions to make. + */ + + protected boolean sendAuthInfoResponseHeaders = false; + protected SessionIdGeneratorBase sessionIdGenerator = null; /** @@ -429,6 +444,26 @@ public abstract class AuthenticatorBase extends ValveBase this.jaspicCallbackHandlerClass = jaspicCallbackHandlerClass; } + /** + * Returns the flag whether authentication information will be sent to a reverse + * proxy on a forwarded request. + * + * @return {@code true} if response headers shall be sent, {@code false} otherwise + */ + public boolean isSendAuthInfoResponseHeaders() { + return sendAuthInfoResponseHeaders; + } + + /** + * Sets the flag whether authentication information will be send to a reverse + * proxy on a forwarded request. + * + * @param {@code true} if response headers shall be sent, {@code false} otherwise + */ + public void setSendAuthInfoResponseHeaders(boolean sendAuthInfoResponseHeaders) { + this.sendAuthInfoResponseHeaders = sendAuthInfoResponseHeaders; + } + // --------------------------------------------------------- Public Methods /** @@ -997,6 +1032,12 @@ public abstract class AuthenticatorBase extends ValveBase request.setAuthType(authType); request.setUserPrincipal(principal); + if (sendAuthInfoResponseHeaders + && Boolean.TRUE.equals(request.getAttribute(Globals.REQUEST_FORWARDED_ATTRIBUTE))) { + response.setHeader("Remote-User", request.getRemoteUser()); + response.setHeader("Auth-Type", request.getAuthType()); + } + Session session = request.getSessionInternal(false); if (session != null) { diff --git a/test/org/apache/catalina/authenticator/TestAuthInfoResponseHeaders.java b/test/org/apache/catalina/authenticator/TestAuthInfoResponseHeaders.java new file mode 100644 index 0000000..c607262 --- /dev/null +++ b/test/org/apache/catalina/authenticator/TestAuthInfoResponseHeaders.java @@ -0,0 +1,169 @@ +/* + * 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.authenticator; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.TesterMapRealm; +import org.apache.catalina.startup.TesterServlet; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.catalina.valves.RemoteIpValve; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.codec.binary.Base64; +import org.apache.tomcat.util.descriptor.web.LoginConfig; +import org.apache.tomcat.util.descriptor.web.SecurityCollection; +import org.apache.tomcat.util.descriptor.web.SecurityConstraint; +import org.junit.Assert; +import org.junit.Test; + +public class TestAuthInfoResponseHeaders extends TomcatBaseTest { + + private static String USER = "user"; + private static String PWD = "pwd"; + private static String ROLE = "role"; + private static String URI = "/protected"; + private static String CONTEXT_PATH = "/foo"; + private static String CLIENT_AUTH_HEADER = "authorization"; + + /* + * Encapsulate the logic to generate an HTTP header + * for BASIC Authentication. + * Note: only used internally, so no need to validate arguments. + */ + private static final class BasicCredentials { + + private final String method; + private final String username; + private final String password; + private final String credentials; + + private BasicCredentials(String aMethod, + String aUsername, String aPassword) { + method = aMethod; + username = aUsername; + password = aPassword; + String userCredentials = username + ":" + password; + byte[] credentialsBytes = + userCredentials.getBytes(StandardCharsets.ISO_8859_1); + String base64auth = Base64.encodeBase64String(credentialsBytes); + credentials= method + " " + base64auth; + } + + private String getCredentials() { + return credentials; + } + } + + @Test + public void testNoHeaders() throws Exception { + doTest(USER, PWD, CONTEXT_PATH + URI, false); + } + + @Test + public void testWithHeaders() throws Exception { + doTest(USER, PWD, CONTEXT_PATH + URI, true); + } + + public void doTest(String user, String pwd, String uri, boolean expectResponseAuthHeaders) + throws Exception { + + if (expectResponseAuthHeaders) { + BasicAuthenticator auth = + (BasicAuthenticator) getTomcatInstance().getHost().findChild( + CONTEXT_PATH).getPipeline().getFirst(); + auth.setSendAuthInfoResponseHeaders(true); + } + getTomcatInstance().start(); + + Map<String,List<String>> reqHeaders = new HashMap<>(); + + List<String> auth = new ArrayList<>(); + auth.add(new BasicCredentials("Basic", USER, PWD).getCredentials()); + reqHeaders.put(CLIENT_AUTH_HEADER, auth); + + List<String> forwardedFor = new ArrayList<>(); + forwardedFor.add("192.168.0.10"); + List<String> forwardedHost = new ArrayList<>(); + forwardedHost.add("localhost"); + reqHeaders.put("X-Forwarded-For", forwardedFor); + reqHeaders.put("X-Forwarded-Host", forwardedHost); + + Map<String,List<String>> respHeaders = new HashMap<>(); + + ByteChunk bc = new ByteChunk(); + int rc = getUrl("http://localhost:" + getPort() + uri, bc, reqHeaders, + respHeaders); + Assert.assertEquals(200, rc); + Assert.assertEquals("OK", bc.toString()); + + if (expectResponseAuthHeaders) { + List<String> remoteUsers = respHeaders.get("Remote-User"); + Assert.assertNotNull(remoteUsers); + Assert.assertEquals(USER, remoteUsers.get(0)); + List<String> authTypes = respHeaders.get("Auth-Type"); + Assert.assertNotNull(authTypes); + Assert.assertEquals(HttpServletRequest.BASIC_AUTH, authTypes.get(0)); + } else { + Assert.assertFalse(respHeaders.containsKey("Remote-User")); + Assert.assertFalse(respHeaders.containsKey("Auth-Type")); + } + + bc.recycle(); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + + // Configure a context with digest auth and a single protected resource + Tomcat tomcat = getTomcatInstance(); + tomcat.getHost().getPipeline().addValve(new RemoteIpValve()); + + // No file system docBase required + Context ctxt = tomcat.addContext(CONTEXT_PATH, null); + + // Add protected servlet + Tomcat.addServlet(ctxt, "TesterServlet", new TesterServlet()); + ctxt.addServletMappingDecoded(URI, "TesterServlet"); + SecurityCollection collection = new SecurityCollection(); + collection.addPatternDecoded(URI); + SecurityConstraint sc = new SecurityConstraint(); + sc.addAuthRole(ROLE); + sc.addCollection(collection); + ctxt.addConstraint(sc); + + // Configure the Realm + TesterMapRealm realm = new TesterMapRealm(); + realm.addUser(USER, PWD); + realm.addUserRole(USER, ROLE); + ctxt.setRealm(realm); + + // Configure the authenticator + LoginConfig lc = new LoginConfig(); + lc.setAuthMethod(HttpServletRequest.BASIC_AUTH); + ctxt.setLoginConfig(lc); + ctxt.getPipeline().addValve(new BasicAuthenticator()); + } +} diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml index 5abc2c3..d0b1c2b 100644 --- a/webapps/docs/config/valve.xml +++ b/webapps/docs/config/valve.xml @@ -1289,6 +1289,19 @@ specified, the platform default provider will be used.</p> </attribute> + <attribute name="sendAuthInfoResponseHeaders" required="false"> + <p>Controls whether the auth information (remote user and auth type) + shall be returned as response headers for a forwarded/proxied request. + When the <code>RemoteIpValve</code> or <code>RemoteIpFilter</code> mark + a forwarded request with the <code>Globals.REQUEST_FORWARDED_ATTRIBUTE</code> + this authenticator can return the values of + <code>HttpServletRequest.getRemoteUser()</code> and + <code>HttpServletRequest.getAuthType()</code> as response headers + <code>Remote-User</code> and <code>Auth-Type</code> to a reverse proxy. + This is useful, e.g., for access log consistency or other decisions to make. + If not specified, the default value is <code>false</code>.</p> + </attribute> + <attribute name="trimCredentials" required="false"> <p>Controls whether leading and/or trailing whitespace is removed from the parsed credentials. If not specified, the default value is @@ -1441,6 +1454,19 @@ specified, the platform default provider will be used.</p> </attribute> + <attribute name="sendAuthInfoResponseHeaders" required="false"> + <p>Controls whether the auth information (remote user and auth type) + shall be returned as response headers for a forwarded/proxied request. + When the <code>RemoteIpValve</code> or <code>RemoteIpFilter</code> mark + a forwarded request with the <code>Globals.REQUEST_FORWARDED_ATTRIBUTE</code> + this authenticator can return the values of + <code>HttpServletRequest.getRemoteUser()</code> and + <code>HttpServletRequest.getAuthType()</code> as response headers + <code>Remote-User</code> and <code>Auth-Type</code> to a reverse proxy. + This is useful, e.g., for access log consistency or other decisions to make. + If not specified, the default value is <code>false</code>.</p> + </attribute> + <attribute name="validateUri" required="false"> <p>Should the URI be validated as required by RFC2617? If not specified, the default value of <code>true</code> will be used. This should @@ -1562,6 +1588,19 @@ specified, the platform default provider will be used.</p> </attribute> + <attribute name="sendAuthInfoResponseHeaders" required="false"> + <p>Controls whether the auth information (remote user and auth type) + shall be returned as response headers for a forwarded/proxied request. + When the <code>RemoteIpValve</code> or <code>RemoteIpFilter</code> mark + a forwarded request with the <code>Globals.REQUEST_FORWARDED_ATTRIBUTE</code> + this authenticator can return the values of + <code>HttpServletRequest.getRemoteUser()</code> and + <code>HttpServletRequest.getAuthType()</code> as response headers + <code>Remote-User</code> and <code>Auth-Type</code> to a reverse proxy. + This is useful, e.g., for access log consistency or other decisions to make. + If not specified, the default value is <code>false</code>.</p> + </attribute> + <attribute name="jaspicCallbackHandlerClass" required="false"> <p>Name of the Java class of the <code>javax.security.auth.callback.CallbackHandler</code> implementation @@ -1811,6 +1850,19 @@ specified, the platform default provider will be used.</p> </attribute> + <attribute name="sendAuthInfoResponseHeaders" required="false"> + <p>Controls whether the auth information (remote user and auth type) + shall be returned as response headers for a forwarded/proxied request. + When the <code>RemoteIpValve</code> or <code>RemoteIpFilter</code> mark + a forwarded request with the <code>Globals.REQUEST_FORWARDED_ATTRIBUTE</code> + this authenticator can return the values of + <code>HttpServletRequest.getRemoteUser()</code> and + <code>HttpServletRequest.getAuthType()</code> as response headers + <code>Remote-User</code> and <code>Auth-Type</code> to a reverse proxy. + This is useful, e.g., for access log consistency or other decisions to make. + If not specified, the default value is <code>false</code>.</p> + </attribute> + <attribute name="storeDelegatedCredential" required="false"> <p>Controls if the user' delegated credential will be stored in the user Principal. If available, the delegated credential will be --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org