Przeglądaj źródła

HADOOP-18120. Hadoop auth does not handle HTTP Headers in a case-insensitive way. Contributed by Janos Makai.

9uapaw 2 lat temu
rodzic
commit
0773fae392

+ 1 - 0
hadoop-common-project/hadoop-auth-examples/src/main/java/org/apache/hadoop/security/authentication/examples/RequestLoggerFilter.java

@@ -116,6 +116,7 @@ public class RequestLoggerFilter implements Filter {
     public void addCookie(Cookie cookie) {
       super.addCookie(cookie);
       List<String> cookies = getHeaderValues("Set-Cookie", false);
+      cookies.addAll(getHeaderValues("set-cookie", false));
       cookies.add(cookie.getName() + "=" + cookie.getValue());
     }
 

+ 3 - 0
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java

@@ -92,6 +92,9 @@ public class AuthenticatedURL {
     @Override
     public void put(URI uri, Map<String, List<String>> responseHeaders) {
       List<String> headers = responseHeaders.get("Set-Cookie");
+      if (headers == null) {
+        headers = responseHeaders.get("set-cookie");
+      }
       if (headers != null) {
         for (String header : headers) {
           List<HttpCookie> cookies;

+ 6 - 0
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java

@@ -280,6 +280,9 @@ public class KerberosAuthenticator implements Authenticator {
     boolean negotiate = false;
     if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
       String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
+      if (authHeader == null) {
+        authHeader = conn.getHeaderField(WWW_AUTHENTICATE.toLowerCase());
+      }
       negotiate = authHeader != null && authHeader.trim().startsWith(NEGOTIATE);
     }
     return negotiate;
@@ -388,6 +391,9 @@ public class KerberosAuthenticator implements Authenticator {
     int status = conn.getResponseCode();
     if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_UNAUTHORIZED) {
       String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
+      if (authHeader == null) {
+        authHeader = conn.getHeaderField(WWW_AUTHENTICATE.toLowerCase());
+      }
       if (authHeader == null || !authHeader.trim().startsWith(NEGOTIATE)) {
         throw new AuthenticationException("Invalid SPNEGO sequence, '" + WWW_AUTHENTICATE +
                                           "' header incorrect: " + authHeader);

+ 3 - 1
hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java

@@ -616,7 +616,9 @@ public class AuthenticationFilter implements Filter {
         // present.. reset to 403 if not found..
         if ((errCode == HttpServletResponse.SC_UNAUTHORIZED)
             && (!httpResponse.containsHeader(
-                KerberosAuthenticator.WWW_AUTHENTICATE))) {
+                KerberosAuthenticator.WWW_AUTHENTICATE)
+                && !httpResponse.containsHeader(
+                KerberosAuthenticator.WWW_AUTHENTICATE.toLowerCase()))) {
           errCode = HttpServletResponse.SC_FORBIDDEN;
         }
         // After Jetty 9.4.21, sendError() no longer allows a custom message.

+ 38 - 0
hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestAuthenticatedURL.java

@@ -89,6 +89,44 @@ public class TestAuthenticatedURL {
     }
   }
 
+  @Test
+  public void testExtractTokenCookieHeader() throws Exception {
+    HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
+
+    Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
+
+    String tokenStr = "foo";
+    Map<String, List<String>> headers = new HashMap<>();
+    List<String> cookies = new ArrayList<>();
+    cookies.add(AuthenticatedURL.AUTH_COOKIE + "=" + tokenStr);
+    headers.put("Set-Cookie", cookies);
+    Mockito.when(conn.getHeaderFields()).thenReturn(headers);
+
+    AuthenticatedURL.Token token = new AuthenticatedURL.Token();
+    AuthenticatedURL.extractToken(conn, token);
+
+    Assert.assertTrue(token.isSet());
+  }
+
+  @Test
+  public void testExtractTokenLowerCaseCookieHeader() throws Exception {
+    HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
+
+    Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
+
+    String tokenStr = "foo";
+    Map<String, List<String>> headers = new HashMap<>();
+    List<String> cookies = new ArrayList<>();
+    cookies.add(AuthenticatedURL.AUTH_COOKIE + "=" + tokenStr);
+    headers.put("set-cookie", cookies);
+    Mockito.when(conn.getHeaderFields()).thenReturn(headers);
+
+    AuthenticatedURL.Token token = new AuthenticatedURL.Token();
+    AuthenticatedURL.extractToken(conn, token);
+
+    Assert.assertTrue(token.isSet());
+  }
+
   @Test
   public void testConnectionConfigurator() throws Exception {
     HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);

+ 82 - 0
hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java

@@ -21,8 +21,13 @@ import static org.apache.hadoop.security.authentication.server.KerberosAuthentic
 import static org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler.NAME_RULES;
 
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.nio.charset.CharacterCodingException;
 import javax.security.sasl.AuthenticationException;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.reflect.FieldUtils;
 import org.apache.hadoop.minikdc.KerberosSecurityTestcase;
 import org.apache.hadoop.security.authentication.KerberosTestUtils;
 import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
@@ -32,10 +37,12 @@ import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHa
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import java.io.File;
 import java.net.HttpURLConnection;
 import java.net.URL;
+import java.util.Arrays;
 import java.util.Properties;
 import java.util.concurrent.Callable;
 
@@ -248,4 +255,79 @@ public class TestKerberosAuthenticator extends KerberosSecurityTestcase {
     Assert.assertTrue(ex.equals(ex2));
   }
 
+  @Test(timeout = 60000)
+  public void testNegotiate() throws NoSuchMethodException, InvocationTargetException,
+          IllegalAccessException, IOException {
+    KerberosAuthenticator kerberosAuthenticator = new KerberosAuthenticator();
+
+    HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
+    Mockito.when(conn.getHeaderField(KerberosAuthenticator.WWW_AUTHENTICATE)).
+            thenReturn(KerberosAuthenticator.NEGOTIATE);
+    Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED);
+
+    Method method = KerberosAuthenticator.class.getDeclaredMethod("isNegotiate",
+            HttpURLConnection.class);
+    method.setAccessible(true);
+
+    Assert.assertTrue((boolean)method.invoke(kerberosAuthenticator, conn));
+  }
+
+  @Test(timeout = 60000)
+  public void testNegotiateLowerCase() throws NoSuchMethodException, InvocationTargetException,
+          IllegalAccessException, IOException {
+    KerberosAuthenticator kerberosAuthenticator = new KerberosAuthenticator();
+
+    HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
+    Mockito.when(conn.getHeaderField("www-authenticate"))
+            .thenReturn(KerberosAuthenticator.NEGOTIATE);
+    Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED);
+
+    Method method = KerberosAuthenticator.class.getDeclaredMethod("isNegotiate",
+            HttpURLConnection.class);
+    method.setAccessible(true);
+
+    Assert.assertTrue((boolean)method.invoke(kerberosAuthenticator, conn));
+  }
+
+  @Test(timeout = 60000)
+  public void testReadToken() throws NoSuchMethodException, IOException, IllegalAccessException,
+          InvocationTargetException {
+    KerberosAuthenticator kerberosAuthenticator = new KerberosAuthenticator();
+    FieldUtils.writeField(kerberosAuthenticator, "base64", new Base64(), true);
+
+    Base64 base64 = new Base64();
+
+    HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
+    Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED);
+    Mockito.when(conn.getHeaderField(KerberosAuthenticator.WWW_AUTHENTICATE))
+            .thenReturn(KerberosAuthenticator.NEGOTIATE + " " +
+                    Arrays.toString(base64.encode("foobar".getBytes())));
+
+    Method method = KerberosAuthenticator.class.getDeclaredMethod("readToken",
+            HttpURLConnection.class);
+    method.setAccessible(true);
+
+    method.invoke(kerberosAuthenticator, conn); // expecting this not to throw an exception
+  }
+
+  @Test(timeout = 60000)
+  public void testReadTokenLowerCase() throws NoSuchMethodException, IOException,
+          IllegalAccessException, InvocationTargetException {
+    KerberosAuthenticator kerberosAuthenticator = new KerberosAuthenticator();
+    FieldUtils.writeField(kerberosAuthenticator, "base64", new Base64(), true);
+
+    Base64 base64 = new Base64();
+
+    HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
+    Mockito.when(conn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_UNAUTHORIZED);
+    Mockito.when(conn.getHeaderField("www-authenticate"))
+            .thenReturn(KerberosAuthenticator.NEGOTIATE +
+                    Arrays.toString(base64.encode("foobar".getBytes())));
+
+    Method method = KerberosAuthenticator.class.getDeclaredMethod("readToken",
+            HttpURLConnection.class);
+    method.setAccessible(true);
+
+    method.invoke(kerberosAuthenticator, conn); // expecting this not to throw an exception
+  }
 }

+ 38 - 0
hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java

@@ -574,6 +574,44 @@ public class TestAuthenticationFilter {
     }
   }
 
+  @Test
+  public void testDoFilterNotAuthenticatedLowerCase() throws Exception {
+    AuthenticationFilter filter = new AuthenticationFilter();
+    try {
+      FilterConfig config = Mockito.mock(FilterConfig.class);
+      Mockito.when(config.getInitParameter("management.operation.return")).
+              thenReturn("true");
+      Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn(
+              DummyAuthenticationHandler.class.getName());
+      Mockito.when(config.getInitParameterNames()).thenReturn(
+              new Vector<>(
+                      Arrays.asList(AuthenticationFilter.AUTH_TYPE,
+                              "management.operation.return")).elements());
+      getMockedServletContextWithStringSigner(config);
+      filter.init(config);
+
+      HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+      Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar"));
+
+      HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+
+      FilterChain chain = Mockito.mock(FilterChain.class);
+
+      Mockito.doAnswer((Answer<Object>) invocation -> {
+        Assert.fail();
+        return null;
+      }).when(chain).doFilter(any(), any());
+
+      Mockito.when(response.containsHeader("www-authenticate")).thenReturn(true);
+      filter.doFilter(request, response, chain);
+
+      Mockito.verify(response).sendError(
+              HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
+    } finally {
+      filter.destroy();
+    }
+  }
+
   private void _testDoFilterAuthentication(boolean withDomainPath,
                                            boolean invalidToken,
                                            boolean expired) throws Exception {