diff --git a/hadoop-common-project/hadoop-auth-examples/src/main/java/org/apache/hadoop/security/authentication/examples/RequestLoggerFilter.java b/hadoop-common-project/hadoop-auth-examples/src/main/java/org/apache/hadoop/security/authentication/examples/RequestLoggerFilter.java index 80489917e08..e96e5062919 100644 --- a/hadoop-common-project/hadoop-auth-examples/src/main/java/org/apache/hadoop/security/authentication/examples/RequestLoggerFilter.java +++ b/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 cookies = getHeaderValues("Set-Cookie", false); + cookies.addAll(getHeaderValues("set-cookie", false)); cookies.add(cookie.getName() + "=" + cookie.getValue()); } diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java index 32f4edfbc57..cb7d36368aa 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java +++ b/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> responseHeaders) { List headers = responseHeaders.get("Set-Cookie"); + if (headers == null) { + headers = responseHeaders.get("set-cookie"); + } if (headers != null) { for (String header : headers) { List cookies; diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java index 06b63c1b991..30e65efe10c 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java +++ b/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); diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java index 3658bd8b8ec..7cc70c493c0 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java +++ b/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. diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestAuthenticatedURL.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestAuthenticatedURL.java index b56fc65b25b..5b3de5208f4 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestAuthenticatedURL.java +++ b/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> headers = new HashMap<>(); + List 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> headers = new HashMap<>(); + List 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); diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java index 177bcb4547e..0d8f1c04137 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java +++ b/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 + } } diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java index 4f4a4521b2f..b0066abbdd3 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java +++ b/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) 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 {