This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/7.0.x by this push:
     new e143100  BZ 62496: Add option to write auth information (remote 
user/auth type) to response headers
e143100 is described below

commit e14310099f13e5cfd589a2de41a765aba05afc20
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  |  42 +++++
 .../authenticator/TestAuthInfoResponseHeaders.java | 170 +++++++++++++++++++++
 webapps/docs/changelog.xml                         |   4 +
 webapps/docs/config/valve.xml                      |  51 ++++++-
 4 files changed, 266 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java 
b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
index a80c4dd..5399716 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,19 @@ public abstract class AuthenticatorBase extends ValveBase
      */
     protected String secureRandomProvider = 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 +401,27 @@ 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 sendAuthInfoResponseHeaders {@code true} if response headers 
shall be
+     *                                    sent, {@code false} otherwise
+     */
+    public void setSendAuthInfoResponseHeaders(boolean 
sendAuthInfoResponseHeaders) {
+        this.sendAuthInfoResponseHeaders = sendAuthInfoResponseHeaders;
+    }
+
     // --------------------------------------------------------- Public Methods
 
     /**
@@ -780,6 +816,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..9004744
--- /dev/null
+++ b/test/org/apache/catalina/authenticator/TestAuthInfoResponseHeaders.java
@@ -0,0 +1,170 @@
+/*
+ *  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.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.deploy.SecurityCollection;
+import org.apache.catalina.deploy.SecurityConstraint;
+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;
+
+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<String,List<String>>();
+
+        List<String> auth = new ArrayList<String>();
+        auth.add(new BasicCredentials("Basic", user, pwd).getCredentials());
+        reqHeaders.put(CLIENT_AUTH_HEADER, auth);
+
+        List<String> forwardedFor = new ArrayList<String>();
+        forwardedFor.add("192.168.0.10");
+        List<String> forwardedHost = new ArrayList<String>();
+        forwardedHost.add("localhost");
+        reqHeaders.put("X-Forwarded-For", forwardedFor);
+        reqHeaders.put("X-Forwarded-Host", forwardedHost);
+
+        Map<String,List<String>> respHeaders = new 
HashMap<String,List<String>>();
+
+        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.addServletMapping(URI, "TesterServlet");
+        SecurityCollection collection = new SecurityCollection();
+        collection.addPattern(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..288020e 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -1190,6 +1190,18 @@
         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>
     </attributes>
 
   </subsection>
@@ -1328,6 +1340,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
@@ -1335,7 +1360,6 @@
         the proxy is modifying the URI passed to Tomcat such that DIGEST
         authentication always fails.</p>
       </attribute>
-
     </attributes>
 
   </subsection>
@@ -1441,6 +1465,18 @@
         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>
     </attributes>
 
   </subsection>
@@ -1674,6 +1710,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