HADOOP-18120. Hadoop auth does not handle HTTP Headers in a case-insensitive way. Contributed by Janos Makai.
This commit is contained in:
parent
78008bc0ee
commit
0773fae392
@ -116,6 +116,7 @@ private List<String> getHeaderValues(String name, boolean reset) {
|
|||||||
public void addCookie(Cookie cookie) {
|
public void addCookie(Cookie cookie) {
|
||||||
super.addCookie(cookie);
|
super.addCookie(cookie);
|
||||||
List<String> cookies = getHeaderValues("Set-Cookie", false);
|
List<String> cookies = getHeaderValues("Set-Cookie", false);
|
||||||
|
cookies.addAll(getHeaderValues("set-cookie", false));
|
||||||
cookies.add(cookie.getName() + "=" + cookie.getValue());
|
cookies.add(cookie.getName() + "=" + cookie.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,9 @@ public synchronized Map<String, List<String>> get(URI uri,
|
|||||||
@Override
|
@Override
|
||||||
public void put(URI uri, Map<String, List<String>> responseHeaders) {
|
public void put(URI uri, Map<String, List<String>> responseHeaders) {
|
||||||
List<String> headers = responseHeaders.get("Set-Cookie");
|
List<String> headers = responseHeaders.get("Set-Cookie");
|
||||||
|
if (headers == null) {
|
||||||
|
headers = responseHeaders.get("set-cookie");
|
||||||
|
}
|
||||||
if (headers != null) {
|
if (headers != null) {
|
||||||
for (String header : headers) {
|
for (String header : headers) {
|
||||||
List<HttpCookie> cookies;
|
List<HttpCookie> cookies;
|
||||||
|
@ -280,6 +280,9 @@ private boolean isNegotiate(HttpURLConnection conn) throws IOException {
|
|||||||
boolean negotiate = false;
|
boolean negotiate = false;
|
||||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
||||||
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
|
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
|
||||||
|
if (authHeader == null) {
|
||||||
|
authHeader = conn.getHeaderField(WWW_AUTHENTICATE.toLowerCase());
|
||||||
|
}
|
||||||
negotiate = authHeader != null && authHeader.trim().startsWith(NEGOTIATE);
|
negotiate = authHeader != null && authHeader.trim().startsWith(NEGOTIATE);
|
||||||
}
|
}
|
||||||
return negotiate;
|
return negotiate;
|
||||||
@ -388,6 +391,9 @@ private byte[] readToken(HttpURLConnection conn)
|
|||||||
int status = conn.getResponseCode();
|
int status = conn.getResponseCode();
|
||||||
if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
||||||
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
|
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
|
||||||
|
if (authHeader == null) {
|
||||||
|
authHeader = conn.getHeaderField(WWW_AUTHENTICATE.toLowerCase());
|
||||||
|
}
|
||||||
if (authHeader == null || !authHeader.trim().startsWith(NEGOTIATE)) {
|
if (authHeader == null || !authHeader.trim().startsWith(NEGOTIATE)) {
|
||||||
throw new AuthenticationException("Invalid SPNEGO sequence, '" + WWW_AUTHENTICATE +
|
throw new AuthenticationException("Invalid SPNEGO sequence, '" + WWW_AUTHENTICATE +
|
||||||
"' header incorrect: " + authHeader);
|
"' header incorrect: " + authHeader);
|
||||||
|
@ -616,7 +616,9 @@ && getMaxInactiveInterval() > 0) {
|
|||||||
// present.. reset to 403 if not found..
|
// present.. reset to 403 if not found..
|
||||||
if ((errCode == HttpServletResponse.SC_UNAUTHORIZED)
|
if ((errCode == HttpServletResponse.SC_UNAUTHORIZED)
|
||||||
&& (!httpResponse.containsHeader(
|
&& (!httpResponse.containsHeader(
|
||||||
KerberosAuthenticator.WWW_AUTHENTICATE))) {
|
KerberosAuthenticator.WWW_AUTHENTICATE)
|
||||||
|
&& !httpResponse.containsHeader(
|
||||||
|
KerberosAuthenticator.WWW_AUTHENTICATE.toLowerCase()))) {
|
||||||
errCode = HttpServletResponse.SC_FORBIDDEN;
|
errCode = HttpServletResponse.SC_FORBIDDEN;
|
||||||
}
|
}
|
||||||
// After Jetty 9.4.21, sendError() no longer allows a custom message.
|
// After Jetty 9.4.21, sendError() no longer allows a custom message.
|
||||||
|
@ -89,6 +89,44 @@ public void testExtractTokenFail() throws Exception {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void testConnectionConfigurator() throws Exception {
|
public void testConnectionConfigurator() throws Exception {
|
||||||
HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
|
HttpURLConnection conn = Mockito.mock(HttpURLConnection.class);
|
||||||
|
@ -21,8 +21,13 @@
|
|||||||
import static org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler.NAME_RULES;
|
import static org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler.NAME_RULES;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.nio.charset.CharacterCodingException;
|
import java.nio.charset.CharacterCodingException;
|
||||||
import javax.security.sasl.AuthenticationException;
|
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.minikdc.KerberosSecurityTestcase;
|
||||||
import org.apache.hadoop.security.authentication.KerberosTestUtils;
|
import org.apache.hadoop.security.authentication.KerberosTestUtils;
|
||||||
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
|
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
|
||||||
@ -32,10 +37,12 @@
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
@ -248,4 +255,79 @@ public void testWrapExceptionWithMessage() {
|
|||||||
Assert.assertTrue(ex.equals(ex2));
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,6 +574,44 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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,
|
private void _testDoFilterAuthentication(boolean withDomainPath,
|
||||||
boolean invalidToken,
|
boolean invalidToken,
|
||||||
boolean expired) throws Exception {
|
boolean expired) throws Exception {
|
||||||
|
Loading…
Reference in New Issue
Block a user