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 988147ce9d66e48f57de430f81bb373d14777a15
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/changelog.xml                         |   4 +
 webapps/docs/config/valve.xml                      |  52 +++++++
 4 files changed, 266 insertions(+)

diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java 
b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
index 92cb4e2..110391a 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..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 62cc09a..fad4a03 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -48,6 +48,10 @@
   <subsection name="Catalina">
     <changelog>
       <add>
+        <bug>62496</bug>: Add option 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 5abc2c3..5e05287 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&apos; 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

Reply via email to