Author: markt
Date: Fri Sep  1 19:51:42 2017
New Revision: 1807004

URL: http://svn.apache.org/viewvc?rev=1807004&view=rev
Log:
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=61280
Add RFC 7617 support to the BasicAuthenticator

Modified:
    tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java
    tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties
    tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java
    tomcat/trunk/webapps/docs/changelog.xml
    tomcat/trunk/webapps/docs/config/valve.xml

Modified: 
tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java?rev=1807004&r1=1807003&r2=1807004&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java 
Fri Sep  1 19:51:42 2017
@@ -20,6 +20,7 @@ package org.apache.catalina.authenticato
 
 
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.security.Principal;
 
@@ -43,10 +44,30 @@ import org.apache.tomcat.util.codec.bina
  * @author Craig R. McClanahan
  */
 public class BasicAuthenticator extends AuthenticatorBase {
+
     private static final Log log = LogFactory.getLog(BasicAuthenticator.class);
 
+    private Charset charset = StandardCharsets.UTF_8;
+    private String charsetString = "UTF-8";
+
+
+    public String getCharset() {
+        return charsetString;
+    }
+
+
+    public void setCharset(String charsetString) {
+        // Only acceptable options are null, "" or "UTF-8" (case insensitive)
+        if (charsetString == null || charsetString.isEmpty()) {
+            charset = StandardCharsets.ISO_8859_1;
+        } else if ("UTF-8".equalsIgnoreCase(charsetString)) {
+            charset = StandardCharsets.UTF_8;
+        } else {
+            throw new 
IllegalArgumentException(sm.getString("basicAuthenticator.invalidCharset"));
+        }
+        this.charsetString = charsetString;
+    }
 
-    // --------------------------------------------------------- Public Methods
 
     @Override
     protected boolean doAuthenticate(Request request, HttpServletResponse 
response)
@@ -66,7 +87,7 @@ public class BasicAuthenticator extends
             ByteChunk authorizationBC = authorization.getByteChunk();
             BasicCredentials credentials = null;
             try {
-                credentials = new BasicCredentials(authorizationBC);
+                credentials = new BasicCredentials(authorizationBC, charset);
                 String username = credentials.getUsername();
                 String password = credentials.getPassword();
 
@@ -89,6 +110,10 @@ public class BasicAuthenticator extends
         value.append("Basic realm=\"");
         value.append(getRealmName(context));
         value.append('\"');
+        if (charsetString != null && !charsetString.isEmpty()) {
+            value.append(", charset=");
+            value.append(charsetString);
+        }
         response.setHeader(AUTH_HEADER_NAME, value.toString());
         response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
         return false;
@@ -112,28 +137,31 @@ public class BasicAuthenticator extends
         // note: we include single white space as its delimiter
         private static final String METHOD = "basic ";
 
-        private ByteChunk authorization;
-        private int initialOffset;
+        private final Charset charset;
+        private final ByteChunk authorization;
+        private final int initialOffset;
         private int base64blobOffset;
         private int base64blobLength;
 
         private String username = null;
         private String password = null;
-
         /**
          * Parse the HTTP Authorization header for BASIC authentication
          * as per RFC 2617 section 2, and the Base64 encoded credentials
          * as per RFC 2045 section 6.8.
          *
-         * @param input The header value to parse in-place
+         * @param input   The header value to parse in-place
+         * @param charset The character set to use to convert the bytes to a
+         *                string
          *
          * @throws IllegalArgumentException If the header does not conform
          *                                  to RFC 2617
          */
-        public BasicCredentials(ByteChunk input)
-                throws IllegalArgumentException {
+        public BasicCredentials(ByteChunk input, Charset charset) throws 
IllegalArgumentException {
             authorization = input;
             initialOffset = input.getOffset();
+            this.charset = charset;
+
             parseMethod();
             byte[] decoded = parseBase64();
             parseCredentials(decoded);
@@ -210,15 +238,12 @@ public class BasicAuthenticator extends
             }
 
             if (colon < 0) {
-                username = new String(decoded, StandardCharsets.ISO_8859_1);
+                username = new String(decoded, charset);
                 // password will remain null!
             }
             else {
-                username = new String(
-                            decoded, 0, colon, StandardCharsets.ISO_8859_1);
-                password = new String(
-                            decoded, colon + 1, decoded.length - colon - 1,
-                            StandardCharsets.ISO_8859_1);
+                username = new String(decoded, 0, colon, charset);
+                password = new String(decoded, colon + 1, decoded.length - 
colon - 1, charset);
                 // tolerate surplus white space around credentials
                 if (password.length() > 1) {
                     password = password.trim();

Modified: 
tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties?rev=1807004&r1=1807003&r2=1807004&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties 
(original)
+++ tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties 
Fri Sep  1 19:51:42 2017
@@ -32,6 +32,8 @@ authenticator.sessionExpired=The time al
 authenticator.unauthorized=Cannot authenticate with the provided credentials
 authenticator.tomcatPrincipalLogoutFail=Logout with TomcatPrincipal instance 
has failed
 
+basicAuthenticator.invalidCharset=The only permitted values are null, the 
empty string or UTF-8
+
 digestAuthenticator.cacheRemove=A valid entry has been removed from client 
nonce cache to make room for new entries. A replay attack is now possible. To 
prevent the possibility of replay attacks, reduce nonceValidity or increase 
cnonceCacheSize. Further warnings of this type will be suppressed for 5 minutes.
 
 formAuthenticator.forwardErrorFail=Unexpected error forwarding to error page

Modified: 
tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java?rev=1807004&r1=1807003&r2=1807004&view=diff
==============================================================================
--- 
tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java 
(original)
+++ 
tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java 
Fri Sep  1 19:51:42 2017
@@ -45,7 +45,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -56,7 +56,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, null);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertNull(credentials.getPassword());
     }
@@ -68,7 +68,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -80,7 +80,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertNull(credentials.getPassword());
     }
@@ -93,7 +93,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD1, credentials.getPassword());
     }
@@ -119,7 +119,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_LONG, credentials.getUsername());
     }
 
@@ -141,7 +141,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_LONG, credentials.getUsername());
     }
 
@@ -157,7 +157,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(METHOD, USER_NAME, PASSWORD);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -174,7 +174,7 @@ public class TestBasicAuthParser {
         BasicAuthenticator.BasicCredentials credentials = null;
         try {
             credentials = new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
             Assert.fail("IllegalArgumentException expected");
         }
         catch (Exception e) {
@@ -197,7 +197,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD + " ", USER_NAME, PASSWORD);
         final BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -213,7 +213,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, PWD_WRONG);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertNotSame(PASSWORD, credentials.getPassword());
     }
@@ -225,7 +225,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, EMPTY_USER_NAME, PASSWORD);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(EMPTY_USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -237,7 +237,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, SHORT_USER_NAME, PASSWORD);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(SHORT_USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -249,7 +249,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, SHORT_PASSWORD);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(SHORT_PASSWORD, credentials.getPassword());
     }
@@ -261,7 +261,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_SPACE);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD_SPACE, credentials.getPassword());
     }
@@ -273,7 +273,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_COLON);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD_COLON, credentials.getPassword());
     }
@@ -285,7 +285,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_COLON);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD_COLON, credentials.getPassword());
     }
@@ -297,7 +297,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_COLON);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD_COLON, credentials.getPassword());
     }
@@ -315,7 +315,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD, "    ");
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -334,7 +334,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, " " + USER_NAME + " ", 
PASSWORD);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                AUTH_HEADER.getHeader());
+                AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -353,7 +353,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, USER_NAME, " " + PASSWORD + " 
");
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                    AUTH_HEADER.getHeader());
+                    AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -378,7 +378,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                    AUTH_HEADER.getHeader());
+                    AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertNotSame(PASSWORD, credentials.getPassword());
         Assert.assertEquals(TRUNCATED_PWD, credentials.getPassword());
@@ -396,7 +396,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                    AUTH_HEADER.getHeader());
+                    AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertNotSame(PASSWORD, credentials.getPassword());
     }
@@ -416,7 +416,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                    AUTH_HEADER.getHeader());
+                    AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(POSSIBLY_DAMAGED_PWD, credentials.getPassword());
     }
@@ -433,7 +433,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                    AUTH_HEADER.getHeader());
+                    AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
@@ -452,7 +452,7 @@ public class TestBasicAuthParser {
                 new BasicAuthHeader(NICE_METHOD, BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new BasicAuthenticator.BasicCredentials(
-                    AUTH_HEADER.getHeader());
+                    AUTH_HEADER.getHeader(), StandardCharsets.UTF_8);
         Assert.assertEquals(USER_NAME, credentials.getUsername());
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1807004&r1=1807003&r2=1807004&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Fri Sep  1 19:51:42 2017
@@ -77,6 +77,10 @@
         added in Java 9 to only disable the caching for JAR URL connections.
         (markt)
       </add>
+      <add>
+        <bug>61280</bug>: Add RFC 7617 support to the
+        <code>BasicAuthenticator</code>. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">

Modified: tomcat/trunk/webapps/docs/config/valve.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/valve.xml?rev=1807004&r1=1807003&r2=1807004&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/valve.xml (original)
+++ tomcat/trunk/webapps/docs/config/valve.xml Fri Sep  1 19:51:42 2017
@@ -1098,6 +1098,19 @@
         used.</p>
       </attribute>
 
+      <attribute name="charset" required="false">
+        <p>Controls if the <code>WWW-Authenticate</code> HTTP header includes a
+        <code>charset</code> authentication parameter as per RFC 7617. The only
+        permitted options are <code>null</code>, the empty string and
+        <code>UTF-8</code>. If <code>UTF-8</code> is specified then the
+        <code>charset</code> authentication parameter will be sent with that
+        value and the provided user name and optional password will be 
converted
+        from bytes to characters using UTF-8. Otherwise, no 
<code>charset</code>
+        authentication parameter will be sent and the provided user name and
+        optional password will be converted from bytes to characters using
+        ISO-8859-1. The default value is <code>UTF-8</code></p>
+      </attribute>
+
       <attribute name="className" required="true">
         <p>Java class name of the implementation to use.  This MUST be set to
         
<strong>org.apache.catalina.authenticator.BasicAuthenticator</strong>.</p>



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to