This is an automated email from the ASF dual-hosted git repository.
rsivaram pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git
The following commit(s) were added to refs/heads/trunk by this push:
new 7cef37c KAFKA-7324: NPE due to lack of SASLExtensions in
SASL/OAUTHBEARER (#5552)
7cef37c is described below
commit 7cef37cf55353f542db3562157547d26c992e782
Author: Ron Dagostino <[email protected]>
AuthorDate: Wed Aug 29 06:16:29 2018 -0400
KAFKA-7324: NPE due to lack of SASLExtensions in SASL/OAUTHBEARER (#5552)
Set empty extensions if null is passed in.
Reviewers: Satish Duggana <[email protected]>, Stanislav Kozlovski
<[email protected]>, Rajini Sivaram <[email protected]>
---
.../kafka/common/security/auth/SaslExtensions.java | 4 ++
.../security/auth/SaslExtensionsCallback.java | 16 ++++--
.../OAuthBearerClientInitialResponse.java | 63 ++++++++++++++++++++--
.../OAuthBearerClientInitialResponseTest.java | 22 ++++++++
.../internals/OAuthBearerSaslClientTest.java | 35 ++++++++++--
5 files changed, 127 insertions(+), 13 deletions(-)
diff --git
a/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensions.java
b/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensions.java
index 75cac05..c129f1e 100644
---
a/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensions.java
+++
b/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensions.java
@@ -24,6 +24,10 @@ import java.util.Map;
* A simple immutable value object class holding customizable SASL extensions
*/
public class SaslExtensions {
+ /**
+ * An "empty" instance indicating no SASL extensions
+ */
+ public static final SaslExtensions NO_SASL_EXTENSIONS = new
SaslExtensions(Collections.emptyMap());
private final Map<String, String> extensionsMap;
public SaslExtensions(Map<String, String> extensionsMap) {
diff --git
a/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensionsCallback.java
b/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensionsCallback.java
index d07be32..c5bd449 100644
---
a/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensionsCallback.java
+++
b/clients/src/main/java/org/apache/kafka/common/security/auth/SaslExtensionsCallback.java
@@ -17,6 +17,8 @@
package org.apache.kafka.common.security.auth;
+import java.util.Objects;
+
import javax.security.auth.callback.Callback;
/**
@@ -24,11 +26,14 @@ import javax.security.auth.callback.Callback;
* in the SASL exchange.
*/
public class SaslExtensionsCallback implements Callback {
- private SaslExtensions extensions;
+ private SaslExtensions extensions = SaslExtensions.NO_SASL_EXTENSIONS;
/**
- * Returns a {@link SaslExtensions} consisting of the extension names and
values that are sent by the client to
- * the server in the initial client SASL authentication message.
+ * Returns always non-null {@link SaslExtensions} consisting of the
extension
+ * names and values that are sent by the client to the server in the
initial
+ * client SASL authentication message. The default value is
+ * {@link SaslExtensions#NO_SASL_EXTENSIONS} so that if this callback is
+ * unhandled the client will see a non-null value.
*/
public SaslExtensions extensions() {
return extensions;
@@ -36,8 +41,11 @@ public class SaslExtensionsCallback implements Callback {
/**
* Sets the SASL extensions on this callback.
+ *
+ * @param extensions
+ * the mandatory extensions to set
*/
public void extensions(SaslExtensions extensions) {
- this.extensions = extensions;
+ this.extensions = Objects.requireNonNull(extensions, "extensions must
not be null");
}
}
diff --git
a/clients/src/main/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponse.java
b/clients/src/main/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponse.java
index ef16ea2..a356f0d 100644
---
a/clients/src/main/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponse.java
+++
b/clients/src/main/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponse.java
@@ -22,6 +22,7 @@ import org.apache.kafka.common.utils.Utils;
import javax.security.sasl.SaslException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -58,7 +59,9 @@ public class OAuthBearerClientInitialResponse {
if (auth == null)
throw new SaslException("Invalid OAUTHBEARER client first message:
'auth' not specified");
properties.remove(AUTH_KEY);
- this.saslExtensions = validateExtensions(new
SaslExtensions(properties));
+ SaslExtensions extensions = new SaslExtensions(properties);
+ validateExtensions(extensions);
+ this.saslExtensions = extensions;
Matcher authMatcher = AUTH_PATTERN.matcher(auth);
if (!authMatcher.matches())
@@ -71,16 +74,48 @@ public class OAuthBearerClientInitialResponse {
this.tokenValue = authMatcher.group("token");
}
+ /**
+ * Constructor
+ *
+ * @param tokenValue
+ * the mandatory token value
+ * @param extensions
+ * the optional extensions
+ * @throws SaslException
+ * if any extension name or value fails to conform to the
required
+ * regular expression as defined by the specification, or if
the
+ * reserved {@code auth} appears as a key
+ */
public OAuthBearerClientInitialResponse(String tokenValue, SaslExtensions
extensions) throws SaslException {
this(tokenValue, "", extensions);
}
+ /**
+ * Constructor
+ *
+ * @param tokenValue
+ * the mandatory token value
+ * @param authorizationId
+ * the optional authorization ID
+ * @param extensions
+ * the optional extensions
+ * @throws SaslException
+ * if any extension name or value fails to conform to the
required
+ * regular expression as defined by the specification, or if
the
+ * reserved {@code auth} appears as a key
+ */
public OAuthBearerClientInitialResponse(String tokenValue, String
authorizationId, SaslExtensions extensions) throws SaslException {
- this.tokenValue = tokenValue;
+ this.tokenValue = Objects.requireNonNull(tokenValue, "token value must
not be null");
this.authorizationId = authorizationId == null ? "" : authorizationId;
- this.saslExtensions = validateExtensions(extensions);
+ validateExtensions(extensions);
+ this.saslExtensions = extensions != null ? extensions :
SaslExtensions.NO_SASL_EXTENSIONS;
}
+ /**
+ * Return the always non-null extensions
+ *
+ * @return the always non-null extensions
+ */
public SaslExtensions extensions() {
return saslExtensions;
}
@@ -97,10 +132,20 @@ public class OAuthBearerClientInitialResponse {
return message.getBytes(StandardCharsets.UTF_8);
}
+ /**
+ * Return the always non-null token value
+ *
+ * @return the always non-null toklen value
+ */
public String tokenValue() {
return tokenValue;
}
+ /**
+ * Return the always non-null authorization ID
+ *
+ * @return the always non-null authorization ID
+ */
public String authorizationId() {
return authorizationId;
}
@@ -108,10 +153,19 @@ public class OAuthBearerClientInitialResponse {
/**
* Validates that the given extensions conform to the standard. They
should also not contain the reserve key name {@link
OAuthBearerClientInitialResponse#AUTH_KEY}
*
+ * @param extensions
+ * optional extensions to validate
+ * @throws SaslException
+ * if any extension name or value fails to conform to the
required
+ * regular expression as defined by the specification, or if
the
+ * reserved {@code auth} appears as a key
+ *
* @see <a href="https://tools.ietf.org/html/rfc7628#section-3.1">RFC 7628,
* Section 3.1</a>
*/
- public static SaslExtensions validateExtensions(SaslExtensions extensions)
throws SaslException {
+ public static void validateExtensions(SaslExtensions extensions) throws
SaslException {
+ if (extensions == null)
+ return;
if
(extensions.map().containsKey(OAuthBearerClientInitialResponse.AUTH_KEY))
throw new SaslException("Extension name " +
OAuthBearerClientInitialResponse.AUTH_KEY + " is invalid");
@@ -124,7 +178,6 @@ public class OAuthBearerClientInitialResponse {
if (!EXTENSION_VALUE_PATTERN.matcher(extensionValue).matches())
throw new SaslException("Extension value (" + extensionValue +
") for extension " + extensionName + " is invalid");
}
- return extensions;
}
/**
diff --git
a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponseTest.java
b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponseTest.java
index 3de6408..0ba9565 100644
---
a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponseTest.java
+++
b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerClientInitialResponseTest.java
@@ -17,6 +17,7 @@
package org.apache.kafka.common.security.oauthbearer.internals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import org.apache.kafka.common.security.auth.SaslExtensions;
import org.junit.Test;
@@ -99,4 +100,25 @@ public class OAuthBearerClientInitialResponseTest {
assertEquals("server.example.com",
response.extensions().map().get("host"));
assertEquals("143", response.extensions().map().get("port"));
}
+
+ @Test
+ public void testNoExtensionsFromByteArray() throws Exception {
+ String message = "n,[email protected],\u0001" +
+ "auth=Bearer
vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg\u0001\u0001";
+ OAuthBearerClientInitialResponse response = new
OAuthBearerClientInitialResponse(message.getBytes(StandardCharsets.UTF_8));
+ assertEquals("vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg",
response.tokenValue());
+ assertEquals("[email protected]", response.authorizationId());
+ assertTrue(response.extensions().map().isEmpty());
+ }
+
+ @Test
+ public void testNoExtensionsFromTokenAndNullExtensions() throws Exception {
+ OAuthBearerClientInitialResponse response = new
OAuthBearerClientInitialResponse("token", null);
+ assertTrue(response.extensions().map().isEmpty());
+ }
+
+ @Test
+ public void testValidateNullExtensions() throws Exception {
+ OAuthBearerClientInitialResponse.validateExtensions(null);
+ }
}
diff --git
a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerSaslClientTest.java
b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerSaslClientTest.java
index 55a8624..fad7431 100644
---
a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerSaslClientTest.java
+++
b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/internals/OAuthBearerSaslClientTest.java
@@ -20,8 +20,8 @@ import org.apache.kafka.common.config.ConfigException;
import org.apache.kafka.common.security.auth.SaslExtensionsCallback;
import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
import org.apache.kafka.common.security.auth.SaslExtensions;
+import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken;
import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback;
-import
org.apache.kafka.common.security.oauthbearer.internals.unsecured.OAuthBearerUnsecuredJws;
import org.easymock.EasyMockSupport;
import org.junit.Test;
@@ -30,9 +30,11 @@ import
javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.sasl.SaslException;
import java.nio.charset.StandardCharsets;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -70,7 +72,32 @@ public class OAuthBearerSaslClientTest extends
EasyMockSupport {
public void handle(Callback[] callbacks) throws
UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof OAuthBearerTokenCallback)
- ((OAuthBearerTokenCallback)
callback).token(createMock(OAuthBearerUnsecuredJws.class));
+ ((OAuthBearerTokenCallback) callback).token(new
OAuthBearerToken() {
+ @Override
+ public String value() {
+ return "";
+ }
+
+ @Override
+ public Set<String> scope() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public long lifetimeMs() {
+ return 100;
+ }
+
+ @Override
+ public String principalName() {
+ return "principalName";
+ }
+
+ @Override
+ public Long startTimeMs() {
+ return null;
+ }
+ });
else if (callback instanceof SaslExtensionsCallback) {
if (toThrow)
throw new ConfigException(errorMessage);
@@ -88,7 +115,7 @@ public class OAuthBearerSaslClientTest extends
EasyMockSupport {
@Test
public void testAttachesExtensionsToFirstClientMessage() throws Exception {
- String expectedToken = new String(new
OAuthBearerClientInitialResponse(null, testExtensions).toBytes(),
StandardCharsets.UTF_8);
+ String expectedToken = new String(new
OAuthBearerClientInitialResponse("", testExtensions).toBytes(),
StandardCharsets.UTF_8);
OAuthBearerSaslClient client = new OAuthBearerSaslClient(new
ExtensionsCallbackHandler(false));
@@ -101,7 +128,7 @@ public class OAuthBearerSaslClientTest extends
EasyMockSupport {
public void testNoExtensionsDoesNotAttachAnythingToFirstClientMessage()
throws Exception {
TEST_PROPERTIES.clear();
testExtensions = new SaslExtensions(TEST_PROPERTIES);
- String expectedToken = new String(new
OAuthBearerClientInitialResponse(null, new
SaslExtensions(TEST_PROPERTIES)).toBytes(), StandardCharsets.UTF_8);
+ String expectedToken = new String(new
OAuthBearerClientInitialResponse("", new
SaslExtensions(TEST_PROPERTIES)).toBytes(), StandardCharsets.UTF_8);
OAuthBearerSaslClient client = new OAuthBearerSaslClient(new
ExtensionsCallbackHandler(false));
String message = new String(client.evaluateChallenge("".getBytes()),
StandardCharsets.UTF_8);