This is an automated email from the ASF dual-hosted git repository.
tabish pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-protonj2.git
The following commit(s) were added to refs/heads/main by this push:
new 12b0f9b1 PROTON-2927 Add SASL mechanism support for OAUTHBEARER
12b0f9b1 is described below
commit 12b0f9b13b4ac60ccdd6965bd21a2a0daaf53aed
Author: Timothy Bish <[email protected]>
AuthorDate: Mon Mar 30 18:18:14 2026 -0400
PROTON-2927 Add SASL mechanism support for OAUTHBEARER
Add a SASL mechanism for OAUTHBEARER SASL exchanges to the client and the
test peer.
---
.../protonj2/client/impl/ClientConnection.java | 5 +
.../protonj2/client/impl/SaslConnectionTest.java | 112 ++++++++++++++++
.../qpid/protonj2/test/driver/ScriptWriter.java | 148 +++++++++++++++++++++
.../protonj2/test/driver/ProtonTestServerTest.java | 56 ++++++++
.../engine/sasl/client/OauthBearerMechanism.java | 122 +++++++++++++++++
.../sasl/client/SaslCredentialsProvider.java | 8 ++
.../engine/sasl/client/SaslMechanisms.java | 12 ++
.../engine/impl/sasl/ProtonSaslClientTest.java | 5 +
.../engine/sasl/client/MechanismTestBase.java | 33 ++++-
.../sasl/client/OauthBearerMechanismTest.java | 107 +++++++++++++++
10 files changed, 603 insertions(+), 5 deletions(-)
diff --git
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
index f12df017..d1520be5 100644
---
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
+++
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
@@ -870,6 +870,11 @@ public final class ClientConnection implements Connection {
public Principal localPrincipal() {
return transport.getLocalPrincipal();
}
+
+ @Override
+ public boolean isSecure() {
+ return transport.isSecure();
+ }
}));
}
diff --git
a/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SaslConnectionTest.java
b/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SaslConnectionTest.java
index 73af0a05..bec4fedb 100644
---
a/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SaslConnectionTest.java
+++
b/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SaslConnectionTest.java
@@ -198,6 +198,118 @@ public class SaslConnectionTest extends
ImperativeClientTestCase {
}
}
+ @Test
+ public void testSaslOauthBearerConnection() throws Exception {
+ final String username = "user";
+ final String password = "eyB1c2VyPSJ1c2VyIiB9";
+
+ final ProtonTestServerOptions serverOptions =
serverOptions().setSecure(true)
+
.setKeyStoreLocation(BROKER_JKS_KEYSTORE)
+
.setKeyStorePassword(PASSWORD)
+
.setVerifyHost(false);
+
+ try (ProtonTestServer peer = new ProtonTestServer(serverOptions)) {
+ peer.expectSaslOauthBearerConnect(username, password, "localhost");
+ peer.expectOpen().respond();
+ peer.expectClose().respond();
+ peer.start();
+
+ URI remoteURI = peer.getServerURI();
+
+ ConnectionOptions clientOptions = connectionOptions();
+ clientOptions.user(username);
+ clientOptions.password(password);
+ clientOptions.sslOptions()
+ .sslEnabled(true)
+ .trustStoreLocation(CLIENT_JKS_TRUSTSTORE)
+ .trustStorePassword(PASSWORD);
+
+ Client container = Client.create();
+ Connection connection = container.connect(remoteURI.getHost(),
remoteURI.getPort(), clientOptions);
+
+ connection.openFuture().get(10, TimeUnit.SECONDS);
+ connection.close();
+
+ peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
+ public void testSaslOauthBearerConnectionWithVirtualHost() throws
Exception {
+ final String virtualHost = "example.com";
+ final String username = "user";
+ final String password = "eyB1c2VyPSJ1c2VyIiB9";
+
+ final ProtonTestServerOptions serverOptions =
serverOptions().setSecure(true)
+
.setKeyStoreLocation(BROKER_JKS_KEYSTORE)
+
.setKeyStorePassword(PASSWORD)
+
.setVerifyHost(false);
+
+ try (ProtonTestServer peer = new ProtonTestServer(serverOptions)) {
+ peer.expectSaslOauthBearerConnect(username, password, virtualHost);
+ peer.expectOpen().respond();
+ peer.expectClose().respond();
+ peer.start();
+
+ URI remoteURI = peer.getServerURI();
+
+ ConnectionOptions clientOptions = connectionOptions();
+ clientOptions.user(username);
+ clientOptions.password(password);
+ clientOptions.virtualHost(virtualHost);
+ clientOptions.sslOptions()
+ .sslEnabled(true)
+ .trustStoreLocation(CLIENT_JKS_TRUSTSTORE)
+ .trustStorePassword(PASSWORD);
+
+ Client container = Client.create();
+ Connection connection = container.connect(remoteURI.getHost(),
remoteURI.getPort(), clientOptions);
+
+ connection.openFuture().get(10, TimeUnit.SECONDS);
+ connection.close();
+
+ peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
+ public void testSaslOauthBearerConnectionWithVirtualHostForcedOmit()
throws Exception {
+ final String username = "user";
+ final String password = "eyB1c2VyPSJ1c2VyIiB9";
+
+ final ProtonTestServerOptions serverOptions =
serverOptions().setSecure(true)
+
.setKeyStoreLocation(BROKER_JKS_KEYSTORE)
+
.setKeyStorePassword(PASSWORD)
+
.setVerifyHost(false);
+
+ try (ProtonTestServer peer = new ProtonTestServer(serverOptions)) {
+ peer.expectSaslOauthBearerConnect(username, password, null);
+ peer.expectOpen().respond();
+ peer.expectClose().respond();
+ peer.start();
+
+ URI remoteURI = peer.getServerURI();
+
+ final ConnectionOptions clientOptions = connectionOptions();
+
+ clientOptions.user(username);
+ clientOptions.password(password);
+ clientOptions.virtualHost("");
+ clientOptions.sslOptions()
+ .sslEnabled(true)
+ .trustStoreLocation(CLIENT_JKS_TRUSTSTORE)
+ .trustStorePassword(PASSWORD);
+
+ Client container = Client.create();
+ Connection connection = container.connect(remoteURI.getHost(),
remoteURI.getPort(), clientOptions);
+
+ connection.openFuture().get(10, TimeUnit.SECONDS);
+ connection.close();
+
+ peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+ }
+ }
+
@Test
public void testSaslAnonymousConnection() throws Exception {
try (ProtonTestServer peer = new ProtonTestServer(serverOptions())) {
diff --git
a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptWriter.java
b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptWriter.java
index 002c6824..52640c41 100644
---
a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptWriter.java
+++
b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/ScriptWriter.java
@@ -465,6 +465,71 @@ public abstract class ScriptWriter {
expectAMQPHeader().respondWithAMQPHeader();
}
+ /**
+ * Creates all the scripted elements needed for a successful SASL
OAUTHBEARER
+ * connection. This is generally used with a server type peer which will be
+ * accepting client connections.
+ * <p>
+ * For this exchange the SASL header is expected which is responded to
with the
+ * corresponding SASL header and an immediate SASL mechanisms frame that
only
+ * advertises OAUTHBEARER as the mechanism. It is expected that the
remote will
+ * send a SASL init with the OAUTHBEARER mechanism selected and the
outcome is
+ * predefined as success. Once done the expectation is added for the AMQP
+ * header to arrive and a header response will be sent.
+ * <p>
+ * The host value will be set in the response is checked if the provided
value is
+ * non-null and is not an empty string.
+ *
+ * @param username
+ * The user name that is expected in the SASL initial response.
+ * @param token
+ * The token that is expected in the SASL initial response.
+ * @param host
+ * The optional host value to add as the host portion of
the initial response as the 'host' entry.
+ */
+ public void expectSaslOauthBearerConnect(String username, String token,
String host) {
+ expectSaslOauthBearerConnect(username, token, host, 0, (String[])
null);
+ }
+
+ /**
+ * Creates all the scripted elements needed for a successful SASL
OAUTHBEARER
+ * connection. This is generally used with a server type peer which will be
+ * accepting client connections.
+ * <p>
+ * For this exchange the SASL header is expected which is responded to
with the
+ * corresponding SASL header and an immediate SASL mechanisms frame that
only
+ * advertises OAUTHBEARER as the mechanism. It is expected that the
remote will
+ * send a SASL init with the OAUTHBEARER mechanism selected and the
outcome is
+ * predefined as success. Once done the expectation is added for the AMQP
+ * header to arrive and a header response will be sent.
+ * <p>
+ * The host value will be set in the response is checked if the provided
value is
+ * non-null and is not an empty string. The port value is checked if the
provided
+ * value is greater than zero.
+ *
+ * @param username
+ * The user name that is expected in the SASL initial response.
+ * @param token
+ * The token that is expected in the SASL initial response.
+ * @param host
+ * The optional host value to add as the host portion of
the initial response as the 'host' entry.
+ * @param port
+ * The optional port value to apply to the initial
response as the 'port' entry.
+ * @param offeredMechanisms
+ * The server offered mechanisms to provide, if null then
only OAUTHBEARER is offered.
+ */
+ public void expectSaslOauthBearerConnect(String username, String token,
String host, int port, String... offeredMechanisms) {
+ expectSASLHeader().respondWithSASLHeader();
+ if (offeredMechanisms == null || offeredMechanisms.length == 0) {
+ remoteSaslMechanisms().withMechanisms("OAUTHBEARER").queue();
+ } else {
+ remoteSaslMechanisms().withMechanisms(offeredMechanisms).queue();
+ }
+
expectSaslInit().withMechanism("OAUTHBEARER").withInitialResponse(saslOauthBearerInitialResponse(username,
token, host, port));
+ remoteSaslOutcome().withCode(SaslCode.OK).queue();
+ expectAMQPHeader().respondWithAMQPHeader();
+ }
+
/**
* Creates all the scripted elements needed for a failed SASL Plain
* connection. This is generally used with a server type peer which will be
@@ -643,6 +708,66 @@ public abstract class ScriptWriter {
remoteSASLHeader().now();
}
+ /**
+ * Used to trigger the sequence of frames that would occur during a
typical client
+ * connection to a remote peer with SASL OAUTHBEARER. This should be
called after a
+ * client connects to the remote as the fired frames would fail until
there is a
+ * connection in place.
+ *
+ * @param username
+ * The token that is expected in the SASL OAUTHBEARER initial
response.
+ * @param token
+ * The password that is expected in the SASL OAUTHBEARER initial
response.
+ */
+ public void triggerClientSaslOAuthBearerConnect(String username, String
token) {
+ expectSASLHeader();
+ expectSaslMechanisms().withSaslServerMechanism("OAUTHBEARER");
+ remoteSaslInit().withMechanism("OAUTHBEARER")
+
.withInitialResponse(saslOauthBearerInitialResponse(username, token, null,
0)).queue();
+ expectSaslOutcome().withCode(SaslCode.OK);
+ remoteAMQPHeader().queue();
+ expectAMQPHeader();
+
+ // This trigger the exchange of frames.
+ remoteSASLHeader().now();
+ }
+
+ /**
+ * Used to trigger the sequence of frames that would occur during a
typical client
+ * connection to a remote peer with SASL OAUTHBEARER. This should be
called after a
+ * client connects to the remote as the fired frames would fail until
there is a
+ * connection in place.
+ *
+ * @param username
+ * The user name that is expected in the SASL OAUTHBEARER initial
response.
+ * @param token
+ * The token that is expected in the SASL OAUTHBEARER initial
response.
+ * @param host
+ * The value to write into the 'host' entry in the
response (if null no host is added).
+ * @param port
+ * The value to write into the 'port' entry in the
response (if is zero or negative no port is added).
+ * @param offeredMechanisms
+ * The set of mechanisms that the server should offer in the SASL
Mechanisms frame
+ */
+ public void triggerClientSaslOAuthBearerConnect(String username, String
token, String host, int port, String... offeredMechanisms) {
+ expectSASLHeader();
+
+ if (offeredMechanisms == null || offeredMechanisms.length == 0) {
+ expectSaslMechanisms().withSaslServerMechanism("OAUTHBEARER");
+ } else {
+ expectSaslMechanisms().withSaslServerMechanisms(offeredMechanisms);
+ }
+
+ remoteSaslInit().withMechanism("OAUTHBEARER")
+
.withInitialResponse(saslOauthBearerInitialResponse(username, token, host,
port)).queue();
+ expectSaslOutcome().withCode(SaslCode.OK);
+ remoteAMQPHeader().queue();
+ expectAMQPHeader();
+
+ // This trigger the exchange of frames.
+ remoteSASLHeader().now();
+ }
+
//----- Utility methods for tests writing raw scripted SASL tests
public byte[] saslPlainInitialResponse(String username, String password) {
@@ -671,6 +796,29 @@ public abstract class ScriptWriter {
return initialResponse;
}
+ public byte[] saslOauthBearerInitialResponse(String username, String
token, String host, int port) {
+ final StringBuilder builder = new StringBuilder();
+ final char SEPARATOR = '\u0001';
+
+ if (username != null && !username.isBlank()) {
+
builder.append("n,a=").append(username).append(",").append(SEPARATOR);
+ } else {
+ builder.append("n,,").append(SEPARATOR);
+ }
+
+ if (host != null && !host.isBlank()) {
+ builder.append("host=").append(host).append(SEPARATOR);
+ }
+
+ if (port > 0) {
+ builder.append("port=").append(port).append(SEPARATOR);
+ }
+
+ builder.append("auth=Bearer
").append(token).append(SEPARATOR).append(SEPARATOR);
+
+ return builder.toString().getBytes(StandardCharsets.UTF_8);
+ }
+
//----- Smart Scripted Response Actions
/**
diff --git
a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/ProtonTestServerTest.java
b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/ProtonTestServerTest.java
index 659db896..e649cbe9 100644
---
a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/ProtonTestServerTest.java
+++
b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/ProtonTestServerTest.java
@@ -122,4 +122,60 @@ public class ProtonTestServerTest extends
TestPeerTestsBase {
peer.waitForScriptToComplete();
}
}
+
+ @Test
+ public void testServerExpectsSaslOauthBearerConnect() throws Exception {
+ try (ProtonTestServer peer = new ProtonTestServer()) {
+ peer.expectSaslOauthBearerConnect("user", "pass", null, 0);
+ peer.start();
+
+ URI remoteURI = peer.getServerURI();
+
+ try (ProtonTestClient client = new ProtonTestClient()) {
+ client.connect(remoteURI.getHost(), remoteURI.getPort());
+ client.expectSASLHeader();
+
client.expectSaslMechanisms().withSaslServerMechanisms("OAUTHBEARER");
+ client.remoteSaslInit().withMechanism("OAUTHBEARER")
+
.withInitialResponse(peer.saslOauthBearerInitialResponse("user", "pass", null,
0)).queue();
+ client.expectSaslOutcome().withCode(SaslCode.OK);
+ client.remoteHeader(AMQPHeader.getAMQPHeader()).queue();
+ client.expectAMQPHeader();
+
+ // Start the exchange with the client SASL header
+ client.remoteHeader(AMQPHeader.getSASLHeader()).now();
+
+ client.waitForScriptToComplete(5, TimeUnit.SECONDS);
+ }
+
+ peer.waitForScriptToComplete();
+ }
+ }
+
+ @Test
+ public void
testServerExpectsSaslOauthBearerConnectAndOffersMoreMechanisms() throws
Exception {
+ try (ProtonTestServer peer = new ProtonTestServer()) {
+ peer.expectSaslOauthBearerConnect("user", "pass", null, 0,
"OAUTHBEARER", "PLAIN", "ANONYMOUS");
+ peer.start();
+
+ URI remoteURI = peer.getServerURI();
+
+ try (ProtonTestClient client = new ProtonTestClient()) {
+ client.connect(remoteURI.getHost(), remoteURI.getPort());
+ client.expectSASLHeader();
+
client.expectSaslMechanisms().withSaslServerMechanisms("OAUTHBEARER", "PLAIN",
"ANONYMOUS");
+ client.remoteSaslInit().withMechanism("OAUTHBEARER")
+
.withInitialResponse(peer.saslOauthBearerInitialResponse("user", "pass", null,
0)).queue();
+ client.expectSaslOutcome().withCode(SaslCode.OK);
+ client.remoteHeader(AMQPHeader.getAMQPHeader()).queue();
+ client.expectAMQPHeader();
+
+ // Start the exchange with the client SASL header
+ client.remoteHeader(AMQPHeader.getSASLHeader()).now();
+
+ client.waitForScriptToComplete(5, TimeUnit.SECONDS);
+ }
+
+ peer.waitForScriptToComplete();
+ }
+ }
}
diff --git
a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/OauthBearerMechanism.java
b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/OauthBearerMechanism.java
new file mode 100644
index 00000000..648a9159
--- /dev/null
+++
b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/OauthBearerMechanism.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.qpid.protonj2.engine.sasl.client;
+
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+import javax.security.sasl.SaslException;
+
+import org.apache.qpid.protonj2.buffer.ProtonBuffer;
+import org.apache.qpid.protonj2.buffer.ProtonBufferAllocator;
+import org.apache.qpid.protonj2.types.Symbol;
+
+/**
+ * Implements the SASL OAUTHBEARER authentication Mechanism .
+ *
+ * User name and Password values are sent without being encrypted.
+ */
+public class OauthBearerMechanism extends AbstractMechanism {
+
+ // RFC 6750 Based pattern match, this protects against invalid tokens
+ private static final Pattern OAUTH_BEARER_TOKEN_PATTERN =
Pattern.compile("^[a-zA-Z0-9\\-\\._~\\+/]+=*$");
+
+ private static final byte SEPARATOR = 0x01;
+ private static final byte[] USER_PREFIX =
"n,a=".getBytes(StandardCharsets.US_ASCII);
+ private static final byte[] USER_SUFFIX =
",".getBytes(StandardCharsets.US_ASCII);
+ private static final byte[] HOST_PREFIX =
"host=".getBytes(StandardCharsets.US_ASCII);
+ private static final byte[] BEARER_PREFIX = "auth=Bearer
".getBytes(StandardCharsets.US_ASCII);
+
+ /**
+ * A singleton instance of the symbolic mechanism name.
+ */
+ public static final Symbol OAUTHBEARER = Symbol.valueOf("OAUTHBEARER");
+
+ private String additionalFailureInformation;
+
+ @Override
+ public Symbol getName() {
+ return OAUTHBEARER;
+ }
+
+ @Override
+ public boolean isApplicable(SaslCredentialsProvider credentials) {
+ // Enforce that user-name and password are required and that format
for a bearer
+ // token as defined in RFC 6750.
+ if (credentials.isSecure() &&
+ credentials.username() != null &&
!credentials.username().isEmpty() &&
+ credentials.password() != null &&
!credentials.password().isEmpty()) {
+
+ return
OAUTH_BEARER_TOKEN_PATTERN.matcher(credentials.password()).matches();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public ProtonBuffer getInitialResponse(SaslCredentialsProvider
credentials) throws SaslException {
+ final byte[] username =
credentials.username().getBytes(StandardCharsets.UTF_8);
+ final byte[] token =
credentials.password().getBytes(StandardCharsets.UTF_8);
+ final byte[] vhost;
+
+ if (credentials.vhost() != null && !credentials.vhost().isBlank()) {
+ vhost = credentials.vhost().getBytes(StandardCharsets.US_ASCII);
+ } else {
+ vhost = null;
+ }
+
+ final int encodedSize;
+
+ if (vhost != null) {
+ encodedSize = USER_PREFIX.length + username.length +
USER_SUFFIX.length + 1 +
+ HOST_PREFIX.length + vhost.length + 1 +
+ BEARER_PREFIX.length + token.length + 2;
+ } else {
+ encodedSize = USER_PREFIX.length + username.length +
USER_SUFFIX.length + 1 +
+ BEARER_PREFIX.length + token.length + 2;
+ }
+
+ final ProtonBuffer response =
ProtonBufferAllocator.defaultAllocator().allocate(encodedSize);
+
+ response.writeBytes(USER_PREFIX);
+ response.writeBytes(username);
+ response.writeBytes(USER_SUFFIX);
+ response.writeByte(SEPARATOR);
+
+ if (vhost != null) {
+ response.writeBytes(HOST_PREFIX);
+ response.writeBytes(vhost);
+ response.writeByte(SEPARATOR);
+ }
+
+ response.writeBytes(BEARER_PREFIX);
+ response.writeBytes(token);
+ response.writeByte(SEPARATOR);
+ response.writeByte(SEPARATOR);
+
+ return response;
+ }
+
+ @Override
+ public ProtonBuffer getChallengeResponse(SaslCredentialsProvider
credentials, ProtonBuffer challenge) throws SaslException {
+ if (challenge != null && challenge.getReadableBytes() > 0 &&
additionalFailureInformation == null) {
+ additionalFailureInformation =
challenge.toString(StandardCharsets.UTF_8);
+ }
+
+ return EMPTY; // Empty response before remote sends SASL failed outcome
+ }
+}
diff --git
a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslCredentialsProvider.java
b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslCredentialsProvider.java
index c3701c08..7814719f 100644
---
a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslCredentialsProvider.java
+++
b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslCredentialsProvider.java
@@ -41,6 +41,14 @@ public interface SaslCredentialsProvider {
*/
String password();
+ /**
+ * Returns <code>true</code> if the transport IO layer is operating over a
secure
+ * means such as TLS which provides encryption in flight.
+ *
+ * @return <code>true</code> if the IO layer is secured (encrypted).
+ */
+ boolean isSecure();
+
/**
* Returns the local principal for use in SASL authentication which is
generally
* provided by the IO layer (TLS). This method can return null if there is
no
diff --git
a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslMechanisms.java
b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslMechanisms.java
index debc3270..2bb59ede 100644
---
a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslMechanisms.java
+++
b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/sasl/client/SaslMechanisms.java
@@ -38,6 +38,18 @@ public enum SaslMechanisms {
return INSTANCE;
}
},
+ OAUTHBEARER {
+
+ @Override
+ public Symbol getName() {
+ return OauthBearerMechanism.OAUTHBEARER;
+ }
+
+ @Override
+ public Mechanism createMechanism() {
+ return new OauthBearerMechanism();
+ }
+ },
SCRAM_SHA_512 {
@Override
diff --git
a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/sasl/ProtonSaslClientTest.java
b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/sasl/ProtonSaslClientTest.java
index 9434d81a..a96505dd 100644
---
a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/sasl/ProtonSaslClientTest.java
+++
b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/sasl/ProtonSaslClientTest.java
@@ -275,6 +275,11 @@ public class ProtonSaslClientTest extends
ProtonEngineTestSupport {
public Principal localPrincipal() {
return null;
}
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
};
return new SaslAuthenticator(credentials);
diff --git
a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/sasl/client/MechanismTestBase.java
b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/sasl/client/MechanismTestBase.java
index 74787eb8..bb85ee1f 100644
---
a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/sasl/client/MechanismTestBase.java
+++
b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/sasl/client/MechanismTestBase.java
@@ -34,19 +34,35 @@ public class MechanismTestBase {
ProtonBufferAllocator.defaultAllocator().allocate(10).setWriteOffset(10);
protected SaslCredentialsProvider credentials() {
- return new UserCredentialsProvider(USERNAME, PASSWORD, HOST, true);
+ return new UserCredentialsProvider(USERNAME, PASSWORD, HOST, true,
false);
}
protected SaslCredentialsProvider credentials(String user, String
password) {
- return new UserCredentialsProvider(user, password, null, false);
+ return new UserCredentialsProvider(user, password, null, false, false);
}
protected SaslCredentialsProvider credentials(String user, String
password, boolean principal) {
- return new UserCredentialsProvider(user, password, null, principal);
+ return new UserCredentialsProvider(user, password, null, principal,
false);
+ }
+
+ protected SaslCredentialsProvider secureCredentials() {
+ return new UserCredentialsProvider(USERNAME, PASSWORD, HOST, true,
false);
+ }
+
+ protected SaslCredentialsProvider secureCredentials(String user, String
password) {
+ return new UserCredentialsProvider(user, password, null, false, true);
+ }
+
+ protected SaslCredentialsProvider secureCredentials(String user, String
password, boolean principal) {
+ return new UserCredentialsProvider(user, password, null, principal,
true);
}
protected SaslCredentialsProvider emptyCredentials() {
- return new UserCredentialsProvider(null, null, null, false);
+ return new UserCredentialsProvider(null, null, null, false, false);
+ }
+
+ protected SaslCredentialsProvider emptySecureCredentials() {
+ return new UserCredentialsProvider(null, null, null, false, true);
}
private static class UserCredentialsProvider implements
SaslCredentialsProvider {
@@ -55,12 +71,14 @@ public class MechanismTestBase {
private final String password;
private final String host;
private final boolean principal;
+ private final boolean secure;
- public UserCredentialsProvider(String username, String password,
String host, boolean principal) {
+ public UserCredentialsProvider(String username, String password,
String host, boolean principal, boolean secure) {
this.username = username;
this.password = password;
this.host = host;
this.principal = principal;
+ this.secure = secure;
}
@Override
@@ -92,5 +110,10 @@ public class MechanismTestBase {
return null;
}
}
+
+ @Override
+ public boolean isSecure() {
+ return secure;
+ }
}
}
diff --git
a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/sasl/client/OauthBearerMechanismTest.java
b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/sasl/client/OauthBearerMechanismTest.java
new file mode 100644
index 00000000..88979908
--- /dev/null
+++
b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/sasl/client/OauthBearerMechanismTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.qpid.protonj2.engine.sasl.client;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.security.sasl.SaslException;
+
+import org.apache.qpid.protonj2.buffer.ProtonBuffer;
+import org.junit.jupiter.api.Test;
+
+public class OauthBearerMechanismTest extends MechanismTestBase {
+
+ @Test
+ public void testGetInitialResponseWithNullUserAndPassword() throws
SaslException {
+ OauthBearerMechanism mech = new OauthBearerMechanism();
+
+ ProtonBuffer response = mech.getInitialResponse(secureCredentials());
+ assertNotNull(response);
+ assertTrue(response.getReadableBytes() != 0);
+ }
+
+ @Test
+ public void testGetChallengeResponse() throws SaslException {
+ OauthBearerMechanism mech = new OauthBearerMechanism();
+
+ ProtonBuffer response = mech.getChallengeResponse(secureCredentials(),
TEST_BUFFER);
+ assertNotNull(response);
+ assertTrue(response.getReadableBytes() == 0);
+ }
+
+ @Test
+ public void testIsNotApplicableWithInsecureCredentials() {
+
assertFalse(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(credentials("user",
"token", false)),
+ "Should not be applicable with no credentials");
+ }
+
+ @Test
+ public void testIsNotApplicableWithNoCredentials() {
+
assertFalse(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials(null,
null, false)),
+ "Should not be applicable with no credentials");
+ }
+
+ @Test
+ public void testIsNotApplicableWithNoUser() {
+
assertFalse(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials(null,
"pass", false)),
+ "Should not be applicable with no username");
+ }
+
+ @Test
+ public void testIsNotApplicableWithNoToken() {
+
assertFalse(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials("user",
null, false)),
+ "Should not be applicable with no token");
+ }
+
+ @Test
+ public void testIsNotApplicableWithEmtpyUser() {
+
assertFalse(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials("",
"pass", false)),
+ "Should not be applicable with empty username");
+ }
+
+ @Test
+ public void testIsNotApplicableWithEmtpyToken() {
+
assertFalse(SaslMechanisms.XOAUTH2.createMechanism().isApplicable(secureCredentials("user",
"", false)),
+ "Should not be applicable with empty token");
+ }
+
+ @Test
+ public void testIsNotApplicableWithIllegalAccessToken() {
+
assertFalse(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials("user",
"illegalChar\000", false)),
+ "Should not be applicable with non vschars");
+ }
+
+ @Test
+ public void testIsNotApplicableWithEmtpyUserAndToken() {
+
assertFalse(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials("",
"", false)),
+ "Should not be applicable with empty user and token");
+ }
+
+ @Test
+ public void testIsApplicableWithUserAndToken() {
+
assertTrue(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials("user",
"2YotnFZFEjr1zCsicMWpAA", false)),
+ "Should be applicable with user and token");
+ }
+
+ @Test
+ public void testIsApplicableWithUserAndPasswordAndPrincipal() {
+
assertTrue(SaslMechanisms.OAUTHBEARER.createMechanism().isApplicable(secureCredentials("user",
"2YotnFZFEjr1zCsicMWpAA", true)),
+ "Should be applicable with user and token and principal");
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]