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

vavrtom pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git


The following commit(s) were added to refs/heads/main by this push:
     new c88188678f QPID-8648: [Broker-J] Allow for max frame size >4096 before 
Open frame (SASL) (#234)
c88188678f is described below

commit c88188678fb1e9b804cacf702507b28bbbf9d105
Author: Daniil Kirilyuk <[email protected]>
AuthorDate: Mon Jan 29 13:17:17 2024 +0100

    QPID-8648: [Broker-J] Allow for max frame size >4096 before Open frame 
(SASL) (#234)
---
 .../protocol/v1_0/AMQPConnection_1_0Impl.java      |   4 +-
 .../v1_0/transport/security/sasl/SaslTest.java     |  37 ++++
 systests/systests-utils/pom.xml                    |   4 +
 .../qpid/tests/utils/AddOAuth2MockProvider.java    |  32 ++++
 .../utils/EmbeddedBrokerPerClassAdminImpl.java     | 203 +++++++++++++++++----
 .../qpid/tests/utils/OAuth2MockEndpoint.java       | 134 ++++++++++++++
 .../qpid/tests/utils/OAuth2MockEndpointHolder.java | 180 ++++++++++++++++++
 7 files changed, 553 insertions(+), 41 deletions(-)

diff --git 
a/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/AMQPConnection_1_0Impl.java
 
b/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/AMQPConnection_1_0Impl.java
index 15ccb3312f..2bd689ef6f 100644
--- 
a/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/AMQPConnection_1_0Impl.java
+++ 
b/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/AMQPConnection_1_0Impl.java
@@ -185,7 +185,7 @@ public class AMQPConnection_1_0Impl extends 
AbstractAMQPConnection<AMQPConnectio
     private final SubjectCreator _subjectCreator;
 
     private int _channelMax = 0;
-    private int _maxFrameSize = 4096;
+    private int _maxFrameSize;
     private String _remoteContainerId;
 
     private SocketAddress _remoteAddress;
@@ -262,6 +262,8 @@ public class AMQPConnection_1_0Impl extends 
AbstractAMQPConnection<AMQPConnectio
         _incomingIdleTimeout = 1000L * port.getHeartbeatDelay();
 
         _frameWriter = new FrameWriter(getDescribedTypeRegistry(), 
getSender());
+
+        _maxFrameSize = getBroker().getNetworkBufferSize();
     }
 
     @Override
diff --git 
a/systests/protocol-tests-amqp-1-0/src/test/java/org/apache/qpid/tests/protocol/v1_0/transport/security/sasl/SaslTest.java
 
b/systests/protocol-tests-amqp-1-0/src/test/java/org/apache/qpid/tests/protocol/v1_0/transport/security/sasl/SaslTest.java
index f903a3ac21..9ef19e8ae1 100644
--- 
a/systests/protocol-tests-amqp-1-0/src/test/java/org/apache/qpid/tests/protocol/v1_0/transport/security/sasl/SaslTest.java
+++ 
b/systests/protocol-tests-amqp-1-0/src/test/java/org/apache/qpid/tests/protocol/v1_0/transport/security/sasl/SaslTest.java
@@ -45,6 +45,7 @@ import 
org.apache.qpid.server.protocol.v1_0.type.transport.Open;
 import org.apache.qpid.tests.protocol.SpecificationTest;
 import org.apache.qpid.tests.protocol.v1_0.FrameTransport;
 import org.apache.qpid.tests.protocol.v1_0.Interaction;
+import org.apache.qpid.tests.utils.AddOAuth2MockProvider;
 import org.apache.qpid.tests.utils.BrokerAdmin;
 import org.apache.qpid.tests.utils.BrokerAdminUsingTestBase;
 
@@ -52,6 +53,7 @@ public class SaslTest extends BrokerAdminUsingTestBase
 {
     private static final Symbol CRAM_MD5 = Symbol.getSymbol("CRAM-MD5");
     private static final Symbol PLAIN = Symbol.getSymbol("PLAIN");
+    private static final Symbol OAUTH2 = Symbol.getSymbol("XOAUTH2");
 
     private static final byte[] SASL_AMQP_HEADER_BYTES = 
"AMQP\3\1\0\0".getBytes(StandardCharsets.UTF_8);
     private static final byte[] AMQP_HEADER_BYTES = 
"AMQP\0\1\0\0".getBytes(StandardCharsets.UTF_8);
@@ -329,4 +331,39 @@ public class SaslTest extends BrokerAdminUsingTestBase
             transport.assertNoMoreResponsesAndChannelClosed();
         }
     }
+
+    @Test
+    @AddOAuth2MockProvider()
+    @SpecificationTest(section = "5.3.2", description = "SASL Negotiation")
+    public void veryLongOauth2Token() throws Exception
+    {
+        final byte[] TOKEN = ("user=xxx\1auth=Bearer " + "A".repeat(10_0000) + 
"\1host=localhost\1\1")
+                .getBytes(StandardCharsets.US_ASCII);
+
+        try (final FrameTransport transport = new 
FrameTransport(getBrokerAdmin(), BrokerAdmin.PortType.AMQP).connect())
+        {
+            final Interaction interaction = transport.newInteraction();
+            final byte[] saslHeaderResponse = 
interaction.protocolHeader(SASL_AMQP_HEADER_BYTES)
+                    .negotiateProtocol().consumeResponse()
+                    .getLatestResponse(byte[].class);
+            assertThat(saslHeaderResponse, 
is(equalTo(SASL_AMQP_HEADER_BYTES)));
+
+            final SaslMechanisms saslMechanismsResponse = 
interaction.consumeResponse().getLatestResponse(SaslMechanisms.class);
+            
assumeTrue(hasItem(OAUTH2).matches(Arrays.asList(saslMechanismsResponse.getSaslServerMechanisms())));
+
+            final Binary initialResponse = new Binary(TOKEN);
+            final SaslOutcome saslOutcome = interaction.saslMechanism(OAUTH2)
+                    .saslInitialResponse(initialResponse)
+                    .saslInit().consumeResponse()
+                    .getLatestResponse(SaslOutcome.class);
+            assertThat(saslOutcome.getCode(), equalTo(SaslCode.OK));
+
+            final byte[] headerResponse = 
interaction.protocolHeader(AMQP_HEADER_BYTES)
+                    .negotiateProtocol().consumeResponse()
+                    .getLatestResponse(byte[].class);
+            assertThat(headerResponse, is(equalTo(AMQP_HEADER_BYTES)));
+
+            transport.assertNoMoreResponses();
+        }
+    }
 }
diff --git a/systests/systests-utils/pom.xml b/systests/systests-utils/pom.xml
index fcb72fe20d..d67a5a497d 100644
--- a/systests/systests-utils/pom.xml
+++ b/systests/systests-utils/pom.xml
@@ -61,6 +61,10 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git 
a/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/AddOAuth2MockProvider.java
 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/AddOAuth2MockProvider.java
new file mode 100644
index 0000000000..11f7a7eec6
--- /dev/null
+++ 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/AddOAuth2MockProvider.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * 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.tests.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface AddOAuth2MockProvider
+{
+}
diff --git 
a/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/EmbeddedBrokerPerClassAdminImpl.java
 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/EmbeddedBrokerPerClassAdminImpl.java
index 03d997f0a3..c995b3ac02 100644
--- 
a/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/EmbeddedBrokerPerClassAdminImpl.java
+++ 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/EmbeddedBrokerPerClassAdminImpl.java
@@ -24,11 +24,12 @@ import java.io.File;
 import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.PrivilegedAction;
+import java.security.cert.X509Certificate;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -38,6 +39,12 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
 import javax.security.auth.Subject;
 
 import com.google.common.util.concurrent.Futures;
@@ -48,6 +55,7 @@ import org.slf4j.LoggerFactory;
 import org.apache.qpid.server.SystemLauncher;
 import org.apache.qpid.server.SystemLauncherListener;
 import 
org.apache.qpid.server.logging.logback.LogbackLoggingSystemLauncherListener;
+import org.apache.qpid.server.model.AuthenticationProvider;
 import org.apache.qpid.server.model.Broker;
 import org.apache.qpid.server.model.ConfiguredObject;
 import org.apache.qpid.server.model.Exchange;
@@ -58,20 +66,25 @@ import org.apache.qpid.server.model.Port;
 import org.apache.qpid.server.model.Queue;
 import org.apache.qpid.server.model.SystemConfig;
 import org.apache.qpid.server.model.VirtualHostNode;
+import org.apache.qpid.server.model.port.AmqpPort;
 import org.apache.qpid.server.plugin.PluggableService;
 import org.apache.qpid.server.security.auth.TaskPrincipal;
+import 
org.apache.qpid.server.security.auth.manager.oauth2.cloudfoundry.CloudFoundryOAuth2IdentityResolverService;
 import org.apache.qpid.server.store.MemoryConfigurationStore;
 import org.apache.qpid.server.util.FileUtils;
 import org.apache.qpid.server.virtualhost.QueueManagingVirtualHost;
 import org.apache.qpid.server.virtualhostnode.JsonVirtualHostNode;
+import org.apache.qpid.test.utils.tls.TlsResource;
 
-@SuppressWarnings("unused")
+@SuppressWarnings({"java:S116", "unchecked", "unused"})
+// sonar complains about variable names
 @PluggableService
 public class EmbeddedBrokerPerClassAdminImpl implements BrokerAdmin
 {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(EmbeddedBrokerPerClassAdminImpl.class);
     public static final String TYPE = "EMBEDDED_BROKER_PER_CLASS";
     private final Map<String, Integer> _ports = new HashMap<>();
+    private String _tempAuthProvider;
     private SystemLauncher _systemLauncher;
     private Broker<?> _broker;
     private VirtualHostNode<?> _currentVirtualHostNode;
@@ -89,7 +102,8 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
             _currentWorkDirectory = 
Files.createTempDirectory(String.format("qpid-work-%s-%s-", timestamp, 
testClass.getSimpleName())).toString();
 
             ConfigItem[] configItems = (ConfigItem[]) 
testClass.getAnnotationsByType(ConfigItem.class);
-            Arrays.stream(configItems).filter(ConfigItem::jvm).forEach(i -> {
+            Arrays.stream(configItems).filter(ConfigItem::jvm).forEach(i ->
+            {
                 _preservedProperties.put(i.name(), 
System.getProperty(i.name()));
                 System.setProperty(i.name(), i.value());
             });
@@ -97,10 +111,8 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
             context.put("qpid.work_dir", _currentWorkDirectory);
             context.put("qpid.port.protocol_handshake_timeout", "1000000");
             context.putAll(Arrays.stream(configItems)
-                                 .filter(i -> !i.jvm())
-                                 .collect(Collectors.toMap(ConfigItem::name,
-                                                           ConfigItem::value,
-                                                           (name, value) -> 
value)));
+                    .filter(i -> !i.jvm())
+                    .collect(Collectors.toMap(ConfigItem::name, 
ConfigItem::value, (name, value) -> value)));
 
             Map<String,Object> systemConfigAttributes = new HashMap<>();
             systemConfigAttributes.put(ConfiguredObject.CONTEXT, context);
@@ -118,7 +130,7 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
             systemLauncherListeners.add(new 
LogbackLoggingSystemLauncherListener());
             systemLauncherListeners.add(new 
ShutdownLoggingSystemLauncherListener());
             systemLauncherListeners.add(new PortExtractingLauncherListener());
-            _systemLauncher = new 
SystemLauncher(systemLauncherListeners.toArray(new 
SystemLauncherListener[systemLauncherListeners.size()]));
+            _systemLauncher = new 
SystemLauncher(systemLauncherListeners.toArray(new SystemLauncherListener[0]));
 
             _systemLauncher.startup(systemConfigAttributes);
         }
@@ -148,9 +160,9 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
         String blueprint = 
System.getProperty("virtualhostnode.context.blueprint");
 
         Map<String, Object> attributes = new HashMap<>();
-        attributes.put(VirtualHostNode.NAME, virtualHostNodeName);
-        attributes.put(VirtualHostNode.TYPE, storeType);
-        attributes.put(VirtualHostNode.CONTEXT, 
Collections.singletonMap("virtualhostBlueprint", blueprint));
+        attributes.put(ConfiguredObject.NAME, virtualHostNodeName);
+        attributes.put(ConfiguredObject.TYPE, storeType);
+        attributes.put(ConfiguredObject.CONTEXT, 
Collections.singletonMap("virtualhostBlueprint", blueprint));
         attributes.put(VirtualHostNode.DEFAULT_VIRTUAL_HOST_NODE, true);
         attributes.put(VirtualHostNode.VIRTUALHOST_INITIAL_CONFIGURATION, 
blueprint);
         if (storeDir != null)
@@ -158,8 +170,12 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
             attributes.put(JsonVirtualHostNode.STORE_PATH, storeDir);
         }
 
-        _currentVirtualHostNode = _broker.createChild(VirtualHostNode.class, 
attributes);
+        if (method.getAnnotation(AddOAuth2MockProvider.class) != null)
+        {
+            createOAuth2AuthenticationManager();
+        }
 
+        _currentVirtualHostNode = _broker.createChild(VirtualHostNode.class, 
attributes);
     }
 
     @Override
@@ -182,6 +198,16 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
             }
             return null;
         });
+
+        if (method.getAnnotation(AddOAuth2MockProvider.class) != null)
+        {
+            final AmqpPort<?> port = (AmqpPort<?>) 
_broker.getPorts().stream().filter(p -> "AMQP".equals(p.getName()))
+                    .findFirst().orElse(null);
+            if (port != null)
+            {
+                port.setAttributes(Map.of(Port.AUTHENTICATION_PROVIDER, 
_tempAuthProvider));
+            }
+        }
     }
 
     @Override
@@ -221,10 +247,10 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
     public void createQueue(final String queueName)
     {
         final Map<String, Object> attributes = new HashMap<>();
-        attributes.put(Queue.NAME, queueName);
-        attributes.put(Queue.TYPE, "standard");
-        final Queue queue = 
_currentVirtualHostNode.getVirtualHost().createChild(Queue.class, attributes);
-        final Exchange exchange = 
_currentVirtualHostNode.getVirtualHost().getChildByName(Exchange.class, 
"amq.direct");
+        attributes.put(ConfiguredObject.NAME, queueName);
+        attributes.put(ConfiguredObject.TYPE, "standard");
+        final Queue<?> queue = 
_currentVirtualHostNode.getVirtualHost().createChild(Queue.class, attributes);
+        final Exchange<?> exchange = 
_currentVirtualHostNode.getVirtualHost().getChildByName(Exchange.class, 
"amq.direct");
         exchange.bind(queueName, queueName, Collections.emptyMap(), false);
     }
 
@@ -310,7 +336,7 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
                 @Override
                 public Map<String, Object> getHeaders()
                 {
-                    return null;
+                    return Map.of();
                 }
 
                 @Override
@@ -332,7 +358,7 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
     @Override
     public int getQueueDepthMessages(final String testQueueName)
     {
-        Queue queue = 
_currentVirtualHostNode.getVirtualHost().getChildByName(Queue.class, 
testQueueName);
+        Queue<?> queue = 
_currentVirtualHostNode.getVirtualHost().getChildByName(Queue.class, 
testQueueName);
         return queue.getQueueDepthMessages();
     }
 
@@ -432,17 +458,89 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
         return TYPE;
     }
 
-    private Queue getQueue(final String queueName)
+    private Queue<?> getQueue(final String queueName)
     {
-        Collection<Queue> queues = 
_currentVirtualHostNode.getVirtualHost().getChildren(Queue.class);
-        for (Queue queue : queues)
+        return 
_currentVirtualHostNode.getVirtualHost().getChildren(Queue.class).stream()
+                .filter(queue -> queue.getName().equals(queueName))
+                .findFirst()
+                .orElseThrow(() -> new NotFoundException(String.format("Queue 
'%s' not found", queueName)));
+    }
+
+    private void createOAuth2AuthenticationManager()
+    {
+        final String TEST_CLIENT_ID = "testClientId";
+        final String TEST_CLIENT_SECRET = "testClientSecret";
+        final String TEST_IDENTITY_RESOLVER_TYPE = 
CloudFoundryOAuth2IdentityResolverService.TYPE;
+        final String TEST_URI_PATTERN = "https://%s:%d%s";;
+        final String TEST_AUTHORIZATION_ENDPOINT_NEEDS_AUTH = "true";
+        final String TEST_SCOPE = "testScope";
+        final String TEST_TRUST_STORE_NAME = null;
+        final String TEST_ENDPOINT_HOST = "localhost";
+        final String TEST_AUTHORIZATION_ENDPOINT_PATH = "/testauth";
+        final String TEST_TOKEN_ENDPOINT_PATH = "/testtoken";
+        final String TEST_IDENTITY_RESOLVER_ENDPOINT_PATH = "/testidresolver";
+        final String TEST_POST_LOGOUT_PATH = "/testpostlogout";
+
+        OAuth2MockEndpointHolder server;
+        try
+        {
+            final TlsResource tlsResource = new TlsResource();
+            tlsResource.beforeAll(null);
+            final Path keyStore = 
tlsResource.createSelfSignedKeyStore("CN=127.0.0.1");
+            server = new 
OAuth2MockEndpointHolder(keyStore.toFile().getAbsolutePath(), 
tlsResource.getSecret(), tlsResource.getKeyStoreType());
+            final OAuth2MockEndpoint identityResolverEndpoint = new 
OAuth2MockEndpoint();
+            identityResolverEndpoint.putExpectedParameter("token", 
"A".repeat(10_0000));
+            identityResolverEndpoint.setExpectedMethod("POST");
+            identityResolverEndpoint.setNeedsAuth(true);
+            identityResolverEndpoint.setResponse(200, 
String.format("{\"user_name\":\"%s\"}", "xxx"));
+
+            server.start();
+            server.setEndpoints(Map.of(TEST_IDENTITY_RESOLVER_ENDPOINT_PATH, 
identityResolverEndpoint));
+
+            final TrustManager[] trustingTrustManager = new TrustManager[]{new 
TrustingTrustManager()};
+
+            final SSLContext sc = SSLContext.getInstance("TLSv1.3");
+            sc.init(null, trustingTrustManager, new 
java.security.SecureRandom());
+            
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+            HttpsURLConnection.setDefaultHostnameVerifier(new 
BlindHostnameVerifier());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+
+        final Map<String, Object> authProviderAttributes = new HashMap<>();
+        String testOAuthProvider = "testOAuthProvider";
+        authProviderAttributes.put(ConfiguredObject.NAME, testOAuthProvider);
+        authProviderAttributes.put(ConfiguredObject.TYPE, "OAuth2");
+        authProviderAttributes.put("clientId", TEST_CLIENT_ID);
+        authProviderAttributes.put("clientSecret", TEST_CLIENT_SECRET);
+        authProviderAttributes.put("identityResolverType", 
TEST_IDENTITY_RESOLVER_TYPE);
+        authProviderAttributes.put("authorizationEndpointURI",
+                String.format(TEST_URI_PATTERN, TEST_ENDPOINT_HOST, 
server.getPort(), TEST_AUTHORIZATION_ENDPOINT_PATH));
+        authProviderAttributes.put("tokenEndpointURI",
+                String.format(TEST_URI_PATTERN, TEST_ENDPOINT_HOST, 
server.getPort(), TEST_TOKEN_ENDPOINT_PATH));
+        authProviderAttributes.put("tokenEndpointNeedsAuth", 
TEST_AUTHORIZATION_ENDPOINT_NEEDS_AUTH);
+        authProviderAttributes.put("identityResolverEndpointURI",
+                String.format(TEST_URI_PATTERN, TEST_ENDPOINT_HOST, 
server.getPort(), TEST_IDENTITY_RESOLVER_ENDPOINT_PATH));
+        authProviderAttributes.put("postLogoutURI",
+                String.format(TEST_URI_PATTERN, TEST_ENDPOINT_HOST, 
server.getPort(), TEST_POST_LOGOUT_PATH));
+        authProviderAttributes.put("scope", TEST_SCOPE);
+        authProviderAttributes.put("trustStore", TEST_TRUST_STORE_NAME);
+        authProviderAttributes.put("secureOnlyMechanisms", List.of());
+        final AuthenticationProvider<?> auth = 
_broker.createChild(AuthenticationProvider.class, authProviderAttributes);
+        final AmqpPort<?> port = (AmqpPort<?>) 
_broker.getPorts().stream().filter(p -> "AMQP".equals(p.getName()))
+                .findFirst().orElse(null);
+        if (port != null)
         {
-            if (queue.getName().equals(queueName))
+            final AuthenticationProvider<?> authProvider = 
(AuthenticationProvider<?>) port
+                    .getAttribute(Port.AUTHENTICATION_PROVIDER);
+            if (authProvider != null)
             {
-                return queue;
+                _tempAuthProvider = authProvider.getName();
             }
+            port.setAttributes(Map.of(Port.AUTHENTICATION_PROVIDER, 
testOAuthProvider));
         }
-        throw new NotFoundException(String.format("Queue '%s' not found", 
queueName));
     }
 
     private class PortExtractingLauncherListener implements 
SystemLauncherListener
@@ -452,30 +550,25 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
         @Override
         public void beforeStartup()
         {
-
+            // logic not used in tests
         }
 
         @Override
         public void errorOnStartup(final RuntimeException e)
         {
-
+            // logic not used in tests
         }
 
         @Override
         public void afterStartup()
         {
-
             if (_systemConfig == null)
             {
                 throw new IllegalStateException("System config is required");
             }
 
             _broker = (Broker<?>) _systemConfig.getContainer();
-            Collection<Port> ports = _broker.getChildren(Port.class);
-            for (Port port : ports)
-            {
-                _ports.put(port.getName(), port.getBoundPort());
-            }
+            _broker.getChildren(Port.class).forEach(port -> 
_ports.put(port.getName(), port.getBoundPort()));
         }
 
         @Override
@@ -487,23 +580,22 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
         @Override
         public void onContainerClose(final SystemConfig<?> systemConfig)
         {
-
+            // logic not used in tests
         }
 
         @Override
         public void onShutdown(final int exitCode)
         {
-
+            // logic not used in tests
         }
 
         @Override
         public void exceptionOnShutdown(final Exception e)
         {
-
+            // logic not used in tests
         }
     }
 
-
     private static class UncaughtExceptionHandler implements 
Thread.UncaughtExceptionHandler
     {
         private final AtomicInteger _count = new AtomicInteger(0);
@@ -541,13 +633,44 @@ public class EmbeddedBrokerPerClassAdminImpl implements 
BrokerAdmin
         @Override
         public void exceptionOnShutdown(final Exception e)
         {
-            if (e instanceof IllegalStateException
-                || e instanceof IllegalStateTransitionException)
+            if (e instanceof IllegalStateException || e instanceof 
IllegalStateTransitionException)
             {
-                System.out.println(
-                        "IllegalStateException occurred on broker shutdown in 
test ");
+                LOGGER.error("IllegalStateException occurred on broker 
shutdown in test");
             }
         }
     }
 
+    // sonar: hostname verifier is used for test purposes
+    @SuppressWarnings("java:S4830")
+    private static final class TrustingTrustManager implements X509TrustManager
+    {
+        @Override
+        public void checkClientTrusted(final X509Certificate[] certs, final 
String authType)
+        {
+            // trust manager is used for test purposes, always trusts client
+        }
+
+        @Override
+        public void checkServerTrusted(final X509Certificate[] certs, final 
String authType)
+        {
+            // trust manager is used for test purposes, always trusts server
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers()
+        {
+            return new X509Certificate[0];
+        }
+    }
+
+    // sonar: hostname verifier is used for test purposes
+    @SuppressWarnings("java:S5527")
+    private static final class BlindHostnameVerifier implements 
HostnameVerifier
+    {
+        @Override
+        public boolean verify(final String arg0, final SSLSession arg1)
+        {
+            return true;
+        }
+    }
 }
diff --git 
a/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/OAuth2MockEndpoint.java
 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/OAuth2MockEndpoint.java
new file mode 100644
index 0000000000..766d579a94
--- /dev/null
+++ 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/OAuth2MockEndpoint.java
@@ -0,0 +1,134 @@
+/*
+ *
+ * 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.tests.utils;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@SuppressWarnings({"java:S116"})
+// sonar complains about variable names
+class OAuth2MockEndpoint
+{
+    private final Map<String, String> _expectedParameters = new HashMap<>();
+
+    private HttpServletResponse _servletResponse;
+    private String _expectedMethod;
+    private String _responseString;
+    private int _responseCode = 200;
+    private boolean _needsAuth;
+
+    public void handleRequest(final HttpServletRequest request, final 
HttpServletResponse response) throws IOException
+    {
+        _servletResponse = response;
+        response.setContentType("application/json");
+        if (_needsAuth)
+        {
+            final String expected = "Basic " +
+                    Base64.getEncoder().encodeToString(("testClientId" + ":" +
+                    "testClientSecret").getBytes(StandardCharsets.UTF_8));
+            doAssertEquals("Authorization required", expected, 
request.getHeader("Authorization"));
+        }
+        if (_expectedMethod != null)
+        {
+            doAssertEquals("Request uses unexpected HTTP method", 
_expectedMethod, request.getMethod());
+        }
+        final Map<String, String[]> parameters = request.getParameterMap();
+        for (final Map.Entry<String, String> entry : 
_expectedParameters.entrySet())
+        {
+            final String expectedParameter = entry.getKey();
+            final String[] parameterValues = parameters.get(expectedParameter);
+            doAssertTrue(String.format("Request is missing parameter '%s'", 
expectedParameter),
+                    parameters.containsKey(expectedParameter));
+            doAssertEquals(String.format("Request has parameter '%s' specified 
more than once", expectedParameter),
+                    1, parameterValues.length);
+            doAssertEquals(String.format("Request parameter '%s' has 
unexpected value", expectedParameter),
+                    _expectedParameters.get(expectedParameter), 
parameterValues[0]);
+        }
+
+        if (_responseCode != 0)
+        {
+            response.setStatus(_responseCode);
+        }
+        
response.getOutputStream().write(_responseString.getBytes(StandardCharsets.UTF_8));
+    }
+
+    public void putExpectedParameter(final String key, final String value)
+    {
+        _expectedParameters.put(key, value);
+    }
+
+    public void setExpectedMethod(final String expectedMethod)
+    {
+        _expectedMethod = expectedMethod;
+    }
+
+    public void setResponseString(final String responseString)
+    {
+        _responseString = responseString;
+    }
+
+    public void setResponseCode(final int responseCode)
+    {
+        _responseCode = responseCode;
+    }
+
+    public void setResponse(final int code, final String message)
+    {
+        setResponseCode(code);
+        setResponseString(message);
+    }
+
+    public void setNeedsAuth(final boolean needsAuth)
+    {
+        this._needsAuth = needsAuth;
+    }
+
+    private void doAssertEquals(final String msg, final Object expected, final 
Object actual) throws IOException
+    {
+        if ((expected == null && actual != null) || (expected != null && 
!expected.equals(actual)))
+        {
+            sendError(String.format("%s; Expected: '%s'; Actual: '%s'", msg, 
expected, actual));
+        }
+    }
+
+    private void doAssertTrue(final String msg, final boolean condition) 
throws IOException
+    {
+        if (!condition)
+        {
+            sendError(msg);
+        }
+    }
+
+    private void sendError(final String errorDescription) throws IOException
+    {
+        _servletResponse.setStatus(500);
+        final String responseString = 
String.format("{\"error\":\"test_failure\",\"error_description\":\"%s\"}",
+                errorDescription);
+        _servletResponse.getOutputStream().write(responseString.getBytes());
+        throw new AssertionError(responseString);
+    }
+}
diff --git 
a/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/OAuth2MockEndpointHolder.java
 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/OAuth2MockEndpointHolder.java
new file mode 100644
index 0000000000..dd54318588
--- /dev/null
+++ 
b/systests/systests-utils/src/main/java/org/apache/qpid/tests/utils/OAuth2MockEndpointHolder.java
@@ -0,0 +1,180 @@
+/*
+ *
+ * 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.tests.utils;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLEngine;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import org.apache.qpid.server.configuration.CommonProperties;
+import org.apache.qpid.server.transport.network.security.ssl.SSLUtil;
+
+@SuppressWarnings({"java:S116"})
+// sonar complains about variable names
+public class OAuth2MockEndpointHolder
+{
+    private final Server _server;
+    private final ServerConnector _connector;
+    private volatile Map<String, OAuth2MockEndpoint> _endpoints;
+
+    OAuth2MockEndpointHolder(final String keyStorePath, final String 
keyStorePassword, final String keyStoreType)
+            throws IOException
+    {
+        this(Map.of(), keyStorePath, keyStorePassword, keyStoreType);
+    }
+
+    private OAuth2MockEndpointHolder(final Map<String, OAuth2MockEndpoint> 
endpoints,
+                                     final String keyStorePath,
+                                     final String keyStorePassword,
+                                     final String keyStoreType)
+            throws IOException
+    {
+        _endpoints = endpoints;
+        final List<String> protocolAllowList =
+                
getSystemPropertyAsList(CommonProperties.QPID_SECURITY_TLS_PROTOCOL_ALLOW_LIST,
+                                        
CommonProperties.QPID_SECURITY_TLS_PROTOCOL_ALLOW_LIST_DEFAULT);
+        final List<String> protocolDenyList =
+                
getSystemPropertyAsList(CommonProperties.QPID_SECURITY_TLS_PROTOCOL_DENY_LIST,
+                                        
CommonProperties.QPID_SECURITY_TLS_PROTOCOL_DENY_LIST_DEFAULT);
+        final List<String> cipherSuiteAllowList =
+                
getSystemPropertyAsList(CommonProperties.QPID_SECURITY_TLS_CIPHER_SUITE_ALLOW_LIST,
+                                        
CommonProperties.QPID_SECURITY_TLS_CIPHER_SUITE_ALLOW_LIST_DEFAULT);
+        final List<String> cipherSuiteDenyList =
+                
getSystemPropertyAsList(CommonProperties.QPID_SECURITY_TLS_CIPHER_SUITE_DENY_LIST,
+                                        
CommonProperties.QPID_SECURITY_TLS_CIPHER_SUITE_DENY_LIST_DEFAULT);
+
+        _server = new Server();
+        final SslContextFactory.Server sslContextFactory = new 
SslContextFactory.Server()
+        {
+            @Override
+            public void customize(final SSLEngine sslEngine)
+            {
+                super.customize(sslEngine);
+                SSLUtil.updateEnabledCipherSuites(sslEngine, 
cipherSuiteAllowList, cipherSuiteDenyList);
+                SSLUtil.updateEnabledTlsProtocols(sslEngine, 
protocolAllowList, protocolDenyList);
+            }
+        };
+        sslContextFactory.setKeyStorePassword(keyStorePassword);
+        
sslContextFactory.setKeyStoreResource(Resource.newResource(keyStorePath));
+        sslContextFactory.setKeyStoreType(keyStoreType);
+
+        // override default jetty excludes as valid IBM JDK are excluded
+        // causing SSL handshake failure (due to default exclude '^SSL_.*$')
+        sslContextFactory.setExcludeCipherSuites("^.*_(MD5|SHA|SHA1)$",
+                                                 "^TLS_RSA_.*$",
+                                                 "^SSL_RSA_.*$",
+                                                 "^.*_NULL_.*$",
+                                                 "^.*_anon_.*$");
+
+        final SecureRequestCustomizer secureRequestCustomizer = new 
SecureRequestCustomizer(false);
+        final HttpConfiguration httpsConfig = new HttpConfiguration();
+        httpsConfig.addCustomizer(secureRequestCustomizer);
+
+        _connector = new ServerConnector(_server, sslContextFactory, new 
HttpConnectionFactory(httpsConfig));
+        _connector.setPort(0);
+        _connector.setReuseAddress(true);
+        _server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(final String target,
+                               final Request baseRequest,
+                               final HttpServletRequest request,
+                               final HttpServletResponse response) throws 
IOException
+            {
+                baseRequest.setHandled(true);
+
+                try
+                {
+                    final OAuth2MockEndpoint
+                            mockEndpoint = 
_endpoints.get(request.getPathInfo());
+                    assertNotNull(mockEndpoint, String.format("Could not find 
mock endpoint for request path '%s'",
+                                                              
request.getPathInfo()));
+                    mockEndpoint.handleRequest(request, response);
+                }
+                catch (Throwable t)
+                {
+                    response.setStatus(500);
+                    
response.getOutputStream().write(String.format("{\"error\":\"test 
failure\",\"error_description\":\"%s\"}", t)
+                                                           .getBytes(UTF_8));
+                }
+            }
+        });
+        _server.addConnector(_connector);
+    }
+
+    public void start() throws Exception
+    {
+        _server.start();
+    }
+
+    public void stop() throws Exception
+    {
+        _server.stop();
+    }
+
+    public int getPort()
+    {
+        return _connector.getLocalPort();
+    }
+
+    public void setEndpoints(final Map<String, OAuth2MockEndpoint> endpoints)
+    {
+        _endpoints = endpoints;
+    }
+
+    private List<String> getSystemPropertyAsList(final String propertyName, 
final String defaultValue)
+    {
+        final String listAsString = System.getProperty(propertyName, 
defaultValue);
+        List<String> listOfStrings = List.of();
+        if (listAsString != null && !"".equals(listAsString))
+        {
+            try
+            {
+                listOfStrings = new 
ObjectMapper().readValue(listAsString.getBytes(UTF_8), new TypeReference<>() { 
});
+            }
+            catch (IOException e)
+            {
+                listOfStrings = Arrays.asList(listAsString.split("\\s*,\\s*"));
+            }
+        }
+        return listOfStrings;
+    }
+}


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


Reply via email to