This is an automated email from the ASF dual-hosted git repository.
jihoonson pushed a commit to branch 0.12.2
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git
The following commit(s) were added to refs/heads/0.12.2 by this push:
new 4842e6c Immediately send 401 on basic HTTP authentication failure
(#5856) (#5966)
4842e6c is described below
commit 4842e6c1ee63a8589909db8529468d1e99f138bd
Author: Jihoon Son <[email protected]>
AuthorDate: Mon Jul 9 13:24:45 2018 -0700
Immediately send 401 on basic HTTP authentication failure (#5856) (#5966)
* Immediately send 401 on basic HTTP authentication failure
* Add unit tests
---
.../io/druid/security/basic/BasicAuthUtils.java | 8 +-
.../authentication/BasicHTTPAuthenticator.java | 16 +-
.../authentication/BasicHTTPAuthenticatorTest.java | 261 +++++++++++++++++++++
.../server/security/AuthenticationResult.java | 23 ++
4 files changed, 303 insertions(+), 5 deletions(-)
diff --git
a/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/BasicAuthUtils.java
b/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/BasicAuthUtils.java
index 9b1f92d..5bc80f8 100644
---
a/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/BasicAuthUtils.java
+++
b/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/BasicAuthUtils.java
@@ -117,7 +117,7 @@ public class BasicAuthUtils
}
@Nullable
- public static String getBasicUserSecretFromHttpReq(HttpServletRequest
httpReq)
+ public static String getEncodedUserSecretFromHttpReq(HttpServletRequest
httpReq)
{
String authHeader = httpReq.getHeader("Authorization");
@@ -133,8 +133,12 @@ public class BasicAuthUtils
return null;
}
- String encodedUserSecret = authHeader.substring(6);
+ return authHeader.substring(6);
+ }
+ @Nullable
+ public static String decodeUserSecret(String encodedUserSecret)
+ {
try {
return
StringUtils.fromUtf8(Base64.getDecoder().decode(encodedUserSecret));
}
diff --git
a/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/authentication/BasicHTTPAuthenticator.java
b/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/authentication/BasicHTTPAuthenticator.java
index eeb406e..e895b8c 100644
---
a/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/authentication/BasicHTTPAuthenticator.java
+++
b/extensions-core/druid-basic-security/src/main/java/io/druid/security/basic/authentication/BasicHTTPAuthenticator.java
@@ -155,15 +155,22 @@ public class BasicHTTPAuthenticator implements
Authenticator
) throws IOException, ServletException
{
HttpServletResponse httpResp = (HttpServletResponse) servletResponse;
- String userSecret =
BasicAuthUtils.getBasicUserSecretFromHttpReq((HttpServletRequest)
servletRequest);
- if (userSecret == null) {
+ String encodedUserSecret =
BasicAuthUtils.getEncodedUserSecretFromHttpReq((HttpServletRequest)
servletRequest);
+ if (encodedUserSecret == null) {
// Request didn't have HTTP Basic auth credentials, move on to the
next filter
filterChain.doFilter(servletRequest, servletResponse);
return;
}
- String[] splits = userSecret.split(":");
+ String decodedUserSecret =
BasicAuthUtils.decodeUserSecret(encodedUserSecret);
+ if (decodedUserSecret == null) {
+ // we recognized a Basic auth header, but could not decode the user
secret
+ httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+
+ String[] splits = decodedUserSecret.split(":");
if (splits.length != 2) {
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
@@ -175,6 +182,9 @@ public class BasicHTTPAuthenticator implements Authenticator
if (checkCredentials(user, password)) {
AuthenticationResult authenticationResult = new
AuthenticationResult(user, authorizerName, name, null);
servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT,
authenticationResult);
+ } else {
+ httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
}
filterChain.doFilter(servletRequest, servletResponse);
diff --git
a/extensions-core/druid-basic-security/src/test/java/io/druid/security/authentication/BasicHTTPAuthenticatorTest.java
b/extensions-core/druid-basic-security/src/test/java/io/druid/security/authentication/BasicHTTPAuthenticatorTest.java
new file mode 100644
index 0000000..e78b424
--- /dev/null
+++
b/extensions-core/druid-basic-security/src/test/java/io/druid/security/authentication/BasicHTTPAuthenticatorTest.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Metamarkets 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 io.druid.security.authentication;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import io.druid.java.util.common.StringUtils;
+import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
+import
io.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
+import
io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
+import
io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
+import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
+import io.druid.server.security.AuthConfig;
+import io.druid.server.security.AuthenticationResult;
+import org.asynchttpclient.util.Base64;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Map;
+
+public class BasicHTTPAuthenticatorTest
+{
+ public static BasicAuthenticatorCredentials USER_A_CREDENTIALS = new
BasicAuthenticatorCredentials(
+ new BasicAuthenticatorCredentialUpdate("helloworld", 20)
+ );
+
+ public static Provider<BasicAuthenticatorCacheManager>
CACHE_MANAGER_PROVIDER = Providers.of(
+ new BasicAuthenticatorCacheManager()
+ {
+ @Override
+ public void handleAuthenticatorUpdate(String authenticatorPrefix,
byte[] serializedUserMap)
+ {
+
+ }
+
+ @Override
+ public Map<String, BasicAuthenticatorUser> getUserMap(String
authenticatorPrefix)
+ {
+ return ImmutableMap.of(
+ "userA", new BasicAuthenticatorUser("userA", USER_A_CREDENTIALS)
+ );
+ }
+ }
+ );
+
+ public static BasicHTTPAuthenticator AUTHENTICATOR = new
BasicHTTPAuthenticator(
+ CACHE_MANAGER_PROVIDER,
+ "basic",
+ "basic",
+ "a",
+ "a",
+ false,
+ null,
+ null
+ );
+
+ @Test
+ public void testGoodPassword() throws IOException, ServletException
+ {
+ String header = Base64.encode(
+ StringUtils.toUtf8("userA:helloworld")
+ );
+ header = StringUtils.format("Basic %s", header);
+
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
+ req.setAttribute(
+ AuthConfig.DRUID_AUTHENTICATION_RESULT,
+ new AuthenticationResult("userA", "basic", "basic", null)
+ );
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(req);
+
+ HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
+ EasyMock.replay(resp);
+
+ FilterChain filterChain = EasyMock.createMock(FilterChain.class);
+ filterChain.doFilter(req, resp);
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(filterChain);
+
+ Filter authenticatorFilter = AUTHENTICATOR.getFilter();
+ authenticatorFilter.doFilter(req, resp, filterChain);
+
+ EasyMock.verify(req, resp, filterChain);
+ }
+
+ @Test
+ public void testBadPassword() throws IOException, ServletException
+ {
+ String header = Base64.encode(
+ StringUtils.toUtf8("userA:badpassword")
+ );
+ header = StringUtils.format("Basic %s", header);
+
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
+ EasyMock.replay(req);
+
+ HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
+ resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(resp);
+
+ FilterChain filterChain = EasyMock.createMock(FilterChain.class);
+ EasyMock.replay(filterChain);
+
+ Filter authenticatorFilter = AUTHENTICATOR.getFilter();
+ authenticatorFilter.doFilter(req, resp, filterChain);
+
+ EasyMock.verify(req, resp, filterChain);
+ }
+
+ @Test
+ public void testUnknownUser() throws IOException, ServletException
+ {
+ String header = Base64.encode(
+ StringUtils.toUtf8("userB:helloworld")
+ );
+ header = StringUtils.format("Basic %s", header);
+
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
+ EasyMock.replay(req);
+
+ HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
+ resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(resp);
+
+ FilterChain filterChain = EasyMock.createMock(FilterChain.class);
+ EasyMock.replay(filterChain);
+
+ Filter authenticatorFilter = AUTHENTICATOR.getFilter();
+ authenticatorFilter.doFilter(req, resp, filterChain);
+
+ EasyMock.verify(req, resp, filterChain);
+ }
+
+ @Test
+ public void testRecognizedButMalformedBasicAuthHeader() throws IOException,
ServletException
+ {
+ String header = Base64.encode(
+ StringUtils.toUtf8("malformed decoded header data")
+ );
+ header = StringUtils.format("Basic %s", header);
+
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
+ EasyMock.replay(req);
+
+ HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
+ resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(resp);
+
+ FilterChain filterChain = EasyMock.createMock(FilterChain.class);
+ EasyMock.replay(filterChain);
+
+ Filter authenticatorFilter = AUTHENTICATOR.getFilter();
+ authenticatorFilter.doFilter(req, resp, filterChain);
+
+ EasyMock.verify(req, resp, filterChain);
+ }
+
+ @Test
+ public void testRecognizedButNotBase64BasicAuthHeader() throws IOException,
ServletException
+ {
+ String header = "Basic this_is_not_base64";
+
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
+ EasyMock.replay(req);
+
+ HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
+ resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(resp);
+
+ FilterChain filterChain = EasyMock.createMock(FilterChain.class);
+ EasyMock.replay(filterChain);
+
+ Filter authenticatorFilter = AUTHENTICATOR.getFilter();
+ authenticatorFilter.doFilter(req, resp, filterChain);
+
+ EasyMock.verify(req, resp, filterChain);
+ }
+
+ @Test
+ public void testUnrecognizedHeader() throws IOException, ServletException
+ {
+ String header = Base64.encode(
+ StringUtils.toUtf8("userA:helloworld")
+ );
+ header = StringUtils.format("NotBasic %s", header);
+
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
+ EasyMock.replay(req);
+
+ HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
+ EasyMock.replay(resp);
+
+ // Authentication filter should move on to the next filter in the chain
without sending a response
+ FilterChain filterChain = EasyMock.createMock(FilterChain.class);
+ filterChain.doFilter(req, resp);
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(filterChain);
+
+ Filter authenticatorFilter = AUTHENTICATOR.getFilter();
+ authenticatorFilter.doFilter(req, resp, filterChain);
+
+ EasyMock.verify(req, resp, filterChain);
+ }
+
+ @Test
+ public void testMissingHeader() throws IOException, ServletException
+ {
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getHeader("Authorization")).andReturn(null);
+ EasyMock.replay(req);
+
+ HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
+ EasyMock.replay(resp);
+
+ // Authentication filter should move on to the next filter in the chain
without sending a response
+ FilterChain filterChain = EasyMock.createMock(FilterChain.class);
+ filterChain.doFilter(req, resp);
+ EasyMock.expectLastCall().times(1);
+ EasyMock.replay(filterChain);
+
+ Filter authenticatorFilter = AUTHENTICATOR.getFilter();
+ authenticatorFilter.doFilter(req, resp, filterChain);
+
+ EasyMock.verify(req, resp, filterChain);
+ }
+}
diff --git
a/server/src/main/java/io/druid/server/security/AuthenticationResult.java
b/server/src/main/java/io/druid/server/security/AuthenticationResult.java
index a1a7d5d..f409af4 100644
--- a/server/src/main/java/io/druid/server/security/AuthenticationResult.java
+++ b/server/src/main/java/io/druid/server/security/AuthenticationResult.java
@@ -21,6 +21,7 @@ package io.druid.server.security;
import javax.annotation.Nullable;
import java.util.Map;
+import java.util.Objects;
/**
* An AuthenticationResult contains information about a successfully
authenticated request.
@@ -84,4 +85,26 @@ public class AuthenticationResult
{
return authenticatedBy;
}
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AuthenticationResult that = (AuthenticationResult) o;
+ return Objects.equals(getIdentity(), that.getIdentity()) &&
+ Objects.equals(getAuthorizerName(), that.getAuthorizerName()) &&
+ Objects.equals(getAuthenticatedBy(), that.getAuthenticatedBy()) &&
+ Objects.equals(getContext(), that.getContext());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(getIdentity(), getAuthorizerName(),
getAuthenticatedBy(), getContext());
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]