This is an automated email from the ASF dual-hosted git repository.

gtully pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/artemis.git

commit 6795672b8f5df7284ccd6cc7ad2d257fb8ef340c
Author: Grzegorz Grzybek <[email protected]>
AuthorDate: Tue Mar 24 14:05:01 2026 +0100

    ARTEMIS-5200 Implement server SASL for XOAUTH2 and OAUTHBEARER
---
 .../amqp/proton/AMQPConnectionContext.java         |   5 +
 .../protocol/amqp/sasl/OAuthBearerSASL.java        |  95 +++++++++++++++++++
 .../amqp/sasl/OAuthBearerServerSASLFactory.java    |  48 ++++++++++
 .../protocol/amqp/sasl/ServerSASLToken.java        |  57 ++++++++++++
 .../protocol/amqp/sasl/TokenSASLResult.java        |  56 +++++++++++
 .../artemis/protocol/amqp/sasl/XOAuth2SASL.java    |  89 ++++++++++++++++++
 .../amqp/sasl/XOAuth2ServerSASLFactory.java        |  48 ++++++++++
 ...mq.artemis.protocol.amqp.sasl.ServerSASLFactory |   2 +
 .../protocol/amqp/sasl/OAuthBearerSASLTest.java    | 103 +++++++++++++++++++++
 .../protocol/amqp/sasl/XOAuth2SASLTest.java        | 103 +++++++++++++++++++++
 10 files changed, 606 insertions(+)

diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
index 82c5132ad7..7dce000344 100644
--- 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
@@ -59,6 +59,7 @@ import 
org.apache.activemq.artemis.protocol.amqp.sasl.AnonymousServerSASL;
 import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory;
 import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASLResult;
 import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
+import org.apache.activemq.artemis.protocol.amqp.sasl.TokenSASLResult;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.remoting.ReadyListener;
 import org.apache.activemq.artemis.utils.ByteUtil;
@@ -728,6 +729,8 @@ public class AMQPConnectionContext extends 
ProtonInitializable implements EventH
          user = saslResult.getUser();
          if (saslResult instanceof PlainSASLResult plainSASLResult) {
             password = plainSASLResult.getPassword();
+         } else if (saslResult instanceof TokenSASLResult tokenSASLResult) {
+            password = tokenSASLResult.getToken();
          }
       }
 
@@ -987,6 +990,8 @@ public class AMQPConnectionContext extends 
ProtonInitializable implements EventH
          if (saslResult != null) {
             if (saslResult instanceof PlainSASLResult plainSASLResult) {
                password = plainSASLResult.getPassword();
+            } else if (saslResult instanceof TokenSASLResult tokenSASLResult) {
+               password = tokenSASLResult.getToken();
             }
          }
 
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerSASL.java
 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerSASL.java
new file mode 100644
index 0000000000..f13c809746
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerSASL.java
@@ -0,0 +1,95 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import org.apache.activemq.artemis.core.security.SecurityStore;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+public class OAuthBearerSASL extends ServerSASLToken {
+
+   // https://datatracker.ietf.org/doc/html/rfc7628#section-3
+   static final String MECHANISM_NAME = "OAUTHBEARER";
+
+   public OAuthBearerSASL(SecurityStore securityStore, String securityDomain, 
RemotingConnection remotingConnection) {
+      super(securityStore, securityDomain, remotingConnection);
+   }
+
+   @Override
+   public String getName() {
+      return MECHANISM_NAME;
+   }
+
+   @Override
+   public byte[] processSASL(byte[] bytes) {
+      // expecting `n,a=<user>,\x01host=<host>\x01port=<port>\x01auth=Bearer 
<token>\x01\x01`
+      // according to https://datatracker.ietf.org/doc/html/rfc7628#section-3.1
+      // `n,a=<user>,` is from https://datatracker.ietf.org/doc/html/rfc5801
+      //  - gs2-cb-flag: "n" = client does not support channel binding,
+      //                 "p" = client supports and used channel binding
+      //                 "y" = client supports CB, thinks the server does not
+      //  - gs2-authzid: "a=<saslname>"
+      if (bytes == null || bytes.length == 0) {
+         result = new TokenSASLResult(false, null, null);
+      } else {
+         if (bytes.length < 2 || !(bytes[bytes.length - 2] == '\001' && 
bytes[bytes.length - 1] == '\001')) {
+            result = new TokenSASLResult(false, null, null);
+         } else {
+            String data = new String(bytes, StandardCharsets.UTF_8);
+            String[] segments = data.split("\001");
+            if (segments.length < 2) {
+               result = new TokenSASLResult(false, null, null);
+            } else {
+               String gs2Header = segments[0];
+               String[] headersSegments = gs2Header.split(",");
+               String user = null;
+               for (String s : headersSegments) {
+                  if (s.startsWith("a=")) {
+                     user = s.substring(2);
+                  }
+               }
+
+               String token = null;
+               for (int i = 1; i < segments.length; i++) {
+                  if (segments[i].startsWith("auth=Bearer ")) {
+                     token = segments[i].substring(12);
+                  }
+               }
+
+               if (token == null) {
+                  result = new TokenSASLResult(false, null, null);
+               } else {
+                  boolean success = authenticate(user, token);
+                  result = new TokenSASLResult(success, user, token);
+               }
+            }
+         }
+      }
+
+      // TODO: validate host and port from the SASL OAUTHBEARER initial 
response?
+
+      if (result.isSuccess()) {
+         return null;
+      }
+
+      // see https://datatracker.ietf.org/doc/html/rfc7628#section-3.2.2
+      return 
Base64.getEncoder().encode("{\"status\":\"invalid_token\"}".getBytes());
+   }
+
+}
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerServerSASLFactory.java
 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerServerSASLFactory.java
new file mode 100644
index 0000000000..b3c996e66e
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerServerSASLFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRoutingHandler;
+import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
+
+public class OAuthBearerServerSASLFactory implements ServerSASLFactory {
+
+   @Override
+   public String getMechanism() {
+      return OAuthBearerSASL.MECHANISM_NAME;
+   }
+
+   @Override
+   public ServerSASL create(ActiveMQServer server, 
ProtocolManager<AmqpInterceptor, AMQPRoutingHandler> manager, Connection 
connection, RemotingConnection remotingConnection) {
+      return new OAuthBearerSASL(server.getSecurityStore(), 
manager.getSecurityDomain(), connection.getProtocolConnection());
+   }
+
+   @Override
+   public int getPrecedence() {
+      return 32;
+   }
+
+   @Override
+   public boolean isDefaultPermitted() {
+      return false;
+   }
+
+}
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLToken.java
 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLToken.java
new file mode 100644
index 0000000000..62d1143255
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLToken.java
@@ -0,0 +1,57 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import org.apache.activemq.artemis.core.security.SecurityStore;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+
+public abstract class ServerSASLToken implements ServerSASL {
+
+   protected final SecurityStore securityStore;
+   protected final String securityDomain;
+   protected RemotingConnection remotingConnection;
+
+   protected SASLResult result = null;
+
+   public ServerSASLToken(SecurityStore securityStore, String securityDomain, 
RemotingConnection remotingConnection) {
+      this.securityStore = securityStore;
+      this.securityDomain = securityDomain;
+      this.remotingConnection = remotingConnection;
+   }
+
+   @Override
+   public SASLResult result() {
+      return result;
+   }
+
+   @Override
+   public void done() {
+   }
+
+   protected boolean authenticate(String user, String token) {
+      if (securityStore != null && securityStore.isSecurityEnabled()) {
+         try {
+            securityStore.authenticate(user, token, remotingConnection, 
securityDomain);
+            return true;
+         } catch (Exception e) {
+            return false;
+         }
+      }
+      return true;
+   }
+
+}
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/TokenSASLResult.java
 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/TokenSASLResult.java
new file mode 100644
index 0000000000..7ef55ff81c
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/TokenSASLResult.java
@@ -0,0 +1,56 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import javax.security.auth.Subject;
+
+/**
+ * A {@link SASLResult} containing a String representation of a token (like 
JWT) and optional username (usually
+ * not relevant).
+ */
+public class TokenSASLResult implements SASLResult {
+
+   private final boolean success;
+   private final String user;
+   private final String token;
+
+   public TokenSASLResult(boolean success, String user, String token) {
+      this.success = success;
+      this.user = user;
+      this.token = token;
+   }
+
+   @Override
+   public boolean isSuccess() {
+      return success;
+   }
+
+   @Override
+   public String getUser() {
+      return user;
+   }
+
+   public String getToken() {
+      return token;
+   }
+
+   @Override
+   public Subject getSubject() {
+      return null;
+   }
+
+}
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2SASL.java
 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2SASL.java
new file mode 100644
index 0000000000..d49cf415a7
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2SASL.java
@@ -0,0 +1,89 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import org.apache.activemq.artemis.core.security.SecurityStore;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+
+import java.util.Base64;
+
+public class XOAuth2SASL extends ServerSASLToken {
+
+   // https://developers.google.com/workspace/gmail/imap/xoauth2-protocol
+   // marked OBSOLETE at 
https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml
+   // Wikipedia mentions XOAUTH2 only here: 
https://en.wikipedia.org/wiki/SMTP_Authentication
+   static final String MECHANISM_NAME = "XOAUTH2";
+
+   public XOAuth2SASL(SecurityStore securityStore, String securityDomain, 
RemotingConnection remotingConnection) {
+      super(securityStore, securityDomain, remotingConnection);
+   }
+
+   @Override
+   public String getName() {
+      return MECHANISM_NAME;
+   }
+
+   @Override
+   public byte[] processSASL(byte[] bytes) {
+      // expecting `user=<username>\x01auth=Bearer <token>\x01\x01`
+      // according to 
https://developers.google.com/workspace/gmail/imap/xoauth2-protocol
+      if (bytes == null || bytes.length == 0) {
+         result = new TokenSASLResult(false, null, null);
+      } else {
+         String userPart = null;
+         String tokenPart = null;
+         int from = 0, to = 0;
+         while (to < bytes.length && bytes[to] != '\001') {
+            to++;
+         }
+         if (to - from >= 5) {
+            userPart = new String(bytes, from, to - from);
+            ++to;
+            from = to;
+         }
+         while (to < bytes.length && bytes[to] != '\001') {
+            to++;
+         }
+         if (to - from >= 12) {
+            tokenPart = new String(bytes, from, to - from);
+         }
+         boolean valid = userPart != null && tokenPart != null;
+         if (valid) {
+            valid = userPart.startsWith("user=");
+            valid &= tokenPart.startsWith("auth=Bearer ");
+            valid &= to == bytes.length - 2; // trailing { 0x01, 0x01 }
+         }
+         if (valid) {
+            String user = userPart.substring(5);
+            String token = tokenPart.substring(12);
+            boolean success = authenticate(user, token);
+            result = new TokenSASLResult(success, user, token);
+         } else {
+            result = new TokenSASLResult(false, null, null);
+         }
+      }
+
+      if (result.isSuccess()) {
+         return null;
+      }
+
+      // see 
https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#error_response
+      String address = remotingConnection.getTransportLocalAddress();
+      return 
Base64.getEncoder().encode(String.format("{\"status\":\"401\",\"schemes\":\"bearer\",\"scope\":\"%s\"}",
 address).getBytes());
+   }
+
+}
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2ServerSASLFactory.java
 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2ServerSASLFactory.java
new file mode 100644
index 0000000000..00dd931942
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2ServerSASLFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
+import org.apache.activemq.artemis.protocol.amqp.proton.AMQPRoutingHandler;
+import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.apache.activemq.artemis.spi.core.remoting.Connection;
+
+public class XOAuth2ServerSASLFactory implements ServerSASLFactory {
+
+   @Override
+   public String getMechanism() {
+      return XOAuth2SASL.MECHANISM_NAME;
+   }
+
+   @Override
+   public ServerSASL create(ActiveMQServer server, 
ProtocolManager<AmqpInterceptor, AMQPRoutingHandler> manager, Connection 
connection, RemotingConnection remotingConnection) {
+      return new XOAuth2SASL(server.getSecurityStore(), 
manager.getSecurityDomain(), connection.getProtocolConnection());
+   }
+
+   @Override
+   public int getPrecedence() {
+      return 16;
+   }
+
+   @Override
+   public boolean isDefaultPermitted() {
+      return false;
+   }
+
+}
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/main/resources/META-INF/services/org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory
 
b/artemis-protocols/artemis-amqp-protocol/src/main/resources/META-INF/services/org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory
index c8f54d2636..6587ae6ef4 100644
--- 
a/artemis-protocols/artemis-amqp-protocol/src/main/resources/META-INF/services/org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/main/resources/META-INF/services/org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory
@@ -2,5 +2,7 @@ 
org.apache.activemq.artemis.protocol.amqp.sasl.AnonymousServerSASLFactory
 org.apache.activemq.artemis.protocol.amqp.sasl.PlainServerSASLFactory
 org.apache.activemq.artemis.protocol.amqp.sasl.GSSAPIServerSASLFactory
 org.apache.activemq.artemis.protocol.amqp.sasl.ExternalServerSASLFactory
+org.apache.activemq.artemis.protocol.amqp.sasl.OAuthBearerServerSASLFactory
+org.apache.activemq.artemis.protocol.amqp.sasl.XOAuth2ServerSASLFactory
 
org.apache.activemq.artemis.protocol.amqp.sasl.scram.SHA256SCRAMServerSASLFactory
 
org.apache.activemq.artemis.protocol.amqp.sasl.scram.SHA512SCRAMServerSASLFactory
\ No newline at end of file
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerSASLTest.java
 
b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerSASLTest.java
new file mode 100644
index 0000000000..5299a74d7f
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/sasl/OAuthBearerSASLTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import org.apache.activemq.artemis.api.core.JsonUtil;
+import org.apache.activemq.artemis.core.security.SecurityStore;
+import org.apache.activemq.artemis.json.JsonObject;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.junit.jupiter.api.Test;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class OAuthBearerSASLTest {
+
+   @Test
+   public void properOAUTHBEARERSASL() throws Exception {
+      SecurityStore securityStore = mock(SecurityStore.class);
+      when(securityStore.isSecurityEnabled()).thenReturn(true);
+      when(securityStore.authenticate("me", "JWT-token", null, 
null)).thenReturn("OK");
+      OAuthBearerSASL sasl = new OAuthBearerSASL(securityStore, null, null);
+      
assertNull(sasl.processSASL(String.format("n,a=%s,\001host=localhost\001port=1234\001auth=Bearer
 %s\001\001", "me", "JWT-token").getBytes()));
+
+      SASLResult result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      TokenSASLResult tokenResult = (TokenSASLResult) result;
+      assertTrue(tokenResult.isSuccess());
+      assertEquals("me", tokenResult.getUser());
+      assertEquals("JWT-token", tokenResult.getToken());
+   }
+
+   @Test
+   public void noInitialResponse() {
+      SecurityStore securityStore = mock(SecurityStore.class);
+      RemotingConnection remotingConnection = mock(RemotingConnection.class);
+      
when(remotingConnection.getTransportLocalAddress()).thenReturn("tcp://localhost:5672");
+
+      OAuthBearerSASL sasl = new OAuthBearerSASL(securityStore, null, 
remotingConnection);
+
+      byte[] challenge = sasl.processSASL(null);
+      assertNotNull(challenge);
+      SASLResult result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      TokenSASLResult tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      challenge = sasl.processSASL(new byte[0]);
+      assertNotNull(challenge);
+      result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      challenge = sasl.processSASL(String.format("user=%s\001auth=Bearer 
%s\001", "me", "JWT-token").getBytes());
+      assertNotNull(challenge);
+      result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      challenge = sasl.processSASL(String.format("user=%s\002auth=Bearer 
%s\001\001", "me", "JWT-token").getBytes());
+      assertNotNull(challenge);
+      result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      String json = new String(Base64.getDecoder().decode(challenge));
+      JsonObject jsonObject = JsonUtil.readJsonObject(json);
+      assertEquals("invalid_token", jsonObject.getString("status"));
+   }
+
+}
diff --git 
a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2SASLTest.java
 
b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2SASLTest.java
new file mode 100644
index 0000000000..e2be7acca0
--- /dev/null
+++ 
b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/sasl/XOAuth2SASLTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.activemq.artemis.protocol.amqp.sasl;
+
+import org.apache.activemq.artemis.api.core.JsonUtil;
+import org.apache.activemq.artemis.core.security.SecurityStore;
+import org.apache.activemq.artemis.json.JsonObject;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.junit.jupiter.api.Test;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class XOAuth2SASLTest {
+
+   @Test
+   public void properXOAUTH2SASL() throws Exception {
+      SecurityStore securityStore = mock(SecurityStore.class);
+      when(securityStore.isSecurityEnabled()).thenReturn(true);
+      when(securityStore.authenticate("me", "JWT-token", null, 
null)).thenReturn("OK");
+      XOAuth2SASL sasl = new XOAuth2SASL(securityStore, null, null);
+      assertNull(sasl.processSASL(String.format("user=%s\001auth=Bearer 
%s\001\001", "me", "JWT-token").getBytes()));
+
+      SASLResult result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      TokenSASLResult tokenResult = (TokenSASLResult) result;
+      assertTrue(tokenResult.isSuccess());
+      assertEquals("me", tokenResult.getUser());
+      assertEquals("JWT-token", tokenResult.getToken());
+   }
+
+   @Test
+   public void noInitialResponse() {
+      SecurityStore securityStore = mock(SecurityStore.class);
+      RemotingConnection remotingConnection = mock(RemotingConnection.class);
+      
when(remotingConnection.getTransportLocalAddress()).thenReturn("tcp://localhost:5672");
+
+      XOAuth2SASL sasl = new XOAuth2SASL(securityStore, null, 
remotingConnection);
+
+      byte[] challenge = sasl.processSASL(null);
+      assertNotNull(challenge);
+      SASLResult result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      TokenSASLResult tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      challenge = sasl.processSASL(new byte[0]);
+      assertNotNull(challenge);
+      result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      challenge = sasl.processSASL(String.format("user=%s\001auth=Bearer 
%s\001", "me", "JWT-token").getBytes());
+      assertNotNull(challenge);
+      result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      challenge = sasl.processSASL(String.format("user=%s\002auth=Bearer 
%s\001\001", "me", "JWT-token").getBytes());
+      assertNotNull(challenge);
+      result = sasl.result();
+      assertInstanceOf(TokenSASLResult.class, result);
+      tokenResult = (TokenSASLResult) result;
+      assertFalse(tokenResult.isSuccess());
+      assertNull(tokenResult.getUser());
+      assertNull(tokenResult.getToken());
+
+      String json = new String(Base64.getDecoder().decode(challenge));
+      JsonObject jsonObject = JsonUtil.readJsonObject(json);
+      assertEquals("401", jsonObject.getString("status"));
+   }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to