This is an automated email from the ASF dual-hosted git repository. michaelo pushed a commit to branch BZ-62496/tomcat-7.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit e764e8562be067bad3efea0e0ff98e81e38e342b Author: Michael Osipov <micha...@apache.org> AuthorDate: Wed Jul 31 13:39:35 2019 +0200 BZ 62496: Add option to write auth information (remote user/auth type) to response headers --- .../catalina/authenticator/AuthenticatorBase.java | 48 ++++++ .../authenticator/TestAuthInfoResponseHeaders.java | 169 +++++++++++++++++++++ webapps/docs/changelog.xml | 4 + webapps/docs/config/valve.xml | 74 +++++++++ 4 files changed, 295 insertions(+) diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java b/java/org/apache/catalina/authenticator/AuthenticatorBase.java index a80c4dd..7f5c03d 100644 --- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java +++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java @@ -38,9 +38,11 @@ import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.deploy.LoginConfig; import org.apache.catalina.deploy.SecurityConstraint; +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; @@ -177,6 +179,26 @@ public abstract class AuthenticatorBase extends ValveBase */ protected String secureRandomProvider = null; + /** + * The name of the JASPIC callback handler class. If none is specified the + * default {@link org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl} + * will be used. + */ + 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; /** @@ -386,6 +408,26 @@ public abstract class AuthenticatorBase extends ValveBase + /** + * 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 /** @@ -780,6 +822,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..881c37b --- /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/changelog.xml b/webapps/docs/changelog.xml index 17ce73e..1eb3a3c 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -63,6 +63,10 @@ <subsection name="Catalina"> <changelog> <add> + <bug>62496</bug>: Add option to write auth information (remote user/auth type) + to response headers. (michaelo) + </add> + <add> <bug>57665</bug>: Add support for the <code>X-Forwarded-Host</code> header to the <code>RemoteIpFilter</code> and <code>RemotepValve</code>. (markt) diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml index e5d50c9..6064943 100644 --- a/webapps/docs/config/valve.xml +++ b/webapps/docs/config/valve.xml @@ -1190,6 +1190,33 @@ 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 + <code>true</code>.</p> + </attribute> + + <attribute name="jaspicCallbackHandlerClass" required="false"> + <p>Name of the Java class of the + <code>javax.security.auth.callback.CallbackHandler</code> implementation + which should be used by JASPIC. If none is specified the default + <code>org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl</code> + will be used.</p> + </attribute> + </attributes> </subsection> @@ -1328,6 +1355,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 @@ -1441,6 +1481,27 @@ 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 + which should be used by JASPIC. If none is specified the default + <code>org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl</code> + will be used.</p> + </attribute> + </attributes> </subsection> @@ -1674,6 +1735,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