diff --git a/docs/modules/ROOT/pages/includes/quarkus-solace-extension-common.adoc b/docs/modules/ROOT/pages/includes/quarkus-solace-extension-common.adoc index bac3aba..4d2cbf0 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-solace-extension-common.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-solace-extension-common.adoc @@ -59,4 +59,20 @@ Timeout in milliseconds to wait for messages to finish processing before shutdow --|long |`10000` +a| [[quarkus-solace_quarkus.client.tracing-enabled]]`link:#quarkus-solace_quarkus.client.tracing-enabled[client.tracing-enabled]` + + +[.description] +-- +Whether to enable or disable tracing for consumer or producer. + +// ifdef::add-copy-button-to-env-var[] +// Environment variable: env_var_with_copy_button:+++QUARKUS_SOLACE_DEVSERVICES_ENABLED+++[] +// endif::add-copy-button-to-env-var[] +// ifndef::add-copy-button-to-env-var[] +// Environment variable: `+++QUARKUS_SOLACE_DEVSERVICES_ENABLED+++` +// endif::add-copy-button-to-env-var[] +--|boolean +|`false` + |=== \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index ebbd2fc..a503373 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -8,7 +8,7 @@ The https://solace.com/products/platform/[Solace PubSub+ Platform]'s https://sol == Quarkus Extension for Solace -Solace Quarkus Extension for integrating with Solace PubSub+ message brokers. The extension provides the ability to publish or consume events from event mesh. +Solace Quarkus Extension provides the ability to integrate with Solace PubSub+ message brokers. The events generated by this extension will be available on event mesh and in the same way the extension can subscribe to any event available on event mesh. Users have the choice to use the extension in two ways @@ -69,7 +69,7 @@ include::includes/quarkus-solace-extension-common.adoc[leveloffset=+1, opts=opti [[configuring-quarkus-solace-client]] == Configuring Quarkus Solace Client -Solace Broker supports different ways to connect and authenticate users. This section shows how to pass <> to quarkus solace client in different scenarios. +Solace Broker supports different ways to connect and authenticate users. This section shows how to pass <> to quarkus solace client in different scenarios. Please refer to https://docs.solace.com/API-Developer-Online-Ref-Documentation/pubsubplus-java/constant-values.html#com.solace.messaging.config.SolaceProperties[Solace Properties] for supported properties and definitions. {empty}1. Connecting to a standalone broker with basic authentication [source,yaml] @@ -101,7 +101,7 @@ quarkus.solace.tls.trust-store-type= quarkus.solace.tls.trust-store-password= ---- -{empty}3. Connecting to a standalone broker with TLS, trust store and client certificate authentication. In case of client certificate authentication broker will read from configured username source(ex: Common Name, Subject Alt Name etc...). Refer to https://docs.solace.com/Security/Configuring-Client-Authentication.htm#Client-Cert[Solace Client Certificate Authentication]. +{empty}4. Connecting to a standalone broker with TLS, trust store and client certificate authentication. In case of client certificate authentication broker will read from configured username source(ex: Common Name, Subject Alt Name etc...). Refer to https://docs.solace.com/Security/Configuring-Client-Authentication.htm#Client-Cert[Solace Client Certificate Authentication]. [source,yaml] ---- quarkus.solace.host=tcps://localhost:55443 @@ -115,7 +115,49 @@ quarkus.solace.authentication.client-cert.keystore-password= quarkus.solace.authentication.client-cert.keystore-format= ---- -{empty}4. Connecting to a HA broker. Here you can configure both active and standby URL and the client will switch connectivity based on availability. +{empty}5. Connecting to a standalone broker with OAUTH authentication scheme. +[source,yaml] +---- +quarkus.solace.host=tcp://localhost:55555 +quarkus.solace.vpn=default +quarkus.solace.authentication.scheme=AUTHENTICATION_SCHEME_OAUTH2 +quarkus.solace.oidc.client-name=solace // client name provided in oidc client config below +quarkus.solace.oidc.refresh.interval=50s // Refresh interval should be less than access token expiry time. Otherwise extension will fail to update access token in solace session. + +quarkus.oidc-client.solace.auth-server-url=http://localhost:7777/auth/realms/master +quarkus.oidc-client.solace.client-id= +quarkus.oidc-client.solace.credentials.secret= +# 'client' is a shortcut for `client_credentials` +quarkus.oidc-client.solace.grant.type=client +---- + +{empty}6. Connecting to a standalone broker with TLS and OAUTH authentication scheme. +[source,yaml] +---- +quarkus.solace.host=tcps://localhost:55443 +quarkus.solace.vpn=default +quarkus.solace.authentication.scheme=AUTHENTICATION_SCHEME_OAUTH2 +quarkus.solace.tls.trust-store-path= +quarkus.solace.tls.trust-store-type= +quarkus.solace.tls.trust-store-password= +quarkus.solace.oidc.client-name=solace // client name provided in oidc client config below +quarkus.solace.oidc.refresh.interval=50s // Refresh interval should be less than access token expiry time. Otherwise extension will fail to update access token in solace session. + +quarkus.oidc-client.solace.auth-server-url=http://localhost:7777/auth/realms/master +quarkus.oidc-client.solace.client-id= +quarkus.oidc-client.solace.credentials.secret= +# 'client' is a shortcut for `client_credentials` +quarkus.oidc-client.solace.grant.type=client +quarkus.oidc-client.solace.tls.trust-store-file= +quarkus.oidc-client.solace.tls.key-store-password= +quarkus.oidc-client.solace.tls.verification= +---- + +For more details on Quarkus OIDC client supported configuration please refer to https://quarkus.io/guides/security-openid-connect-client-reference[OPENID CONNECT (OIDC) AND OAUTH2 CLIENT AND FILTERS] and https://quarkus.io/guides/security-oidc-configuration-properties-reference[OIDC configuration reference] + +NOTE: The current version is tested with client_credentials grant type where Solace broker is configured as Resource Server. + +{empty}7. Connecting to a HA broker. Here you can configure both active and standby URL and the client will switch connectivity based on availability. Remaining configurations and authentication mechanisms can be used as-is. [source,yaml] ---- quarkus.solace.host=tcp://active-host-name:55555,tcp://standby-host-name:55555 @@ -269,8 +311,8 @@ quarkus.solace.vpn=default quarkus.solace.authentication.basic.username=basic quarkus.solace.authentication.basic.password=basic -mp.messaging.incoming.temperatures-out.connector=quarkus-solace -mp.messaging.incoming.temperatures-out.producer.topic=temperatures +mp.messaging.outgoing.temperatures-out.connector=quarkus-solace +mp.messaging.outgoing.temperatures-out.producer.topic=temperatures ---- 1. When running in dev mode or tests dev services will automatically start a Solace PubSub+ broker and if broker configuration details are not provided the extension automatically picks up the details of broker started by dev services. @@ -427,6 +469,28 @@ public class TemperaturesProcessor { } ---- +[[open-telemetry-tracing]] +== Open Telemetry Tracing + +Extension supports generating trace messages for th messages consumed and published by the extension. To enabling tracing for consumers and producers use the below configuration. +[source,yaml] +---- +quarkus.solace.host=tcp://localhost:55555 +quarkus.solace.vpn=default +quarkus.solace.authentication.basic.username=test +quarkus.solace.authentication.basic.password=test + +mp.messaging.incoming.temperatures.connector=quarkus-solace +mp.messaging.incoming.temperatures.consumer.queue.name=temperatures +mp.messaging.incoming.temperatures.client.tracing-enabled=true + +mp.messaging.outgoing.temperatures-out.connector=quarkus-solace +mp.messaging.outgoing.temperatures-out.producer.topic=temperatures +mp.messaging.outgoing.temperatures-out.client.tracing-enabled=true +---- + +NOTE: Context Propagation is not fully supported in current version. + [[health-checks]] == Health Checks @@ -443,7 +507,7 @@ The liveness check captures any unrecoverable failure happening during the commu The readiness check verifies that the Quarkus Solace Messaging Connector is ready to consume/produce messages to the configured Solace queues/topics. [[dev-services]] -Dev Services +== Dev Services Solace Dev Services for Quarkus will spin up latest version of Solace PubSub standard with label `solace` when running tests or in dev mode. Solace Dev Services are enabled by default and will check for any existing containers with same label to reuse. If none is present a new container is started. diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 1cefd8a..f3c31da 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -12,5 +12,6 @@ pom solace-client-integration-tests + solace-client-oauth-integration-tests \ No newline at end of file diff --git a/integration-tests/solace-client-oauth-integration-tests/pom.xml b/integration-tests/solace-client-oauth-integration-tests/pom.xml new file mode 100644 index 0000000..d230e22 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + + com.solace.quarkus + quarkus-solace-integration-tests-parent + ${revision}${sha1}${changelist} + + + solace-client-oauth-integration-tests + Quarkus Solace Client - OAuth Integration Tests + + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + com.solace.quarkus + quarkus-solace-client + ${project.version} + + + io.quarkus + quarkus-smallrye-health + + + + io.quarkus + quarkus-junit5 + test + + + org.testcontainers + testcontainers + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + 3.24.2 + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-jar-plugin + + + + test-jar + + + + + + maven-surefire-plugin + + + + + + + + + native-image + + + native + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + org.jboss.logmanager.LogManager + + ${maven.home} + + + + + + + + + native + + + + + \ No newline at end of file diff --git a/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceConsumer.java b/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceConsumer.java new file mode 100644 index 0000000..28047a9 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceConsumer.java @@ -0,0 +1,60 @@ +package com.solace.quarkus; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; + +import com.solace.messaging.MessagingService; +import com.solace.messaging.config.MissingResourcesCreationConfiguration; +import com.solace.messaging.receiver.DirectMessageReceiver; +import com.solace.messaging.receiver.PersistentMessageReceiver; +import com.solace.messaging.resources.Queue; +import com.solace.messaging.resources.TopicSubscription; + +import io.quarkus.runtime.ShutdownEvent; + +@ApplicationScoped +public class SolaceConsumer { + + private final DirectMessageReceiver directReceiver; + private final PersistentMessageReceiver persistentReceiver; + List direct = new CopyOnWriteArrayList<>(); + List persistent = new CopyOnWriteArrayList<>(); + + public SolaceConsumer(MessagingService solace) { + directReceiver = solace.createDirectMessageReceiverBuilder() + .withSubscriptions(TopicSubscription.of("hello/direct")) + .build().start(); + persistentReceiver = solace.createPersistentMessageReceiverBuilder() + .withMissingResourcesCreationStrategy( + MissingResourcesCreationConfiguration.MissingResourcesCreationStrategy.CREATE_ON_START) + .withSubscriptions(TopicSubscription.of("hello/persistent")) + .build(Queue.durableExclusiveQueue("hello/persistent")).start(); + + directReceiver.receiveAsync(h -> consumeDirect(h.getPayloadAsString())); + persistentReceiver.receiveAsync(h -> consumePersistent(h.getPayloadAsString())); + } + + public void shutdown(@Observes ShutdownEvent event) { + directReceiver.terminate(1); + persistentReceiver.terminate(1); + } + + public void consumeDirect(String message) { + direct.add(message); + } + + public void consumePersistent(String message) { + persistent.add(message); + } + + public List direct() { + return direct; + } + + public List persistent() { + return persistent; + } +} \ No newline at end of file diff --git a/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceCustomizer.java b/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceCustomizer.java new file mode 100644 index 0000000..706952b --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceCustomizer.java @@ -0,0 +1,13 @@ +package com.solace.quarkus; + +import jakarta.enterprise.context.ApplicationScoped; + +import com.solace.messaging.MessagingServiceClientBuilder; + +@ApplicationScoped +public class SolaceCustomizer implements MessagingServiceClientCustomizer { + @Override + public MessagingServiceClientBuilder customize(MessagingServiceClientBuilder builder) { + return builder; + } +} diff --git a/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceResource.java b/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceResource.java new file mode 100644 index 0000000..6fa3b47 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceResource.java @@ -0,0 +1,61 @@ +package com.solace.quarkus; + +import java.util.List; + +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import com.solace.messaging.MessagingService; +import com.solace.messaging.publisher.DirectMessagePublisher; +import com.solace.messaging.publisher.PersistentMessagePublisher; +import com.solace.messaging.resources.Topic; + +import io.quarkus.runtime.StartupEvent; + +@Path("/solace") +public class SolaceResource { + + @Inject + SolaceConsumer consumer; + + @Inject + MessagingService solace; + private DirectMessagePublisher directMessagePublisher; + private PersistentMessagePublisher persistentMessagePublisher; + + public void init(@Observes StartupEvent ev) { + directMessagePublisher = solace.createDirectMessagePublisherBuilder().build().start(); + persistentMessagePublisher = solace.createPersistentMessagePublisherBuilder().build().start(); + } + + @GET + @Path("/direct") + @Produces("application/json") + public List getDirectMessages() { + return consumer.direct(); + } + + @GET + @Path("/persistent") + @Produces("application/json") + public List getPersistentMessages() { + return consumer.persistent(); + } + + @POST + @Path("/direct") + public void sendDirect(String message) { + directMessagePublisher.publish(message, Topic.of("hello/direct")); + } + + @POST + @Path("/persistent") + public void sendPersistent(String message) { + persistentMessagePublisher.publish(message, Topic.of("hello/persistent")); + } + +} diff --git a/integration-tests/solace-client-oauth-integration-tests/src/main/resources/application.properties b/integration-tests/solace-client-oauth-integration-tests/src/main/resources/application.properties new file mode 100644 index 0000000..bcf83e8 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.solace.devservices.enabled=false \ No newline at end of file diff --git a/integration-tests/solace-client-oauth-integration-tests/src/main/resources/keycloak.crt b/integration-tests/solace-client-oauth-integration-tests/src/main/resources/keycloak.crt new file mode 100644 index 0000000..1a6238c --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/main/resources/keycloak.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIJAIHrWDyBHPjWMA0GCSqGSIb3DQEBCwUAMIGJMQswCQYD +VQQGEwJDQTEMMAoGA1UECAwDTi9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoM +BlNvbGFjZTEgMB4GCSqGSIb3DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNV +BAsMCm15b3JnLXVuaXQxEzARBgNVBAMMCm15a2V5Y2xvYWswIBcNMjQwMzExMDky +NjIxWhgPMjEyNDAyMTYwOTI2MjFaMIGJMQswCQYDVQQGEwJDQTEMMAoGA1UECAwD +Ti9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoMBlNvbGFjZTEgMB4GCSqGSIb3 +DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNVBAsMCm15b3JnLXVuaXQxEzAR +BgNVBAMMCm15a2V5Y2xvYWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQC6zmPMXjha+0jor9iVEQhu2WUKKRKubTS3eAY72ez9IA+ml1HXh2rqVIPv8F5v +GasxUEbHzDgBrsuQyROwpXei69EuDWY6g3w7ifu6rkx+xAB9ipv9DjUXc3sH37nE +2xK5HnB7CO8O/HoSBbxXw0sJ7SYyDaYujn17fOJyr4Rljqfkj20ok7HzmUH/Wph1 +9WZ9ShfkaRw0SVIBeNc+3i/KorERgLfnBydyd9fDsrnIjSJxMWOA2PMDsA8lzvEk +lObn5dlLGIN5nMuNBpefnLQ0y5aA5Ikpzn1I3bwzPqQJt4v2fXem2NPSuMOOa042 +oJFZQD9GJRsmC5Kf7o7oKejjAgMBAAGjLjAsMCoGA1UdEQQjMCGCCm15a2V5Y2xv +YWuCCWxvY2FsaG9zdIIIa2V5Y2xvYWswDQYJKoZIhvcNAQELBQADggEBALGDHoxV +KGxZ/O81fOPPg8YNrkPkAvZu4kEEKoS/0bW+npzCRDZu9nULxwRDZJdfU7mNqjHx +RbLMk/rwI2F1aBnBXHMV22wqW/3d+0B6JqnJFz+9Mb0453f8+Dn6ZpxgMYPh2rn/ +80wW4g8wuofcdcHxqTd/fNbzWN8kRVsjqIysXjD4w2zb9q/yFwRq0+WeOhc6FJiz +PnLINignHS/zqslCPD53T+Crqcx8vLrLSoMucCXCkgJeX/joLYmjhdgG3ewIOz/C ++BWWOT73+mfe8e87rEX4e3hpJ+0ZBkP25u+6Q02OYU9A1PtBLDyZqeEH/WiIrJNO +UaS8kTKbscA5voc= +-----END CERTIFICATE----- diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeyCloakContainer.java b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeyCloakContainer.java new file mode 100644 index 0000000..054470d --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeyCloakContainer.java @@ -0,0 +1,102 @@ +package com.solace.quarkus.oauth; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.MountableFile; + +public class KeyCloakContainer extends GenericContainer { + + public KeyCloakContainer() { + super("quay.io/keycloak/keycloak:20.0.0"); + addFixedExposedPort(7777, Service.HTTP.getPort()); + addFixedExposedPort(7778, Service.HTTPS.getPort()); + withEnv("KEYCLOAK_ADMIN", "admin"); + withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin"); + withEnv("KEYCLOAK_FRONTEND_URL", "https://localhost:7778"); + withEnv("KC_HOSTNAME_URL", "https://localhost:7778"); + withEnv("KC_HTTPS_CERTIFICATE_FILE", "/opt/keycloak/conf/server.crt"); + withEnv("KC_HTTPS_CERTIFICATE_KEY_FILE", "/opt/keycloak/conf/server.key"); + waitingFor(Wait.forLogMessage(".*Listening.*", 1)); + withNetwork(Network.SHARED); + withNetworkAliases("keycloak"); + withCopyFileToContainer(MountableFile.forClasspathResource("keycloak/realms/solace-realm.json"), + "/opt/keycloak/data/import/solace-realm.json"); + withCopyFileToContainer(MountableFile.forClasspathResource("keycloak.crt"), "/opt/keycloak/conf/server.crt"); + withCopyFileToContainer(MountableFile.forClasspathResource("keycloak.key"), "/opt/keycloak/conf/server.key"); + withCommand("start-dev", "--import-realm"); + } + + public void createHostsFile() { + try (FileWriter fileWriter = new FileWriter("target/hosts")) { + String dockerHost = this.getHost(); + if ("localhost".equals(dockerHost)) { + fileWriter.write("127.0.0.1 keycloak"); + } else { + fileWriter.write(dockerHost + " keycloak"); + } + fileWriter.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Host address for provided service + * + * @param service - service for which host needs to be retrieved + * @return host address exposed from the container + */ + public String getOrigin(Service service) { + return String.format("%s://%s:%s", service.getProtocol(), getHost(), getMappedPort(service.getPort())); + } + + public enum Service { + HTTP("http", 8080, "http", false), + HTTPS("https", 8443, "https", true); + + private final String name; + private final Integer port; + private final String protocol; + private final boolean supportSSL; + + Service(String name, Integer port, String protocol, boolean supportSSL) { + this.name = name; + this.port = port; + this.protocol = protocol; + this.supportSSL = supportSSL; + } + + /** + * @return Port assigned for the service + */ + public Integer getPort() { + return this.port; + } + + /** + * @return Protocol of the service + */ + public String getProtocol() { + return this.protocol; + } + + /** + * @return Name of the service + */ + public String getName() { + return this.name; + } + + /** + * @return Is SSL for this service supported ? + */ + public boolean isSupportSSL() { + return this.supportSSL; + } + } +} diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeycloakResource.java b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeycloakResource.java new file mode 100644 index 0000000..04259b3 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeycloakResource.java @@ -0,0 +1,97 @@ +package com.solace.quarkus.oauth; + +import static org.awaitility.Awaitility.await; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Map; + +import org.testcontainers.shaded.org.awaitility.Awaitility; +import org.testcontainers.utility.MountableFile; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class KeycloakResource implements QuarkusTestResourceLifecycleManager { + KeyCloakContainer keyCloakContainer; + private SolaceContainer solaceContainer; + + @Override + public Map start() { + writeJks(); + + keyCloakContainer = new KeyCloakContainer(); + keyCloakContainer.start(); + keyCloakContainer.createHostsFile(); + await().until(() -> keyCloakContainer.isRunning()); + solaceContainer = new SolaceContainer("solace/solace-pubsub-standard:latest"); + solaceContainer.withCredentials("user", "pass") + .withClientCert(MountableFile.forClasspathResource("solace.pem"), + MountableFile.forClasspathResource("keycloak.crt"), false) + .withOAuth() + .withExposedPorts(SolaceContainer.Service.SMF.getPort(), SolaceContainer.Service.SMF_SSL.getPort(), 1943, 8080) + .withPublishTopic("hello/direct", SolaceContainer.Service.SMF) + .withPublishTopic("hello/persistent", SolaceContainer.Service.SMF); + + solaceContainer.start(); + Awaitility.await().until(() -> solaceContainer.isRunning()); + + return Map.ofEntries( + Map.entry("quarkus.oidc-client.solace.auth-server-url", + keyCloakContainer.getOrigin(KeyCloakContainer.Service.HTTPS) + "/realms/solace"), + Map.entry("quarkus.oidc-client.solace.tls.trust-store-file", "target/keycloak.jks"), + Map.entry("quarkus.oidc-client.solace.tls.key-store-password", "password"), + Map.entry("quarkus.oidc-client.solace.client-id", "solace"), + Map.entry("quarkus.oidc-client.solace.credentials.secret", "solace-secret"), + Map.entry("quarkus.oidc-client.solace.tls.verification", "none"), + // Map.entry("quarkus.oidc-client.solace.refresh-token-time-skew", "5s"), + Map.entry("quarkus.solace.host", solaceContainer.getOrigin(SolaceContainer.Service.SMF_SSL)), + Map.entry("quarkus.solace.vpn", solaceContainer.getVpn()), + Map.entry("quarkus.solace.oidc.refresh.interval", "5s"), + Map.entry("quarkus.solace.oidc.client-name", "solace"), + Map.entry("quarkus.solace.authentication.scheme", "AUTHENTICATION_SCHEME_OAUTH2"), + Map.entry("quarkus.solace.tls.cert-validated", "false"), + Map.entry("quarkus.solace.tls.cert-validate-servername", "false")); + } + + private static void writeJks() { + try (var fis = new FileInputStream(KeycloakResource.class.getResource("/keycloak.crt").getFile()); + var fos = new FileOutputStream("target/keycloak.jks")) { + createKeyStore(fis.readAllBytes(), null).store(fos, "password".toCharArray()); + } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static KeyStore createKeyStore(byte[] ca, byte[] serviceCa) { + try { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + if (ca != null) { + keyStore.setCertificateEntry("keycloak", + cf.generateCertificate(new ByteArrayInputStream(ca))); + } + if (serviceCa != null) { + keyStore.setCertificateEntry("service-ca", + cf.generateCertificate(new ByteArrayInputStream(serviceCa))); + } + return keyStore; + } catch (Exception ignored) { + return null; + } + } + + @Override + public void stop() { + keyCloakContainer.stop(); + solaceContainer.stop(); + } + +} \ No newline at end of file diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceContainer.java b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceContainer.java new file mode 100644 index 0000000..26d810e --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceContainer.java @@ -0,0 +1,540 @@ +package com.solace.quarkus.oauth; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Pair; +import org.testcontainers.shaded.org.awaitility.Awaitility; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +import com.github.dockerjava.api.command.InspectContainerResponse; + +public class SolaceContainer extends GenericContainer { + + public static final String INTEGRATION_TEST_QUEUE_NAME = "integration-test-queue"; + public static final String INTEGRATION_TEST_QUEUE_SUBSCRIPTION = "quarkus/integration/test/provisioned/queue/topic"; + public static final String INTEGRATION_TEST_DMQ_NAME = "integration-test-queue-dmq"; + public static final String INTEGRATION_TEST_ERROR_QUEUE_NAME = "integration-test-error-queue"; + public static final String INTEGRATION_TEST_ERROR_QUEUE_SUBSCRIPTION = "quarkus/integration/test/provisioned/queue/error/topic"; + + public static final String INTEGRATION_TEST_PARTITION_QUEUE_NAME = "integration-test-partition-queue"; + public static final String INTEGRATION_TEST_PARTITION_QUEUE_SUBSCRIPTION = "quarkus/integration/test/provisioned/partition/queue/topic"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("solace/solace-pubsub-standard"); + + private static final String DEFAULT_VPN = "default"; + + private static final String DEFAULT_USERNAME = "default"; + + private static final String SOLACE_READY_MESSAGE = ".*Running pre-startup checks:.*"; + + private static final String SOLACE_ACTIVE_MESSAGE = "Primary Virtual Router is now active"; + + private static final String TMP_SCRIPT_LOCATION = "/tmp/script.cli"; + + private static final Long SHM_SIZE = (long) Math.pow(1024, 3); + + private String username = "root"; + + private String password = "password"; + + private String vpn = DEFAULT_VPN; + + private final List> publishTopicsConfiguration = new ArrayList<>(); + private final List> subscribeTopicsConfiguration = new ArrayList<>(); + + private boolean withClientCert; + private boolean withOAuth; + private boolean clientCertificateAuthority; + + /** + * Create a new solace container with the specified image name. + * + * @param dockerImageName the image name that should be used. + */ + public SolaceContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public SolaceContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withCreateContainerCmdModifier(cmd -> { + cmd.withUser("1000"); + cmd.getHostConfig() + .withShmSize(SHM_SIZE) + .withMemorySwap(-1L) + .withMemoryReservation(0L); + }); + this.waitStrategy = Wait.forLogMessage(SOLACE_READY_MESSAGE, 1).withStartupTimeout(Duration.ofSeconds(60)); + withExposedPorts(8080); + withEnv("system_scaling_maxconnectioncount", "100"); + withEnv("logging_system_output", "all"); + withEnv("username_admin_globalaccesslevel", "admin"); + withEnv("username_admin_password", "admin"); + withNetwork(Network.SHARED); + withNetworkAliases("solace"); + } + + @Override + protected void configure() { + withCopyToContainer(createConfigurationScript(), TMP_SCRIPT_LOCATION); + } + + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + executeCommand("chown 1000:0 -R /var/lib/solace"); + if (withClientCert) { + executeCommand("cp", "/tmp/solace.pem", "/usr/sw/jail/certs/solace.pem"); + executeCommand("cp", "/tmp/rootCA.crt", "/usr/sw/jail/certs/rootCA.crt"); + } + executeCommand("cp", TMP_SCRIPT_LOCATION, "/usr/sw/jail/cliscripts/script.cli"); + waitOnCommandResult(SOLACE_ACTIVE_MESSAGE, "grep", "-R", SOLACE_ACTIVE_MESSAGE, "/usr/sw/jail/logs/system.log"); + executeCommand("/usr/sw/loads/currentload/bin/cli", "-A", "-es", "script.cli"); + } + + private Transferable createConfigurationScript() { + StringBuilder scriptBuilder = new StringBuilder(); + updateConfigScript(scriptBuilder, "enable"); + updateConfigScript(scriptBuilder, "configure"); + + // telemetry configuration + updateConfigScript(scriptBuilder, "message-vpn default"); + updateConfigScript(scriptBuilder, "create telemetry-profile trace"); + updateConfigScript(scriptBuilder, "trace"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "create filter default"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "create subscription \">\""); + updateConfigScript(scriptBuilder, "end"); + + updateConfigScript(scriptBuilder, "configure"); + // create replay log + updateConfigScript(scriptBuilder, "message-spool message-vpn default"); + updateConfigScript(scriptBuilder, "create replay-log integration-test-replay-log"); + updateConfigScript(scriptBuilder, "max-spool-usage 10"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "exit"); + + // create Error queue, DMQ and a queue. Assign DMQ to queue + + // Error Queue + updateConfigScript(scriptBuilder, "message-spool message-vpn default"); + updateConfigScript(scriptBuilder, "create queue " + INTEGRATION_TEST_ERROR_QUEUE_NAME); + updateConfigScript(scriptBuilder, "access-type exclusive"); + updateConfigScript(scriptBuilder, "respect-ttl"); + updateConfigScript(scriptBuilder, "max-spool-usage 300"); + updateConfigScript(scriptBuilder, "subscription topic " + INTEGRATION_TEST_ERROR_QUEUE_SUBSCRIPTION); + updateConfigScript(scriptBuilder, "permission all consume"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "exit"); + + // DMQ + updateConfigScript(scriptBuilder, "message-spool message-vpn default"); + updateConfigScript(scriptBuilder, "create queue " + INTEGRATION_TEST_DMQ_NAME); + updateConfigScript(scriptBuilder, "access-type exclusive"); + updateConfigScript(scriptBuilder, "max-spool-usage 300"); + updateConfigScript(scriptBuilder, "permission all consume"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "exit"); + + // Queue with DMQ assigned + updateConfigScript(scriptBuilder, "message-spool message-vpn default"); + updateConfigScript(scriptBuilder, "create queue " + INTEGRATION_TEST_QUEUE_NAME); + updateConfigScript(scriptBuilder, "access-type exclusive"); + updateConfigScript(scriptBuilder, "subscription topic " + INTEGRATION_TEST_QUEUE_SUBSCRIPTION); + updateConfigScript(scriptBuilder, "max-spool-usage 300"); + updateConfigScript(scriptBuilder, "permission all consume"); + updateConfigScript(scriptBuilder, "dead-message-queue " + INTEGRATION_TEST_DMQ_NAME); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "exit"); + + // Partitioned Queue + updateConfigScript(scriptBuilder, "message-spool message-vpn default"); + updateConfigScript(scriptBuilder, "create queue " + INTEGRATION_TEST_PARTITION_QUEUE_NAME); + updateConfigScript(scriptBuilder, "access-type non-exclusive"); + updateConfigScript(scriptBuilder, "subscription topic " + INTEGRATION_TEST_PARTITION_QUEUE_SUBSCRIPTION); + updateConfigScript(scriptBuilder, "max-spool-usage 300"); + updateConfigScript(scriptBuilder, "permission all consume"); + updateConfigScript(scriptBuilder, "partition"); + updateConfigScript(scriptBuilder, "count 4"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "exit"); + + // Create VPN if not default + if (!vpn.equals(DEFAULT_VPN)) { + updateConfigScript(scriptBuilder, "create message-vpn " + vpn); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "exit"); + } + + // Configure username and password + if (username.equals(DEFAULT_USERNAME)) { + throw new RuntimeException("Cannot override password for default client"); + } + updateConfigScript(scriptBuilder, "create client-username " + username + " message-vpn " + vpn); + updateConfigScript(scriptBuilder, "password " + password); + updateConfigScript(scriptBuilder, "acl-profile default"); + updateConfigScript(scriptBuilder, "client-profile default"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "exit"); + + if (withClientCert) { + if (clientCertificateAuthority) { + updateConfigScript(scriptBuilder, "configure"); + // Client certificate authority configuration + updateConfigScript(scriptBuilder, "authentication"); + updateConfigScript(scriptBuilder, "create client-certificate-authority RootCA"); + updateConfigScript(scriptBuilder, "certificate file rootCA.crt"); + updateConfigScript(scriptBuilder, "show client-certificate-authority ca-name *"); + updateConfigScript(scriptBuilder, "end"); + + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + // Enable client certificate authentication + updateConfigScript(scriptBuilder, "authentication client-certificate"); + updateConfigScript(scriptBuilder, "allow-api-provided-username"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + } else { + updateConfigScript(scriptBuilder, "configure"); + // Domain certificate authority configuration + updateConfigScript(scriptBuilder, "ssl"); + updateConfigScript(scriptBuilder, "create domain-certificate-authority RootCA"); + updateConfigScript(scriptBuilder, "certificate file rootCA.crt"); + updateConfigScript(scriptBuilder, "show domain-certificate-authority ca-name *"); + updateConfigScript(scriptBuilder, "end"); + } + + // Server certificates configuration + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "ssl"); + updateConfigScript(scriptBuilder, "server-certificate solace.pem"); + updateConfigScript(scriptBuilder, "cipher-suite msg-backbone name AES128-SHA"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "end"); + + } + + if (withOAuth) { + // Configure OAuth authentication + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + updateConfigScript(scriptBuilder, "authentication oauth"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + } else { + // Configure VPN Basic authentication + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + updateConfigScript(scriptBuilder, "authentication basic auth-type internal"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + } + + // create OAuth profile + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + updateConfigScript(scriptBuilder, "authentication"); + updateConfigScript(scriptBuilder, "create oauth profile integration_test_oauth_profile"); + updateConfigScript(scriptBuilder, "authorization-groups-claim-name \"\" "); + updateConfigScript(scriptBuilder, "oauth-role resource-server"); + updateConfigScript(scriptBuilder, "issuer https://localhost:7778/realms/solace"); + updateConfigScript(scriptBuilder, "disconnect-on-token-expiration"); + updateConfigScript(scriptBuilder, "endpoints"); + updateConfigScript(scriptBuilder, "discovery https://keycloak:8443/realms/solace/.well-known/openid-configuration"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "username-claim-name sub"); + updateConfigScript(scriptBuilder, "resource-server"); + updateConfigScript(scriptBuilder, "required-audience pubsub+aud"); + updateConfigScript(scriptBuilder, "no validate-type"); + updateConfigScript(scriptBuilder, "required-type JWT"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "client-id solace"); + updateConfigScript(scriptBuilder, "client-secret solace-secret"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + + if (!publishTopicsConfiguration.isEmpty() || !subscribeTopicsConfiguration.isEmpty()) { + // Enable services + updateConfigScript(scriptBuilder, "configure"); + // Configure default ACL + updateConfigScript(scriptBuilder, "acl-profile default message-vpn " + vpn); + // Configure default action to disallow + if (!subscribeTopicsConfiguration.isEmpty()) { + updateConfigScript(scriptBuilder, "subscribe-topic default-action disallow"); + } + if (!publishTopicsConfiguration.isEmpty()) { + updateConfigScript(scriptBuilder, "publish-topic default-action disallow"); + } + updateConfigScript(scriptBuilder, "exit"); + + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + updateConfigScript(scriptBuilder, "service"); + for (Pair topicConfig : publishTopicsConfiguration) { + Service service = topicConfig.getValue(); + String topicName = topicConfig.getKey(); + updateConfigScript(scriptBuilder, service.getName()); + if (service.isSupportSSL()) { + if (withClientCert) { + updateConfigScript(scriptBuilder, "ssl"); + } else { + updateConfigScript(scriptBuilder, "plain-text"); + } + } + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + // Add publish/subscribe topic exceptions + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "acl-profile default message-vpn " + vpn); + updateConfigScript( + scriptBuilder, + String.format("publish-topic exceptions %s list %s", service.getName(), topicName)); + updateConfigScript(scriptBuilder, "end"); + } + + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + updateConfigScript(scriptBuilder, "service"); + for (Pair topicConfig : subscribeTopicsConfiguration) { + Service service = topicConfig.getValue(); + String topicName = topicConfig.getKey(); + updateConfigScript(scriptBuilder, service.getName()); + if (service.isSupportSSL()) { + if (withClientCert) { + updateConfigScript(scriptBuilder, "ssl"); + } else { + updateConfigScript(scriptBuilder, "plain-text"); + } + } + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + // Add publish/subscribe topic exceptions + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "acl-profile default message-vpn " + vpn); + updateConfigScript( + scriptBuilder, + String.format("subscribe-topic exceptions %s list %s", service.getName(), topicName)); + updateConfigScript(scriptBuilder, "end"); + } + } + return Transferable.of(scriptBuilder.toString()); + } + + private void executeCommand(String... command) { + try { + ExecResult execResult = execInContainer(command); + if (execResult.getExitCode() != 0) { + logCommandError(execResult.getStderr(), command); + } + } catch (IOException | InterruptedException e) { + logCommandError(e.getMessage(), command); + } + } + + private void updateConfigScript(StringBuilder scriptBuilder, String command) { + scriptBuilder.append(command).append("\n"); + } + + private void waitOnCommandResult(String waitingFor, String... command) { + Awaitility + .await() + .pollInterval(Duration.ofMillis(500)) + .timeout(Duration.ofSeconds(30)) + .until(() -> { + try { + return execInContainer(command).getStdout().contains(waitingFor); + } catch (IOException | InterruptedException e) { + logCommandError(e.getMessage(), command); + return true; + } + }); + } + + private void logCommandError(String error, String... command) { + logger().error("Could not execute command {}: {}", command, error); + } + + /** + * Sets the client credentials + * + * @param username Client username + * @param password Client password + * @return This container. + */ + public SolaceContainer withCredentials(final String username, final String password) { + this.username = username; + this.password = password; + return this; + } + + /** + * Adds the topic configuration + * + * @param topic Name of the topic + * @param service Service to be supported on provided topic + * @return This container. + */ + // public SolaceContainer withTopic(String topic, Service service) { + // topicsConfiguration.add(Pair.of(topic, service)); + // addExposedPort(service.getPort()); + // return this; + // } + + /** + * Adds the publish topic exceptions configuration + * + * @param topic Name of the topic + * @param service Service to be supported on provided topic + * @return This container. + */ + public SolaceContainer withPublishTopic(String topic, Service service) { + publishTopicsConfiguration.add(Pair.of(topic, service)); + addExposedPort(service.getPort()); + return this; + } + + /** + * Adds the subscribe topic exceptions configuration + * + * @param topic Name of the topic + * @param service Service to be supported on provided topic + * @return This container. + */ + public SolaceContainer withSubscribeTopic(String topic, Service service) { + subscribeTopicsConfiguration.add(Pair.of(topic, service)); + addExposedPort(service.getPort()); + return this; + } + + /** + * Sets the VPN name + * + * @param vpn VPN name + * @return This container. + */ + public SolaceContainer withVpn(String vpn) { + this.vpn = vpn; + return this; + } + + /** + * Sets the solace server ceritificates + * + * @param certFile Server certificate + * @param caFile Certified Authority ceritificate + * @return This container. + */ + public SolaceContainer withClientCert(final MountableFile certFile, final MountableFile caFile, + boolean clientCertificateAuthority) { + this.withClientCert = true; + this.clientCertificateAuthority = clientCertificateAuthority; + return withCopyFileToContainer(certFile, "/tmp/solace.pem").withCopyFileToContainer(caFile, "/tmp/rootCA.crt"); + } + + /** + * Sets OAuth authentication + */ + public SolaceContainer withOAuth() { + this.withOAuth = true; + return this; + } + + /** + * Configured VPN + * + * @return the configured VPN that should be used for connections + */ + public String getVpn() { + return this.vpn; + } + + /** + * Host address for provided service + * + * @param service - service for which host needs to be retrieved + * @return host address exposed from the container + */ + public String getOrigin(Service service) { + return String.format("%s://%s:%s", service.getProtocol(), getHost(), getMappedPort(service.getPort())); + } + + /** + * Configured username + * + * @return the standard username that should be used for connections + */ + public String getUsername() { + return this.username; + } + + /** + * Configured password + * + * @return the standard password that should be used for connections + */ + public String getPassword() { + return this.password; + } + + public enum Service { + AMQP("amqp", 5672, "amqp", false), + MQTT("mqtt", 1883, "tcp", false), + REST("rest", 9000, "http", false), + SMF("smf", 55555, "tcp", true), + SMF_SSL("smf", 55443, "tcps", true); + + private final String name; + private final Integer port; + private final String protocol; + private final boolean supportSSL; + + Service(String name, Integer port, String protocol, boolean supportSSL) { + this.name = name; + this.port = port; + this.protocol = protocol; + this.supportSSL = supportSSL; + } + + /** + * @return Port assigned for the service + */ + public Integer getPort() { + return this.port; + } + + /** + * @return Protocol of the service + */ + public String getProtocol() { + return this.protocol; + } + + /** + * @return Name of the service + */ + public String getName() { + return this.name; + } + + /** + * @return Is SSL for this service supported ? + */ + public boolean isSupportSSL() { + return this.supportSSL; + } + } +} diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthIT.java b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthIT.java new file mode 100644 index 0000000..edd5720 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthIT.java @@ -0,0 +1,8 @@ +package com.solace.quarkus.oauth; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class SolaceOAuthIT extends SolaceOAuthTest { + +} \ No newline at end of file diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthTest.java b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthTest.java new file mode 100644 index 0000000..5105a57 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthTest.java @@ -0,0 +1,62 @@ +package com.solace.quarkus.oauth; + +import static org.awaitility.Awaitility.await; + +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; + +@QuarkusTest +@QuarkusTestResource(value = KeycloakResource.class, restrictToAnnotatedClass = true) +class SolaceOAuthTest { + private final TypeRef> listOfString = new TypeRef<>() { + // Empty + }; + + @Test + void testDirect() throws InterruptedException { + List list = RestAssured + .given().header("Accept", "application/json") + .get("/solace/direct").as(listOfString); + Assertions.assertThat(list).isEmpty(); + + for (int i = 0; i < 3; i++) { + RestAssured + .given().body("hello " + i) + .post("/solace/direct") + .then().statusCode(204); + Thread.sleep(5000); + } + + await().until(() -> RestAssured + .given().header("Accept", "application/json") + .get("/solace/direct").as(listOfString).size() == 3); + } + + @Test + void testPersistent() throws InterruptedException { + List list = RestAssured + .given().header("Accept", "application/json") + .get("/solace/persistent").as(listOfString); + Assertions.assertThat(list).isEmpty(); + + for (int i = 0; i < 3; i++) { + RestAssured + .given().body("hello " + i) + .post("/solace/persistent") + .then().statusCode(204); + Thread.sleep(5000); + } + + await().until(() -> RestAssured + .given().header("Accept", "application/json") + .get("/solace/persistent").as(listOfString).size() == 3); + } + +} diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.crt b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.crt new file mode 100644 index 0000000..1a6238c --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIJAIHrWDyBHPjWMA0GCSqGSIb3DQEBCwUAMIGJMQswCQYD +VQQGEwJDQTEMMAoGA1UECAwDTi9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoM +BlNvbGFjZTEgMB4GCSqGSIb3DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNV +BAsMCm15b3JnLXVuaXQxEzARBgNVBAMMCm15a2V5Y2xvYWswIBcNMjQwMzExMDky +NjIxWhgPMjEyNDAyMTYwOTI2MjFaMIGJMQswCQYDVQQGEwJDQTEMMAoGA1UECAwD +Ti9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoMBlNvbGFjZTEgMB4GCSqGSIb3 +DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNVBAsMCm15b3JnLXVuaXQxEzAR +BgNVBAMMCm15a2V5Y2xvYWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQC6zmPMXjha+0jor9iVEQhu2WUKKRKubTS3eAY72ez9IA+ml1HXh2rqVIPv8F5v +GasxUEbHzDgBrsuQyROwpXei69EuDWY6g3w7ifu6rkx+xAB9ipv9DjUXc3sH37nE +2xK5HnB7CO8O/HoSBbxXw0sJ7SYyDaYujn17fOJyr4Rljqfkj20ok7HzmUH/Wph1 +9WZ9ShfkaRw0SVIBeNc+3i/KorERgLfnBydyd9fDsrnIjSJxMWOA2PMDsA8lzvEk +lObn5dlLGIN5nMuNBpefnLQ0y5aA5Ikpzn1I3bwzPqQJt4v2fXem2NPSuMOOa042 +oJFZQD9GJRsmC5Kf7o7oKejjAgMBAAGjLjAsMCoGA1UdEQQjMCGCCm15a2V5Y2xv +YWuCCWxvY2FsaG9zdIIIa2V5Y2xvYWswDQYJKoZIhvcNAQELBQADggEBALGDHoxV +KGxZ/O81fOPPg8YNrkPkAvZu4kEEKoS/0bW+npzCRDZu9nULxwRDZJdfU7mNqjHx +RbLMk/rwI2F1aBnBXHMV22wqW/3d+0B6JqnJFz+9Mb0453f8+Dn6ZpxgMYPh2rn/ +80wW4g8wuofcdcHxqTd/fNbzWN8kRVsjqIysXjD4w2zb9q/yFwRq0+WeOhc6FJiz +PnLINignHS/zqslCPD53T+Crqcx8vLrLSoMucCXCkgJeX/joLYmjhdgG3ewIOz/C ++BWWOT73+mfe8e87rEX4e3hpJ+0ZBkP25u+6Q02OYU9A1PtBLDyZqeEH/WiIrJNO +UaS8kTKbscA5voc= +-----END CERTIFICATE----- diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.key b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.key new file mode 100644 index 0000000..762b81c --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC6zmPMXjha+0jo +r9iVEQhu2WUKKRKubTS3eAY72ez9IA+ml1HXh2rqVIPv8F5vGasxUEbHzDgBrsuQ +yROwpXei69EuDWY6g3w7ifu6rkx+xAB9ipv9DjUXc3sH37nE2xK5HnB7CO8O/HoS +BbxXw0sJ7SYyDaYujn17fOJyr4Rljqfkj20ok7HzmUH/Wph19WZ9ShfkaRw0SVIB +eNc+3i/KorERgLfnBydyd9fDsrnIjSJxMWOA2PMDsA8lzvEklObn5dlLGIN5nMuN +BpefnLQ0y5aA5Ikpzn1I3bwzPqQJt4v2fXem2NPSuMOOa042oJFZQD9GJRsmC5Kf +7o7oKejjAgMBAAECggEBAKvWAI1UumhOsFGCuCrfQS+egEgpYgrbX9vI54sUuuBZ +Jqxqk5k78whc+AS1ylhOd2BkZMeTPo2luZGUta0PeI6Ad6nyH3CB1Lx7//hILwuI +xp606yqLcCEDVE/458yCbKWmr3ctz6Gsc6myZv8gIR2fbTwrvAslfZ4jUbaHZ7V2 +QNcRrcqXrHJjlEZ6VcrFfKwr3Wp3mMvb3ogpZOlgAKxGtSvXNDahq4pUMMn/wD3k +iPSewYz2K1UR4Hz5DV7+tnNk2lXn/gEBpmW3Z5xk5kNpNhtE9ZFOnkFwSGGGpWGy +tlm3Cg+Mc/JGnQ06ZiNth9WUpjc3yMUTRb45ZwL+RKkCgYEA8n+KCbKfoocGnkcU +0+qHiEBFnAN0lWD/66otS07QsZoldxadOAQnZUxiR8Xxnm4ynUCYZvqRoBJisGV8 +30N6TZP/92qrNLq5A2EpAq/VbnL9nVNzxRP+nPRDikdcl5C/4XA0QZoIfIW535Iy +OdNJEAVWucQwhARyYbnpslFu+v8CgYEAxTUKvEZqhkqwnfHAIR8+h9HB2phsy3fP +ZFyr5jky7x+wO8YjGV42yiLtDOauSiNamxjLADy2qEmjDstsQatPFoXAI//386q1 +KG3qmI0fJHaUhBzAYUSjKIZgoVmr+n3Mr+cuTVKV+IhqHPpnT0d5NWD0k3U7oUoZ +7suiiHbKhh0CgYEAqXP+HbC4ZHY+ZbP+Fee5NbjT66VufkP+EcwlQo6cvr6cl48x +5cbhUKQDuWvU34TZ0ZEl7jACOv0eAW2pyMn6WOOm5lmfsYUZbAclBT+hwUCRgLKk +H39NWJhH6gTb6v23V+10VrMwYvN/Y39hoY7Ha26Pn9g8nsQMucWUTIsjJjkCgYAw +6zxzgcAw+dwgAfUYAkkfpe/BiugJ/PlsOvTFUlEJMkIkQb05ML7Em69T8PExIN37 +9UV+FJF243VYWSvMinM+8gS8qWVXg3QWyFVWbENaZzPmJb+vITib9+GGhNj9dTFO +PTmmIqNjGGvCLndsGh2+GQPyhDU7iEcwjkEOOvF4HQKBgQDNcnw0m8kZ+olzrFJ8 +QZXnxPFAU2q2yu23UbXtWuZ9Ld/5ZE4iEd1Pu8ZDsYonZ+hKmtmo3U4f8cFg/pYM +IxX2uNSiioqKMfLhIorkDoHm0+iZZRHGooQbv06R2qe7rPIIoFbSgU3M+CKgxkPN +VGIsOTBuOmJTWCy0hcPW/8n93w== +-----END PRIVATE KEY----- diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak/realms/solace-realm.json b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak/realms/solace-realm.json new file mode 100644 index 0000000..e7551c4 --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak/realms/solace-realm.json @@ -0,0 +1,2266 @@ +{ + "id": "solace", + "realm": "solace", + "displayName": "Keycloak", + "displayNameHtml": "
Keycloak
", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 86400, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 600, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "6af43dbd-b06e-47aa-967c-d7b0c98849e9", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "6d4367b1-0d06-4670-ae11-a6ec3c629f54", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "ee59e325-8dfb-426f-a233-1992768a559d", + "name": "default-roles-master", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "981a4159-7622-437d-9602-9c52d1549fa3", + "name": "admin", + "description": "${role_admin}", + "composite": true, + "composites": { + "realm": [ + "create-realm" + ], + "client": { + "master-realm": [ + "impersonation", + "manage-clients", + "view-users", + "query-groups", + "query-users", + "manage-realm", + "view-authorization", + "view-events", + "manage-users", + "query-clients", + "manage-authorization", + "create-client", + "query-realms", + "view-realm", + "view-clients", + "view-identity-providers", + "manage-identity-providers", + "manage-events" + ] + } + }, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "ba195745-8e0c-481b-bd20-cb99c07a3fe1", + "name": "create-realm", + "description": "${role_create-realm}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + } + ], + "client": { + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "22ab11ee-f680-4809-a49d-ad319c8f5ce1", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "5f9b7d7d-f097-4dd5-83b3-3bf602532df6", + "attributes": {} + } + ], + "master-realm": [ + { + "id": "1ec2babd-b6d7-4247-be98-9f3df6c35ab8", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "e602b75a-fe5b-4250-b516-4933ac013967", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "55a51d1e-c22d-412f-bbf9-eda59fb4aaa7", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "master-realm": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "2854dbc7-b758-4178-89d2-856863cf78d2", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "ab3321f5-cf52-4058-b1d7-3be25e0348f0", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "c055a88d-cddc-49e7-ac69-84d561df56d8", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "d8f301c6-7960-4f22-a10a-cc25a7295f6f", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "98236c38-bfd1-44de-b6cf-29a6496a4f64", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "4a659aeb-76f0-48bb-829d-d3295b0d9ca8", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "aee3ef48-e571-4719-91e7-11b5353a31a1", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "4c52b6f8-bb47-47f3-b836-34c8409886a2", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "287fbb84-c5dc-4262-b01d-0c1cfe12f6bf", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "aba6fe78-f504-46e7-94c0-cd096f6ab4bb", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "f2c0273b-6e38-480d-92e5-76a42e9e9071", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "ed1395c4-5c99-4b85-b985-301a65230c04", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "master-realm": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "c59fb7e1-7bfe-49ca-a953-6a437718d1e2", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "60d63bd9-f508-4eca-a2d1-9ab3542c07b8", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "22d12dc8-90a5-4180-b660-438760307405", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + } + ], + "account": [ + { + "id": "57444ccb-f476-4619-8527-336f3aee62f9", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "7394f404-2a1a-4670-b7c3-ad3e7b5cf09f", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "ec843402-f7d0-4785-bc3f-d73abe51b0e9", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "0386d08c-374b-400c-ac03-6d06b683af48", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "1596de1b-b78e-49a3-86b3-f2b7bcbb9668", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "85fee210-cfa2-477f-938a-cffc34982adb", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "bf342f28-b040-46ce-94a3-f76cc72df837", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + } + ], + "solace": [] + } + }, + "groups": [], + "defaultRole": { + "id": "ee59e325-8dfb-426f-a233-1992768a559d", + "name": "default-roles-master", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "master" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "users": [ + { + "id": "6c67c64e-0c0a-480e-a9d5-a93dd8b60151", + "createdTimestamp": 1709310343758, + "username": "service-account-solace", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "solace", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-master" + ], + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/master/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/master/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a85bf90b-bf07-4b48-ab42-96e5ff0a0186", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/master/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/master/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "58780fe6-7bc3-439c-b8fc-ca8c5269336b", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "00ee6480-fb8e-4e37-a8f2-10befdaf9b43", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "5f9b7d7d-f097-4dd5-83b3-3bf602532df6", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "clientId": "master-realm", + "name": "master Realm", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "edfcd34d-a719-44c0-af83-71cce3f9924c", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/master/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/master/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "0bdd7724-8687-4144-bba9-e88fc204d306", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "3c699672-dde8-41c6-838d-e3dde45325c9", + "clientId": "solace", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "solace-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "id.token.as.detached.signature": "false", + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "exclude.session.state.from.auth.response": "false", + "oidc.ciba.grant.enabled": "false", + "saml.artifact.binding": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "require.pushed.authorization.requests": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "d74bada9-c17c-4fda-8a5a-37ecfda51bc9", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "1a2837a2-c688-4ea1-90ff-9cd355677ec1", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "cab8b971-23ef-4790-9b41-1b7c7daaf76d", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "solace_scope", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "26df2d1b-89c9-4b5f-aaa9-bb9216119d1b", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "2ac54554-f68a-4657-b9b1-1bb0c133a0b7", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "9d57e45d-46e0-4aa8-87a1-105143b06525", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "07318111-38b8-4d3c-9f4e-70118320a4ab", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "96bff8f0-0f4c-4331-902b-d44682cd609d", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "302921eb-6ebe-439b-bc9d-7f48c1fd2cf7", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "49e4cfb0-70a5-459c-97e7-8cca7b82e7a3", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "7c4e36e0-0e53-4e1b-9cbf-60b0f283f462", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "4b9188f6-1034-45fe-923c-f43aca1c3416", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "c24e819f-b1ee-41f5-99c8-e83cbfe0ca60", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "3dbe0889-8886-47d8-ad4e-2cf86708809e", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "86d29826-2aa9-4861-9c7c-7fd82a4c7510", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "7e869280-544e-434d-a912-2daaba46e85d", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "a17661b4-063a-44c5-8731-f9ba2521fb3c", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "2ad197bc-9d30-4e1f-9f58-4c97eb840101", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "c426579a-07bb-4c91-a62e-da68bd90a6bd", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "686b368a-82b9-47dc-aaa1-af0c61d6c7a0", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "25f27b4a-8e21-4ca9-85b3-fc6bb1010cda", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "6ca67292-8c98-4386-ac0e-0a6f8c773f5d", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "aa060031-1422-48b5-a40b-aed592238aeb", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "971e264c-64f8-4cbe-bc92-ee76d491d9b6", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "2d749ebf-d125-4247-b824-e8d7fa3b67ba", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "695b889a-bf3a-4af2-b960-a6de3e110dda", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "ea54b479-5698-4f9c-93c0-e938f2010ca1", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "7d543137-b00b-406e-bbd5-140f11d8dc74", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "5c6301d1-7e0c-48fb-a98f-c63d0efa1149", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "078ad50e-8881-4559-b1c3-12889a0a94b1", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "1f60eeca-c70b-4a54-80d4-7e20998a33cb", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "87e64829-4247-417b-acf0-6f4bc692697f", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "81f9e655-9af0-46b2-abcf-3d0744f884b0", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "660d8c7f-dbcf-4df5-bf6a-78b0b29a0979", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "10c03ddc-0781-46b2-b3e1-dc0bd7c9bc19", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "5ef1ebc2-9bd6-45d0-9c76-9ebbc6cd475d", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "495f9e6a-794b-4c6c-9ea3-d435bf7eea45", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "d26374c0-bec6-448e-88f0-c5090b74994e", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "be3ff804-ecc5-4aaf-8373-51f33fb1285b", + "name": "solace_scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ef768530-e6aa-4846-bf75-98ac60672179", + "name": "pubsub+", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "false", + "access.token.claim": "true", + "included.custom.audience": "pubsub+aud" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "email", + "profile", + "role_list", + "roles", + "web-origins" + ], + "defaultOptionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "d8c7889b-d487-4c17-92c8-c7243f29f2a9", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "7ccc2338-62aa-4271-a9e4-7656549caffd", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "620c2c71-e45f-4791-99fc-1197fdccc9a5", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "c63236fa-cf74-4838-8179-77ec9fa3ef05", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "saml-user-property-mapper" + ] + } + }, + { + "id": "bdbaae20-fcf0-4461-a219-7e1d04a37caa", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "c2416358-7086-4a39-a940-973caeea8191", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "2c832c64-be94-4d2d-9676-4532ce66fcf6", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "09dea1c3-d309-4862-8968-8fcde12d349e", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "70f4173c-d247-46f4-a5e9-747dfa92a6ea", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "b41b27c4-da26-4394-93f0-5421cc3a6e04", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "4add83dd-2df1-4513-a172-53c26b34d1d8", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "950e2e88-dbc1-40f1-a487-50a4514cb2b7", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "9597f99b-40ce-483c-bdac-619c47ef3a3b", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "168b554b-08e0-41f2-8c47-578a4501b171", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "556f5237-fe8e-4f5a-a9f4-f16a1bca041e", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "261852d6-efbd-473f-9209-c7a48aa29504", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "9b81c87d-3be3-4b92-b340-32e7c9081df4", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "826ee75a-84bc-48f8-a718-2451506015d4", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c33dda76-da97-41c8-8e04-f3b9234999fb", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Account verification options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5d2646b6-9d6a-46a3-9a70-bd700b9dd0de", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "b0128d0e-b05f-46fd-9089-1c1614181495", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "7f23c06a-e8d8-4c35-8b8e-83417a2bcb8d", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "40e7ec79-f8cf-4da5-baa1-0688b929fa1d", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "9365facb-be45-4aba-9587-5390f3040272", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "855e65a7-8ff7-42a1-9987-adb605d95744", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "e050340f-a8be-4c85-b94e-98f91acf7ee3", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "fd9238b6-adf9-4713-8913-8961d3049825", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "User creation or linking", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5d89ebe0-fccb-4f22-87ed-ec7e3782ed5e", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "dcb3cd09-48dd-4cc6-b935-3d0b905875ea", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Authentication Options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "1e23398f-2c1a-4791-86b2-ea62427d6605", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f5981653-3268-407d-9692-b4c822520655", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "cd82dd4c-ffed-4eed-91e0-93ef27ea0180", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "3fe4c594-8027-48d1-a9a6-52200251c80f", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "56559a9a-f2e3-4d33-9adf-836cc14efb5e", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "ec3571ad-ea3a-4148-b1a9-f5c9a6079c21", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "600", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5" + }, + "keycloakVersion": "16.1.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/integration-tests/solace-client-oauth-integration-tests/src/test/resources/solace.pem b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/solace.pem new file mode 100644 index 0000000..060741a --- /dev/null +++ b/integration-tests/solace-client-oauth-integration-tests/src/test/resources/solace.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIDvzCCAqegAwIBAgIJAKWD5vMlbhRKMA0GCSqGSIb3DQEBCwUAMIGHMQswCQYD +VQQGEwJDQTEMMAoGA1UECAwDTi9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoM +BlNvbGFjZTEgMB4GCSqGSIb3DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNV +BAsMCm15b3JnLXVuaXQxETAPBgNVBAMMCG15cHVic3ViMCAXDTI0MDMxMTA5MjY1 +NVoYDzIxMjQwMjE2MDkyNjU1WjCBhzELMAkGA1UEBhMCQ0ExDDAKBgNVBAgMA04v +QTEPMA0GA1UEBwwGT3R0YXdhMQ8wDQYDVQQKDAZTb2xhY2UxIDAeBgkqhkiG9w0B +CQEWEW15ZW1haWxAZW1haWwuY29tMRMwEQYDVQQLDApteW9yZy11bml0MREwDwYD +VQQDDAhteXB1YnN1YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOpw +2cA6+gvALPEFbTuxdJDWJBI0yewOA0wcjYa3T7zO/akyD+EDcxKZPXXfOdOKWMm1 +5ByMT7Qi+1h0uLPR5tSDP2Ya02Qw2d8bSQWLuNlt2p/Hb698A+y1GFrNe2ggKLsc +aYmjoQa7+rEz1LHas88WGqHhXxCIH0zsb7LfsUM35AeXgdeRmoGlHqA9bJMGN5m7 +ktn93ejP1wfMlGn+eHeNVkM6+G6pjdYbn8WRF6jvky54QE0E4A6U94CZflbG9Efh +86MT1pJEtPzhgKriAt/x+tYqZeE1mvHk/L5eWmnik+KPjLoKXDnF2rAJeBJczjO/ +4hP8K6lUfZ9juqIRKB8CAwEAAaMqMCgwJgYDVR0RBB8wHYIIbXlwdWJzdWKCCWxv +Y2FsaG9zdIIGc29sYWNlMA0GCSqGSIb3DQEBCwUAA4IBAQDWgXDnGeaGThiaSCQJ +GtoH3GSsNtStY5faLIAICnnpjFUoPtZL1CDvhXUqm6GIBpF5Y1f++kSFSNoMJ4M7 +wev5w05jHecCJj2Aan0SBO0wjK0R1BUJkZg0HIa0tOylzeHn5WnR56XEPjL6kdxg +sz0QW+J5DCUX2SO/mRjtoQl3Dyxrtut8HwRbWkbe+kdgXh2FIk526PnVkDfoNusD +MRF5WoKM2uYkYCpmV+CAbuRMmJu0JI+HDatbtSXiTn/xosJlG6WYzBQ+4ZL+6PKF +I7LtP6eqIln09Ujetkp27tmPm2gRv6U2tp85AWZTVQ2cWH5py5XFroE2RR3V6j28 +xy6W +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqcNnAOvoLwCzx +BW07sXSQ1iQSNMnsDgNMHI2Gt0+8zv2pMg/hA3MSmT113znTiljJteQcjE+0IvtY +dLiz0ebUgz9mGtNkMNnfG0kFi7jZbdqfx2+vfAPstRhazXtoICi7HGmJo6EGu/qx +M9Sx2rPPFhqh4V8QiB9M7G+y37FDN+QHl4HXkZqBpR6gPWyTBjeZu5LZ/d3oz9cH +zJRp/nh3jVZDOvhuqY3WG5/FkReo75MueEBNBOAOlPeAmX5WxvRH4fOjE9aSRLT8 +4YCq4gLf8frWKmXhNZrx5Py+Xlpp4pPij4y6Clw5xdqwCXgSXM4zv+IT/CupVH2f +Y7qiESgfAgMBAAECggEAB64oxA5qkKX8Eu1Nlc4Ldo89YUdPcidHXl/1Fvu8ZgAV ++UwFjyaQx4Qzqj/k4hQ/MmR+E51ZIxqeR1iTkHiI6l9eXVb1o+uhx5haPQ9FwAHE +TsW21/XlHwUTxi3DJDchfnfA0VyF8vWHkfSTvDvg9iDQQItklOMQu3Fne2GuqfgD +tpTYz1qmiUOG2gS1P+0d/BE4HGtQyu2UPwymeJIVH+2dfWMRCUzLdqfZyLGIpSe8 +mJheyPSri/2XFsC/9fxuO3OkNMOwVDYIYOutECQr2i3qVo/0tPd0zqKB/wAV5GN0 +WcYYulgLy4YAJNAAW6+IeMcZWz9Mk0qjgRTZe9VFgQKBgQD78OQXb8zbSScmWqx1 +k0ffJzkWoJkJUnwOAtCLowvQbpi88RaCAhxQHsISXD7KJrfWE/y00QtY7omIq9Jc +WOJnmUpnrG7DqYRJEiz29hLHJSHa4U//onhhH4NQgtHwR6kX8s5rlN6DZ2jm5ctn +v64/5uxz5gdXIW6ixDNyl+f1wQKBgQDuN8gdtmjUN2lUBMEasbgn5W3WDVhJDUfX +QLvxQuMxJ7/YwRW+/sFgHyrCaBdcF7G/NcqsHblRoOwu/7jbyw0u23CY6WwRXv2j +fIZGt6Sqsl/7LL1klcMYbME5lgn7qhzHgUnVYLJysAwmXlEAgHtD+pqocdfB+pPY +RDGGjqlV3wKBgQDi9Ay05By5iXt//HyQ6dz7tBykOoXBtRFVmcl9kKIK4CYtRkzN +TtNshVi0K27QsfI3IggqZon/UdqJSKcWU2eYhalWHSomjiVBoeLpkaA2z0dhIkjr +ctNYQogLVd2Cwzsa/LpghVmxK81++pCyZCS3IfHtMdF49v/wFih2WUs2wQKBgEgp +s8B0eosXAhxGmGzKu3uyf7RhNIZktIebf5OVbJd+cBpsW3cRW2kP5/ceaz0lnF3N +IMlE89erhQCzzL8gYqz4IsLfqzIT8Yft+AtCJGrlQDgplHH9AC3M/DfCoOGQ5cj1 +/HTcJxKhC/0vgyBAy5aLOwCeA/sqOlFATzRw0RFHAoGBAIydEqgzBQW5i+19Filo +8mADdtQmwSIyRJ2L3nsMbdej+2wPVxQItRUVLMumfKmhffW2E3bC5y0j/rvOqxKa +igiyDlOoBEnonnYODhSNOwFRaRqqSWjjRKnoA517+XYtrlLtxxEQIATLPhXuF3b9 +i1RRMw3EhJL+BHQYIXm65A/q +-----END PRIVATE KEY----- diff --git a/quarkus-solace-client/deployment/pom.xml b/quarkus-solace-client/deployment/pom.xml index b7158cb..39d7c14 100644 --- a/quarkus-solace-client/deployment/pom.xml +++ b/quarkus-solace-client/deployment/pom.xml @@ -27,6 +27,10 @@ io.quarkus quarkus-devservices-deployment + + io.quarkus + quarkus-oidc-client-deployment + io.quarkus quarkus-junit5-internal diff --git a/quarkus-solace-client/deployment/src/main/java/com/solace/quarkus/deployment/SolaceProcessor.java b/quarkus-solace-client/deployment/src/main/java/com/solace/quarkus/deployment/SolaceProcessor.java index 6305d4e..d95bcc9 100644 --- a/quarkus-solace-client/deployment/src/main/java/com/solace/quarkus/deployment/SolaceProcessor.java +++ b/quarkus-solace-client/deployment/src/main/java/com/solace/quarkus/deployment/SolaceProcessor.java @@ -10,15 +10,14 @@ import com.solace.messaging.MessagingService; import com.solace.quarkus.MessagingServiceClientCustomizer; +import com.solace.quarkus.runtime.OidcProvider; import com.solace.quarkus.runtime.SolaceConfig; import com.solace.quarkus.runtime.SolaceRecorder; import com.solace.quarkus.runtime.observability.SolaceMetricBinder; import com.solacesystems.jcsmp.JCSMPFactory; import io.quarkus.arc.SyntheticCreationalContext; -import io.quarkus.arc.deployment.SyntheticBeanBuildItem; -import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; -import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.deployment.*; import io.quarkus.deployment.annotations.*; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; @@ -31,13 +30,14 @@ import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; class SolaceProcessor { - private static final String FEATURE = "solace-client"; private static final ParameterizedType SOLACE_CUSTOMIZER_INJECTION_TYPE = ParameterizedType.create( DotName.createSimple(Instance.class), new Type[] { ClassType.create(DotName.createSimple(MessagingServiceClientCustomizer.class.getName())) }, null); + private static final Type OIDC_PROVIDER = ClassType.create(DotName.createSimple(OidcProvider.class)); + private static final AnnotationInstance[] EMPTY_ANNOTATIONS = new AnnotationInstance[0]; @BuildStep @@ -59,21 +59,24 @@ ExtensionSslNativeSupportBuildItem ssl() { @Record(ExecutionTime.RUNTIME_INIT) ServiceStartBuildItem init( SolaceConfig config, SolaceRecorder recorder, - ShutdownContextBuildItem shutdown, BuildProducer syntheticBeans) { + ShutdownContextBuildItem shutdown, BuildProducer syntheticBeans, + BuildProducer additionalBeanBuildItemBuildProducer) { Function, MessagingService> function = recorder.init(config, shutdown); + additionalBeanBuildItemBuildProducer.produce(AdditionalBeanBuildItem.unremovableOf(OidcProvider.class)); + SyntheticBeanBuildItem.ExtendedBeanConfigurator solaceConfigurator = SyntheticBeanBuildItem .configure(MessagingService.class) .defaultBean() .scope(ApplicationScoped.class) .addInjectionPoint(SOLACE_CUSTOMIZER_INJECTION_TYPE, EMPTY_ANNOTATIONS) + .addInjectionPoint(OIDC_PROVIDER) .createWith(function) .unremovable() .setRuntimeInit(); syntheticBeans.produce(solaceConfigurator.done()); - return new ServiceStartBuildItem(FEATURE); } diff --git a/quarkus-solace-client/runtime/pom.xml b/quarkus-solace-client/runtime/pom.xml index 5e1155b..771601c 100644 --- a/quarkus-solace-client/runtime/pom.xml +++ b/quarkus-solace-client/runtime/pom.xml @@ -18,7 +18,10 @@ com.solace solace-messaging-client - + + io.quarkus + quarkus-oidc-client + io.quarkus quarkus-micrometer diff --git a/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/OidcProvider.java b/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/OidcProvider.java new file mode 100644 index 0000000..67fd962 --- /dev/null +++ b/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/OidcProvider.java @@ -0,0 +1,82 @@ +package com.solace.quarkus.runtime; + +import static com.solace.messaging.config.SolaceProperties.AuthenticationProperties.SCHEME_OAUTH2_ACCESS_TOKEN; + +import java.time.Duration; +import java.util.Optional; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import com.solace.messaging.MessagingService; + +import io.quarkus.logging.Log; +import io.quarkus.oidc.client.OidcClient; +import io.quarkus.oidc.client.OidcClients; +import io.quarkus.oidc.client.Tokens; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.infrastructure.Infrastructure; + +@ApplicationScoped +public class OidcProvider { + @ConfigProperty(name = "quarkus.solace.oidc.refresh.interval", defaultValue = "60s") + Duration duration; + + @ConfigProperty(name = "quarkus.solace.oidc.client-name") + Optional oidcClientName; + + @Inject + OidcClients clients; + + private volatile Tokens lastToken; + + Tokens getToken() { + OidcClient client = getClient(); + Tokens firstToken = client.getTokens().await().indefinitely(); + lastToken = firstToken; + return firstToken; + } + + void init(MessagingService service) { + OidcClient client = getClient(); + Multi.createFrom().ticks().every(duration) + .emitOn(Infrastructure.getDefaultWorkerPool()) + .filter(x -> lastToken == null + || lastToken.getRefreshTokenTimeSkew() == null + || lastToken.isAccessTokenWithinRefreshInterval()) + .call(() -> { + if (lastToken != null && lastToken.getRefreshToken() != null) { + Log.info("Refreshing access token for Solace connection"); + return client.refreshTokens(lastToken.getRefreshToken()).invoke(tokens -> lastToken = tokens); + } else { + Log.info("Acquiring access token for Solace connection"); + return client.getTokens().invoke(tokens -> lastToken = tokens); + } + }) + .onFailure().call(t -> { + Log.error("Failed to acquire access token for Solace connection", t); + // ignore the refresh + return Uni.createFrom().voidItem(); + }) + .subscribe().with(x -> { + if (service.isConnected()) { + service.updateProperty(SCHEME_OAUTH2_ACCESS_TOKEN, lastToken.getAccessToken()); + } else { + Log.info("Solace service is not connected, cannot update access token without valid connection"); + } + }); + } + + OidcClient getClient() { + return oidcClientName.map(clients::getClient) + .orElseGet(clients::getClient); + } + + public Tokens getLastToken() { + return lastToken; + } + +} \ No newline at end of file diff --git a/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/SolaceRecorder.java b/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/SolaceRecorder.java index 01bcda6..f4bc041 100644 --- a/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/SolaceRecorder.java +++ b/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/SolaceRecorder.java @@ -38,11 +38,18 @@ public MessagingService apply(SyntheticCreationalContext conte } } - MessagingServiceClientBuilder builder = MessagingService.builder(ConfigurationProfile.V1) - .fromProperties(properties); - Instance reference = context.getInjectedReference(CUSTOMIZER); + OidcProvider oidcProvider = context.getInjectedReference(OidcProvider.class); + + String authScheme = (String) properties.get(SolaceProperties.AuthenticationProperties.SCHEME); + + if (oidcProvider != null && authScheme != null && "AUTHENTICATION_SCHEME_OAUTH2".equals(authScheme)) { + properties.put(SolaceProperties.AuthenticationProperties.SCHEME_OAUTH2_ACCESS_TOKEN, + oidcProvider.getToken().getAccessToken()); + } + MessagingServiceClientBuilder builder = MessagingService.builder(ConfigurationProfile.V1) + .fromProperties(properties); MessagingService service; if (reference.isUnsatisfied()) { service = builder.build(); @@ -54,12 +61,16 @@ public MessagingService apply(SyntheticCreationalContext conte } } + if ("AUTHENTICATION_SCHEME_OAUTH2".equals(authScheme)) { + oidcProvider.init(service); + } var tmp = service; shutdown.addLastShutdownTask(() -> { if (tmp.isConnected()) { tmp.disconnect(); } }); + return service.connect(); } }; diff --git a/quarkus-solace-messaging-connector/runtime/pom.xml b/quarkus-solace-messaging-connector/runtime/pom.xml index 3d295a1..adf1440 100644 --- a/quarkus-solace-messaging-connector/runtime/pom.xml +++ b/quarkus-solace-messaging-connector/runtime/pom.xml @@ -23,6 +23,11 @@ io.smallrye.reactive smallrye-connector-attribute-processor + + io.smallrye.reactive + smallrye-reactive-messaging-otel + 4.16.0 + com.solace solace-messaging-client @@ -132,6 +137,13 @@ slf4j-log4j12 test + + + org.keycloak + keycloak-admin-client + test + + diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/SolaceConnector.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/SolaceConnector.java index 6badc57..289408f 100644 --- a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/SolaceConnector.java +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/SolaceConnector.java @@ -37,6 +37,7 @@ //@ConnectorAttribute(name = "client.type", type = "string", direction = INCOMING_AND_OUTGOING, description = "Direct or persisted", defaultValue = "persisted") @ConnectorAttribute(name = "client.lazy.start", type = "boolean", direction = INCOMING_AND_OUTGOING, description = "Whether the receiver or publisher is started at initialization or lazily at subscription time", defaultValue = "false") @ConnectorAttribute(name = "client.graceful-shutdown", type = "boolean", direction = INCOMING_AND_OUTGOING, description = "Whether to shutdown client gracefully", defaultValue = "true") +@ConnectorAttribute(name = "client.tracing-enabled", type = "boolean", direction = INCOMING_AND_OUTGOING, description = "Whether to enable tracing for incoming and outgoing messages", defaultValue = "false") @ConnectorAttribute(name = "client.graceful-shutdown.wait-timeout", type = "long", direction = INCOMING_AND_OUTGOING, description = "Timeout in milliseconds to wait for messages to finish processing before shutdown", defaultValue = "10000") @ConnectorAttribute(name = "consumer.queue.name", type = "string", direction = INCOMING, description = "The queue name of receiver.") @ConnectorAttribute(name = "consumer.queue.type", type = "string", direction = INCOMING, description = "The queue type of receiver. Supported values `durable-exclusive`, `durable-non-exclusive`, `non-durable-exclusive`", defaultValue = "durable-exclusive") diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/incoming/SolaceIncomingChannel.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/incoming/SolaceIncomingChannel.java index 634a05a..7f0c685 100644 --- a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/incoming/SolaceIncomingChannel.java +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/incoming/SolaceIncomingChannel.java @@ -4,9 +4,7 @@ import java.time.Duration; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Flow; @@ -22,6 +20,7 @@ import com.solace.messaging.config.MissingResourcesCreationConfiguration.MissingResourcesCreationStrategy; import com.solace.messaging.config.ReceiverActivationPassivationConfiguration; import com.solace.messaging.config.ReplayStrategy; +import com.solace.messaging.config.SolaceConstants; import com.solace.messaging.receiver.InboundMessage; import com.solace.messaging.receiver.PersistentMessageReceiver; import com.solace.messaging.resources.Queue; @@ -29,6 +28,8 @@ import com.solace.quarkus.messaging.SolaceConnectorIncomingConfiguration; import com.solace.quarkus.messaging.fault.*; import com.solace.quarkus.messaging.i18n.SolaceLogging; +import com.solace.quarkus.messaging.tracing.SolaceOpenTelemetryInstrumenter; +import com.solace.quarkus.messaging.tracing.SolaceTrace; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; @@ -44,13 +45,14 @@ public class SolaceIncomingChannel implements ReceiverActivationPassivationConfi private final SolaceAckHandler ackHandler; private final SolaceFailureHandler failureHandler; private final AtomicBoolean closed = new AtomicBoolean(false); - private final AtomicBoolean alive = new AtomicBoolean(false); + private final AtomicBoolean alive = new AtomicBoolean(true); private final PersistentMessageReceiver receiver; private final Flow.Publisher> stream; private final ExecutorService pollerThread; private final boolean gracefulShutdown; private final long gracefulShutdownWaitTimeout; private final List failures = new ArrayList<>(); + private final SolaceOpenTelemetryInstrumenter solaceOpenTelemetryInstrumenter; private volatile MessagingService solace; // Assuming we won't ever exceed the limit of an unsigned long... @@ -107,19 +109,55 @@ public SolaceIncomingChannel(Vertx vertx, SolaceConnectorIncomingConfiguration i // TODO Here use a subscription receiver.receiveAsync with an internal queue this.pollerThread = Executors.newSingleThreadExecutor(); - this.stream = Multi.createBy().repeating() + + Multi> incomingMulti = Multi.createBy().repeating() .uni(() -> Uni.createFrom().item(receiver::receiveMessage) .runSubscriptionOn(pollerThread)) .until(__ -> closed.get()) .emitOn(context::runOnContext) .map(consumed -> new SolaceInboundMessage<>(consumed, ackHandler, failureHandler, - unacknowledgedMessageTracker, this::reportFailure)) - .plug(m -> lazyStart - ? m.onSubscription() - .call(() -> Uni.createFrom().completionStage(receiver.startAsync())) - : m) + unacknowledgedMessageTracker, this::reportFailure)); + + if (ic.getClientTracingEnabled()) { + solaceOpenTelemetryInstrumenter = SolaceOpenTelemetryInstrumenter.createForIncoming(); + incomingMulti = incomingMulti.map(message -> { + InboundMessage consumedMessage = message.getMetadata(SolaceInboundMetadata.class).get().getMessage(); + Map messageProperties = new HashMap<>(); + + messageProperties.put("messaging.solace.replication_group_message_id", + consumedMessage.getReplicationGroupMessageId().toString()); + messageProperties.put("messaging.solace.priority", Integer.toString(consumedMessage.getPriority())); + if (consumedMessage.getProperties().size() > 0) { + messageProperties.putAll(consumedMessage.getProperties()); + } + SolaceTrace solaceTrace = new SolaceTrace.Builder() + .withDestinationKind("queue") + .withTopic(consumedMessage.getDestinationName()) + .withMessageID(consumedMessage.getApplicationMessageId()) + .withCorrelationID(consumedMessage.getCorrelationId()) + .withPartitionKey( + consumedMessage + .hasProperty(SolaceConstants.MessageUserPropertyConstants.QUEUE_PARTITION_KEY) + ? consumedMessage + .getProperty( + SolaceConstants.MessageUserPropertyConstants.QUEUE_PARTITION_KEY) + : null) + .withPayloadSize(Long.valueOf(consumedMessage.getPayloadAsBytes().length)) + .withProperties(messageProperties) + .build(); + return solaceOpenTelemetryInstrumenter.traceIncoming(message, solaceTrace, true); + }); + } else { + solaceOpenTelemetryInstrumenter = null; + } + + this.stream = incomingMulti.plug(m -> lazyStart + ? m.onSubscription() + .call(() -> Uni.createFrom().completionStage(receiver.startAsync())) + : m) .onItem().invoke(() -> alive.set(true)) .onFailure().retry().withBackOff(Duration.ofSeconds(1)).atMost(3).onFailure().invoke(this::reportFailure); + if (!lazyStart) { receiver.start(); } diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/outgoing/SolaceOutgoingChannel.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/outgoing/SolaceOutgoingChannel.java index 1b4d353..84c0282 100644 --- a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/outgoing/SolaceOutgoingChannel.java +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/outgoing/SolaceOutgoingChannel.java @@ -22,6 +22,8 @@ import com.solace.messaging.resources.Topic; import com.solace.quarkus.messaging.SolaceConnectorOutgoingConfiguration; import com.solace.quarkus.messaging.i18n.SolaceLogging; +import com.solace.quarkus.messaging.tracing.SolaceOpenTelemetryInstrumenter; +import com.solace.quarkus.messaging.tracing.SolaceTrace; import io.netty.handler.codec.http.HttpHeaderValues; import io.smallrye.mutiny.Uni; @@ -42,8 +44,9 @@ public class SolaceOutgoingChannel private final SenderProcessor processor; private final boolean gracefulShutdown; private final long gracefulShutdownWaitTimeout; - private final AtomicBoolean alive = new AtomicBoolean(false); + private final AtomicBoolean alive = new AtomicBoolean(true); private final List failures = new ArrayList<>(); + private final SolaceOpenTelemetryInstrumenter solaceOpenTelemetryInstrumenter; private volatile boolean isPublisherReady = true; private volatile MessagingService solace; @@ -75,8 +78,14 @@ public SolaceOutgoingChannel(Vertx vertx, SolaceConnectorOutgoingConfiguration o } boolean lazyStart = oc.getClientLazyStart(); this.topic = Topic.of(oc.getProducerTopic().orElse(this.channel)); + if (oc.getClientTracingEnabled()) { + solaceOpenTelemetryInstrumenter = SolaceOpenTelemetryInstrumenter.createForOutgoing(); + } else { + solaceOpenTelemetryInstrumenter = null; + } this.processor = new SenderProcessor(oc.getProducerMaxInflightMessages(), oc.getProducerWaitForPublishReceipt(), - m -> sendMessage(solace, m, oc.getProducerWaitForPublishReceipt()).onFailure().invoke(this::reportFailure)); + m -> sendMessage(solace, m, oc.getProducerWaitForPublishReceipt(), oc.getClientTracingEnabled()).onFailure() + .invoke(this::reportFailure)); this.subscriber = MultiUtils.via(processor, multi -> multi.plug( m -> lazyStart ? m.onSubscription().call(() -> Uni.createFrom().completionStage(publisher.startAsync())) : m)); if (!lazyStart) { @@ -91,10 +100,11 @@ public void ready() { }); } - private Uni sendMessage(MessagingService solace, Message m, boolean waitForPublishReceipt) { + private Uni sendMessage(MessagingService solace, Message m, boolean waitForPublishReceipt, + boolean isTracingEnabled) { // TODO - Use isPublisherReady to check if publisher is in ready state before publishing. This is required when back-pressure is set to reject. We need to block this call till isPublisherReady is true - return publishMessage(publisher, m, solace.messageBuilder(), waitForPublishReceipt) + return publishMessage(publisher, m, solace.messageBuilder(), waitForPublishReceipt, isTracingEnabled) .onItem().transformToUni(receipt -> { alive.set(true); if (receipt != null) { @@ -118,7 +128,7 @@ private synchronized void reportFailure(Throwable throwable) { } private Uni publishMessage(PersistentMessagePublisher publisher, Message m, - OutboundMessageBuilder msgBuilder, boolean waitForPublishReceipt) { + OutboundMessageBuilder msgBuilder, boolean waitForPublishReceipt, boolean isTracingEnabled) { publishedMessagesTracker.increment(); AtomicReference topic = new AtomicReference<>(this.topic); OutboundMessage outboundMessage; @@ -159,6 +169,7 @@ private Uni publishMessage(PersistentMessagePublisher publisher, topic.set(Topic.of(metadata.getDynamicDestination())); } }); + Object payload = m.getPayload(); if (payload instanceof OutboundMessage) { outboundMessage = (OutboundMessage) payload; @@ -173,6 +184,25 @@ private Uni publishMessage(PersistentMessagePublisher publisher, .withHTTPContentHeader(HttpHeaderValues.APPLICATION_JSON.toString(), "") .build(Json.encode(payload)); } + + if (isTracingEnabled) { + SolaceTrace solaceTrace = new SolaceTrace.Builder() + .withDestinationKind("topic") + .withTopic(topic.get().getName()) + .withMessageID(outboundMessage.getApplicationMessageId()) + .withCorrelationID(outboundMessage.getCorrelationId()) + .withPartitionKey( + outboundMessage + .hasProperty(SolaceConstants.MessageUserPropertyConstants.QUEUE_PARTITION_KEY) + ? outboundMessage + .getProperty( + SolaceConstants.MessageUserPropertyConstants.QUEUE_PARTITION_KEY) + : null) + .withPayloadSize(Long.valueOf(outboundMessage.getPayloadAsBytes().length)) + .withProperties(outboundMessage.getProperties()).build(); + solaceOpenTelemetryInstrumenter.traceOutgoing(m, solaceTrace); + } + return Uni.createFrom(). emitter(e -> { boolean exitExceptionally = false; try { diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceAttributeExtractor.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceAttributeExtractor.java new file mode 100644 index 0000000..0b4428c --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceAttributeExtractor.java @@ -0,0 +1,72 @@ +package com.solace.quarkus.messaging.tracing; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; + +public class SolaceAttributeExtractor implements AttributesExtractor { + private final MessagingAttributesGetter messagingAttributesGetter; + + public SolaceAttributeExtractor() { + this.messagingAttributesGetter = new SolaceMessagingAttributesGetter(); + } + + @Override + public void onStart(AttributesBuilder attributesBuilder, Context context, SolaceTrace solaceTrace) { + attributesBuilder.put("messaging.solace.partition_number", solaceTrace.getPartitionKey()); + } + + @Override + public void onEnd(AttributesBuilder attributesBuilder, Context context, SolaceTrace solaceTrace, Void unused, + Throwable throwable) { + + } + + public MessagingAttributesGetter getMessagingAttributesGetter() { + return messagingAttributesGetter; + } + + private static final class SolaceMessagingAttributesGetter implements MessagingAttributesGetter { + @Override + public String getSystem(final SolaceTrace solaceTrace) { + return "SolacePubSub+"; + } + + @Override + public String getDestinationKind(SolaceTrace solaceTrace) { + return solaceTrace.getDestinationKind(); + } + + @Override + public String getDestination(final SolaceTrace solaceTrace) { + return solaceTrace.getTopic(); + } + + @Override + public boolean isTemporaryDestination(final SolaceTrace solaceTrace) { + return false; + } + + @Override + public String getConversationId(final SolaceTrace solaceTrace) { + return solaceTrace.getCorrelationId(); + } + + @Override + public Long getMessagePayloadSize(final SolaceTrace solaceTrace) { + return solaceTrace.getPayloadSize(); + } + + @Override + public Long getMessagePayloadCompressedSize(final SolaceTrace solaceTrace) { + return null; + } + + @Override + public String getMessageId(final SolaceTrace solaceTrace, final Void unused) { + return solaceTrace.getMessageId(); + } + + } +} diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceOpenTelemetryInstrumenter.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceOpenTelemetryInstrumenter.java new file mode 100644 index 0000000..4c16fcf --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceOpenTelemetryInstrumenter.java @@ -0,0 +1,60 @@ +package com.solace.quarkus.messaging.tracing; + +import org.eclipse.microprofile.reactive.messaging.Message; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; +import io.smallrye.reactive.messaging.tracing.TracingUtils; + +public class SolaceOpenTelemetryInstrumenter { + + private final Instrumenter instrumenter; + + public SolaceOpenTelemetryInstrumenter(Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + + public static SolaceOpenTelemetryInstrumenter createForIncoming() { + return createInstrumenter(true); + } + + public static SolaceOpenTelemetryInstrumenter createForOutgoing() { + return createInstrumenter(false); + } + + private static SolaceOpenTelemetryInstrumenter createInstrumenter(boolean incoming) { + MessageOperation messageOperation = incoming ? MessageOperation.RECEIVE : MessageOperation.SEND; + + SolaceAttributeExtractor myExtractor = new SolaceAttributeExtractor(); + MessagingAttributesGetter attributesGetter = myExtractor.getMessagingAttributesGetter(); + var spanNameExtractor = MessagingSpanNameExtractor.create(attributesGetter, messageOperation); + InstrumenterBuilder builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", spanNameExtractor); + var attributesExtractor = MessagingAttributesExtractor.create(attributesGetter, messageOperation); + + builder + .addAttributesExtractor(attributesExtractor) + .addAttributesExtractor(myExtractor); + + if (incoming) { + return new SolaceOpenTelemetryInstrumenter(builder.buildConsumerInstrumenter(SolaceTraceTextMapGetter.INSTANCE)); + } else { + return new SolaceOpenTelemetryInstrumenter(builder.buildProducerInstrumenter(SolaceTraceTextMapSetter.INSTANCE)); + } + } + // + + public Message traceIncoming(Message message, SolaceTrace myTrace, boolean makeCurrent) { + return TracingUtils.traceIncoming(instrumenter, message, myTrace, makeCurrent); + } + + public void traceOutgoing(Message message, SolaceTrace myTrace) { + TracingUtils.traceOutgoing(instrumenter, message, myTrace); + } + +} diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTrace.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTrace.java new file mode 100644 index 0000000..cc3dba7 --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTrace.java @@ -0,0 +1,101 @@ +package com.solace.quarkus.messaging.tracing; + +import java.util.Map; + +public class SolaceTrace { + private final String destinationKind; + private final String topic; + private final String messageId; + private final String correlationId; + private final String partitionKey; + private final Long payloadSize; + private final Map messageProperties; + + private SolaceTrace(String destinationKind, String topic, String messageId, String correlationId, String partitionKey, + Long payloadSize, Map messageProperties) { + this.destinationKind = destinationKind; + this.topic = topic; + this.messageId = messageId; + this.correlationId = correlationId; + this.partitionKey = partitionKey; + this.payloadSize = payloadSize; + this.messageProperties = messageProperties; + } + + public String getDestinationKind() { + return destinationKind; + } + + public String getTopic() { + return topic; + } + + public String getMessageId() { + return messageId; + } + + public String getCorrelationId() { + return correlationId; + } + + public String getPartitionKey() { + return partitionKey; + } + + public Long getPayloadSize() { + return payloadSize; + } + + public Map getMessageProperties() { + return messageProperties; + } + + public static class Builder { + private String destinationKind; + private String topic; + private String messageId; + private String correlationId; + private String partitionKey; + private Long payloadSize; + private Map properties; + + public Builder withDestinationKind(String destinationKind) { + this.destinationKind = destinationKind; + return this; + } + + public Builder withTopic(String topic) { + this.topic = topic; + return this; + } + + public Builder withMessageID(String messageId) { + this.messageId = messageId; + return this; + } + + public Builder withCorrelationID(String correlationId) { + this.correlationId = correlationId; + return this; + } + + public Builder withPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + return this; + } + + public Builder withPayloadSize(Long payloadSize) { + this.payloadSize = payloadSize; + return this; + } + + public Builder withProperties(Map properties) { + this.properties = properties; + return this; + } + + public SolaceTrace build() { + return new SolaceTrace(destinationKind, topic, messageId, correlationId, partitionKey, payloadSize, properties); + } + } +} diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapGetter.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapGetter.java new file mode 100644 index 0000000..b4e1c60 --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapGetter.java @@ -0,0 +1,27 @@ +package com.solace.quarkus.messaging.tracing; + +import java.util.ArrayList; +import java.util.Map; + +import io.opentelemetry.context.propagation.TextMapGetter; + +public enum SolaceTraceTextMapGetter implements TextMapGetter { + INSTANCE; + + @Override + public Iterable keys(SolaceTrace carrier) { + Map headers = carrier.getMessageProperties(); + return new ArrayList<>(headers.keySet()); + } + + @Override + public String get(final SolaceTrace carrier, final String key) { + if (carrier != null) { + Map properties = carrier.getMessageProperties(); + if (properties != null) { + return properties.get(key); + } + } + return null; + } +} diff --git a/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapSetter.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapSetter.java new file mode 100644 index 0000000..327f583 --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapSetter.java @@ -0,0 +1,17 @@ +package com.solace.quarkus.messaging.tracing; + +import java.util.Map; + +import io.opentelemetry.context.propagation.TextMapSetter; + +public enum SolaceTraceTextMapSetter implements TextMapSetter { + INSTANCE; + + @Override + public void set(SolaceTrace carrier, String key, String value) { + if (carrier != null) { + Map properties = carrier.getMessageProperties(); + properties.put(key, value); + } + } +} diff --git a/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/SolaceOAuthTest.java b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/SolaceOAuthTest.java new file mode 100644 index 0000000..63db077 --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/SolaceOAuthTest.java @@ -0,0 +1,172 @@ +package com.solace.quarkus.messaging; + +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; + +import java.io.*; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.*; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; + +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.awaitility.Awaitility; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.testcontainers.utility.MountableFile; + +import com.solace.messaging.MessagingService; +import com.solace.messaging.config.SolaceProperties; +import com.solace.messaging.config.profile.ConfigurationProfile; +import com.solace.messaging.publisher.PersistentMessagePublisher; +import com.solace.messaging.resources.Topic; +import com.solace.quarkus.messaging.base.KeyCloakContainer; +import com.solace.quarkus.messaging.base.SolaceContainer; +import com.solace.quarkus.messaging.incoming.SolaceIncomingChannel; + +import io.smallrye.mutiny.Multi; +import io.smallrye.reactive.messaging.test.common.config.MapBasedConfig; +import io.vertx.mutiny.core.Vertx; + +public class SolaceOAuthTest { + + private static final String SOLACE_IMAGE = "solace/solace-pubsub-standard:latest"; + + private static SolaceContainer createSolaceContainer() { + return new SolaceContainer(SOLACE_IMAGE); + } + + private static KeyCloakContainer keyCloakContainer; + private static SolaceContainer solaceContainer; + + @BeforeAll + static void startContainers() { + keyCloakContainer = new KeyCloakContainer(); + keyCloakContainer.start(); + keyCloakContainer.createHostsFile(); + await().until(() -> keyCloakContainer.isRunning()); + + solaceContainer = createSolaceContainer(); + solaceContainer.withCredentials("user", "pass") + .withClientCert(MountableFile.forClasspathResource("solace.pem"), + MountableFile.forClasspathResource("keycloak.crt"), false) + .withOAuth() + .withExposedPorts(SolaceContainer.Service.SMF.getPort(), SolaceContainer.Service.SMF_SSL.getPort(), 1943, 8080) + .withPublishTopic("quarkus/integration/test/replay/messages", SolaceContainer.Service.SMF) + .withPublishTopic("quarkus/integration/test/default/>", SolaceContainer.Service.SMF) + .withPublishTopic("quarkus/integration/test/provisioned/>", SolaceContainer.Service.SMF) + .withPublishTopic("quarkus/integration/test/dynamic/>", SolaceContainer.Service.SMF); + + solaceContainer.start(); + await().until(() -> solaceContainer.isRunning()); + } + + private static KeyStore createKeyStore(byte[] ca, byte[] serviceCa) { + try { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + if (ca != null) { + keyStore.setCertificateEntry("keycloak", + cf.generateCertificate(new ByteArrayInputStream(ca))); + } + if (serviceCa != null) { + keyStore.setCertificateEntry("service-ca", + cf.generateCertificate(new ByteArrayInputStream(serviceCa))); + } + return keyStore; + } catch (Exception ignored) { + return null; + } + } + + private String getAccessToken() throws IOException { + ClassLoader classLoader = SolaceOAuthTest.class.getClassLoader(); + InputStream is = new FileInputStream(classLoader.getResource("keycloak.crt").getFile()); + KeyStore trustStore = createKeyStore(is.readAllBytes(), null); + Client resteasyClient = ClientBuilder.newBuilder() + .connectTimeout(30, TimeUnit.SECONDS) + .trustStore(trustStore) + .hostnameVerifier(new DefaultHostnameVerifier()) + .build(); + + Keycloak keycloak = KeycloakBuilder.builder() + .serverUrl(keyCloakContainer.getOrigin(KeyCloakContainer.Service.HTTPS)) + .realm("solace") + .clientId("solace") + .clientSecret("solace-secret") + .grantType("client_credentials") + .resteasyClient(resteasyClient) + .build(); + return keycloak.tokenManager().getAccessTokenString(); + } + + private MessagingService getMessagingService() throws IOException { + Properties properties = new Properties(); + properties.put(SolaceProperties.TransportLayerProperties.HOST, + solaceContainer.getOrigin(SolaceContainer.Service.SMF_SSL)); + properties.put(SolaceProperties.ServiceProperties.VPN_NAME, solaceContainer.getVpn()); + properties.put(SolaceProperties.AuthenticationProperties.SCHEME, "AUTHENTICATION_SCHEME_OAUTH2"); + properties.put(SolaceProperties.TransportLayerSecurityProperties.CERT_VALIDATED, "false"); + properties.put(SolaceProperties.TransportLayerSecurityProperties.CERT_VALIDATE_SERVERNAME, "false"); + properties.put(SolaceProperties.AuthenticationProperties.SCHEME_OAUTH2_ACCESS_TOKEN, getAccessToken()); + + MessagingService messagingService = MessagingService.builder(ConfigurationProfile.V1) + .fromProperties(properties) + .build(); + messagingService.connect(); + + return messagingService; + } + + @Test + void oauthTest() throws IOException { + MapBasedConfig config = new MapBasedConfig() + .with("channel-name", "in") + .with("consumer.queue.name", "queue-" + UUID.randomUUID().getMostSignificantBits()) + .with("consumer.queue.add-additional-subscriptions", true) + .with("consumer.queue.missing-resource-creation-strategy", "create-on-start") + .with("consumer.queue.subscriptions", SolaceContainer.INTEGRATION_TEST_QUEUE_SUBSCRIPTION); + + MessagingService messagingService = getMessagingService(); + SolaceIncomingChannel solaceIncomingChannel = new SolaceIncomingChannel(Vertx.vertx(), + new SolaceConnectorIncomingConfiguration(config), messagingService); + + CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); + CopyOnWriteArrayList ackedMessageList = new CopyOnWriteArrayList<>(); + + Flow.Publisher> stream = solaceIncomingChannel.getStream(); + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + Multi.createFrom().publisher(stream).subscribe().with(message -> { + list.add(message); + executorService.schedule(() -> { + ackedMessageList.add(message); + CompletableFuture.runAsync(message::ack); + }, 1, TimeUnit.SECONDS); + }); + + // Produce messages + PersistentMessagePublisher publisher = messagingService.createPersistentMessagePublisherBuilder() + .build() + .start(); + Topic tp = Topic.of(SolaceContainer.INTEGRATION_TEST_QUEUE_SUBSCRIPTION); + publisher.publish("1", tp); + publisher.publish("2", tp); + publisher.publish("3", tp); + publisher.publish("4", tp); + publisher.publish("5", tp); + + Awaitility.await().until(() -> list.size() == 5); + // Assert on acknowledged messages + solaceIncomingChannel.close(); + Awaitility.await().atMost(2, TimeUnit.MINUTES).until(() -> ackedMessageList.size() == 5); + executorService.shutdown(); + } + +} diff --git a/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/KeyCloakContainer.java b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/KeyCloakContainer.java new file mode 100644 index 0000000..c3474dd --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/KeyCloakContainer.java @@ -0,0 +1,102 @@ +package com.solace.quarkus.messaging.base; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.MountableFile; + +public class KeyCloakContainer extends GenericContainer { + + public KeyCloakContainer() { + super("quay.io/keycloak/keycloak:20.0.0"); + addFixedExposedPort(7777, Service.HTTP.getPort()); + addFixedExposedPort(7778, Service.HTTPS.getPort()); + withEnv("KEYCLOAK_ADMIN", "admin"); + withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin"); + withEnv("KEYCLOAK_FRONTEND_URL", "https://localhost:7778"); + withEnv("KC_HOSTNAME_URL", "https://localhost:7778"); + withEnv("KC_HTTPS_CERTIFICATE_FILE", "/opt/keycloak/conf/server.crt"); + withEnv("KC_HTTPS_CERTIFICATE_KEY_FILE", "/opt/keycloak/conf/server.key"); + waitingFor(Wait.forLogMessage(".*Listening.*", 1)); + withNetwork(Network.SHARED); + withNetworkAliases("keycloak"); + withCopyFileToContainer(MountableFile.forClasspathResource("keycloak/realms/solace-realm.json"), + "/opt/keycloak/data/import/solace-realm.json"); + withCopyFileToContainer(MountableFile.forClasspathResource("keycloak.crt"), "/opt/keycloak/conf/server.crt"); + withCopyFileToContainer(MountableFile.forClasspathResource("keycloak.key"), "/opt/keycloak/conf/server.key"); + withCommand("start-dev", "--import-realm"); + } + + public void createHostsFile() { + try (FileWriter fileWriter = new FileWriter("target/hosts")) { + String dockerHost = this.getHost(); + if ("localhost".equals(dockerHost)) { + fileWriter.write("127.0.0.1 keycloak"); + } else { + fileWriter.write(dockerHost + " keycloak"); + } + fileWriter.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Host address for provided service + * + * @param service - service for which host needs to be retrieved + * @return host address exposed from the container + */ + public String getOrigin(Service service) { + return String.format("%s://%s:%s", service.getProtocol(), getHost(), getMappedPort(service.getPort())); + } + + public enum Service { + HTTP("http", 8080, "http", false), + HTTPS("https", 8443, "https", true); + + private final String name; + private final Integer port; + private final String protocol; + private final boolean supportSSL; + + Service(String name, Integer port, String protocol, boolean supportSSL) { + this.name = name; + this.port = port; + this.protocol = protocol; + this.supportSSL = supportSSL; + } + + /** + * @return Port assigned for the service + */ + public Integer getPort() { + return this.port; + } + + /** + * @return Protocol of the service + */ + public String getProtocol() { + return this.protocol; + } + + /** + * @return Name of the service + */ + public String getName() { + return this.name; + } + + /** + * @return Is SSL for this service supported ? + */ + public boolean isSupportSSL() { + return this.supportSSL; + } + } +} diff --git a/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceBrokerExtension.java b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceBrokerExtension.java index d3a4403..7cfefaf 100644 --- a/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceBrokerExtension.java +++ b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceBrokerExtension.java @@ -6,12 +6,8 @@ import java.time.Duration; import org.jboss.logging.Logger; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.*; import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; public class SolaceBrokerExtension implements BeforeAllCallback, ParameterResolver, CloseableResource { diff --git a/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceContainer.java b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceContainer.java index a6ad7a7..d01d6d3 100644 --- a/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceContainer.java +++ b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/SolaceContainer.java @@ -6,6 +6,7 @@ import java.util.List; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.Transferable; import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Pair; @@ -50,6 +51,8 @@ public class SolaceContainer extends GenericContainer { private final List> subscribeTopicsConfiguration = new ArrayList<>(); private boolean withClientCert; + private boolean withOAuth; + private boolean clientCertificateAuthority; /** * Create a new solace container with the specified image name. @@ -76,6 +79,8 @@ public SolaceContainer(DockerImageName dockerImageName) { withEnv("logging_system_output", "all"); withEnv("username_admin_globalaccesslevel", "admin"); withEnv("username_admin_password", "admin"); + withNetwork(Network.SHARED); + withNetworkAliases("solace"); } @Override @@ -100,6 +105,17 @@ private Transferable createConfigurationScript() { updateConfigScript(scriptBuilder, "enable"); updateConfigScript(scriptBuilder, "configure"); + // telemetry configuration + updateConfigScript(scriptBuilder, "message-vpn default"); + updateConfigScript(scriptBuilder, "create telemetry-profile trace"); + updateConfigScript(scriptBuilder, "trace"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "create filter default"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "create subscription \">\""); + updateConfigScript(scriptBuilder, "end"); + + updateConfigScript(scriptBuilder, "configure"); // create replay log updateConfigScript(scriptBuilder, "message-spool message-vpn default"); updateConfigScript(scriptBuilder, "create replay-log integration-test-replay-log"); @@ -177,12 +193,31 @@ private Transferable createConfigurationScript() { updateConfigScript(scriptBuilder, "exit"); if (withClientCert) { - // Client certificate authority configuration - updateConfigScript(scriptBuilder, "authentication"); - updateConfigScript(scriptBuilder, "create client-certificate-authority RootCA"); - updateConfigScript(scriptBuilder, "certificate file rootCA.crt"); - updateConfigScript(scriptBuilder, "show client-certificate-authority ca-name *"); - updateConfigScript(scriptBuilder, "end"); + if (clientCertificateAuthority) { + updateConfigScript(scriptBuilder, "configure"); + // Client certificate authority configuration + updateConfigScript(scriptBuilder, "authentication"); + updateConfigScript(scriptBuilder, "create client-certificate-authority RootCA"); + updateConfigScript(scriptBuilder, "certificate file rootCA.crt"); + updateConfigScript(scriptBuilder, "show client-certificate-authority ca-name *"); + updateConfigScript(scriptBuilder, "end"); + + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + // Enable client certificate authentication + updateConfigScript(scriptBuilder, "authentication client-certificate"); + updateConfigScript(scriptBuilder, "allow-api-provided-username"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + } else { + updateConfigScript(scriptBuilder, "configure"); + // Domain certificate authority configuration + updateConfigScript(scriptBuilder, "ssl"); + updateConfigScript(scriptBuilder, "create domain-certificate-authority RootCA"); + updateConfigScript(scriptBuilder, "certificate file rootCA.crt"); + updateConfigScript(scriptBuilder, "show domain-certificate-authority ca-name *"); + updateConfigScript(scriptBuilder, "end"); + } // Server certificates configuration updateConfigScript(scriptBuilder, "configure"); @@ -190,21 +225,49 @@ private Transferable createConfigurationScript() { updateConfigScript(scriptBuilder, "server-certificate solace.pem"); updateConfigScript(scriptBuilder, "cipher-suite msg-backbone name AES128-SHA"); updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "end"); + } + + if (withOAuth) { + // Configure OAuth authentication + updateConfigScript(scriptBuilder, "configure"); updateConfigScript(scriptBuilder, "message-vpn " + vpn); - // Enable client certificate authentication - updateConfigScript(scriptBuilder, "authentication client-certificate"); - updateConfigScript(scriptBuilder, "allow-api-provided-username"); + updateConfigScript(scriptBuilder, "authentication oauth"); updateConfigScript(scriptBuilder, "no shutdown"); updateConfigScript(scriptBuilder, "end"); } else { // Configure VPN Basic authentication + updateConfigScript(scriptBuilder, "configure"); updateConfigScript(scriptBuilder, "message-vpn " + vpn); updateConfigScript(scriptBuilder, "authentication basic auth-type internal"); updateConfigScript(scriptBuilder, "no shutdown"); updateConfigScript(scriptBuilder, "end"); } + // create OAuth profile + updateConfigScript(scriptBuilder, "configure"); + updateConfigScript(scriptBuilder, "message-vpn " + vpn); + updateConfigScript(scriptBuilder, "authentication"); + updateConfigScript(scriptBuilder, "create oauth profile integration_test_oauth_profile"); + updateConfigScript(scriptBuilder, "authorization-groups-claim-name \"\" "); + updateConfigScript(scriptBuilder, "oauth-role resource-server"); + updateConfigScript(scriptBuilder, "issuer https://localhost:7778/realms/solace"); + updateConfigScript(scriptBuilder, "disconnect-on-token-expiration"); + updateConfigScript(scriptBuilder, "endpoints"); + updateConfigScript(scriptBuilder, "discovery https://keycloak:8443/realms/solace/.well-known/openid-configuration"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "username-claim-name sub"); + updateConfigScript(scriptBuilder, "resource-server"); + updateConfigScript(scriptBuilder, "required-audience pubsub+aud"); + updateConfigScript(scriptBuilder, "no validate-type"); + updateConfigScript(scriptBuilder, "required-type JWT"); + updateConfigScript(scriptBuilder, "exit"); + updateConfigScript(scriptBuilder, "client-id solace"); + updateConfigScript(scriptBuilder, "client-secret solace-secret"); + updateConfigScript(scriptBuilder, "no shutdown"); + updateConfigScript(scriptBuilder, "end"); + if (!publishTopicsConfiguration.isEmpty() || !subscribeTopicsConfiguration.isEmpty()) { // Enable services updateConfigScript(scriptBuilder, "configure"); @@ -375,11 +438,21 @@ public SolaceContainer withVpn(String vpn) { * @param caFile Certified Authority ceritificate * @return This container. */ - public SolaceContainer withClientCert(final MountableFile certFile, final MountableFile caFile) { + public SolaceContainer withClientCert(final MountableFile certFile, final MountableFile caFile, + boolean clientCertificateAuthority) { this.withClientCert = true; + this.clientCertificateAuthority = clientCertificateAuthority; return withCopyFileToContainer(certFile, "/tmp/solace.pem").withCopyFileToContainer(caFile, "/tmp/rootCA.crt"); } + /** + * Sets OAuth authentication + */ + public SolaceContainer withOAuth() { + this.withOAuth = true; + return this; + } + /** * Configured VPN * diff --git a/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/tracing/TracingPropogationTest.java b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/tracing/TracingPropogationTest.java new file mode 100644 index 0000000..c119aa7 --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/tracing/TracingPropogationTest.java @@ -0,0 +1,256 @@ +package com.solace.quarkus.messaging.tracing; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.solace.messaging.publisher.OutboundMessage; +import com.solace.messaging.publisher.PersistentMessagePublisher; +import com.solace.messaging.receiver.InboundMessage; +import com.solace.messaging.receiver.PersistentMessageReceiver; +import com.solace.messaging.resources.Queue; +import com.solace.messaging.resources.Topic; +import com.solace.messaging.resources.TopicSubscription; +import com.solace.quarkus.messaging.base.WeldTestBase; +import com.solace.quarkus.messaging.incoming.SolaceInboundMetadata; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.smallrye.mutiny.Multi; +import io.smallrye.reactive.messaging.test.common.config.MapBasedConfig; + +public class TracingPropogationTest extends WeldTestBase { + private SdkTracerProvider tracerProvider; + private InMemorySpanExporter spanExporter; + + @BeforeEach + public void setup() { + GlobalOpenTelemetry.resetForTest(); + + spanExporter = InMemorySpanExporter.create(); + SpanProcessor spanProcessor = SimpleSpanProcessor.create(spanExporter); + + tracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(spanProcessor) + .setSampler(Sampler.alwaysOn()) + .build(); + + OpenTelemetrySdk.builder() + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .setTracerProvider(tracerProvider) + .buildAndRegisterGlobal(); + } + + @AfterAll + static void shutdown() { + GlobalOpenTelemetry.resetForTest(); + } + + @Test + void consumer() { + MapBasedConfig config = commonConfig() + .with("mp.messaging.incoming.in.connector", "quarkus-solace") + .with("mp.messaging.incoming.in.client.tracing-enabled", "true") + .with("mp.messaging.incoming.in.consumer.queue.name", queue) + .with("mp.messaging.incoming.in.consumer.queue.add-additional-subscriptions", "true") + .with("mp.messaging.incoming.in.consumer.queue.missing-resource-creation-strategy", "create-on-start") + .with("mp.messaging.incoming.in.consumer.queue.subscriptions", "quarkus/integration/test/replay/messages"); + + // Run app that consumes messages + MyConsumer app = runApplication(config, MyConsumer.class); + + // Produce messages + PersistentMessagePublisher publisher = messagingService.createPersistentMessagePublisherBuilder() + .build() + .start(); + Topic tp = Topic.of("quarkus/integration/test/replay/messages"); + publisher.publish("1", tp); + publisher.publish("2", tp); + publisher.publish("3", tp); + publisher.publish("4", tp); + publisher.publish("5", tp); + + // Assert on published messages + await().untilAsserted(() -> assertThat(app.getReceived()).contains("1", "2", "3", "4", "5")); + + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(5, spans.size()); + + assertEquals(5, spans.stream().map(SpanData::getTraceId).collect(Collectors.toSet()).size()); + + SpanData span = spans.get(0); + assertEquals(SpanKind.CONSUMER, span.getKind()); + }); + } + + @Test + void publisher() { + MapBasedConfig config = commonConfig() + .with("mp.messaging.outgoing.out.connector", "quarkus-solace") + .with("mp.messaging.outgoing.out.client.tracing-enabled", "true") + .with("mp.messaging.outgoing.out.producer.topic", topic); + + List expected = new CopyOnWriteArrayList<>(); + + // Start listening first + PersistentMessageReceiver receiver = messagingService.createPersistentMessageReceiverBuilder() + .withSubscriptions(TopicSubscription.of(topic)) + .build(Queue.nonDurableExclusiveQueue()); + receiver.receiveAsync(inboundMessage -> expected.add(inboundMessage.getPayloadAsString())); + receiver.start(); + + // Run app that publish messages + MyApp app = runApplication(config, MyApp.class); + // Assert on published messages + await().untilAsserted(() -> assertThat(app.getAcked()).contains("1", "2", "3", "4", "5")); + // Assert on received messages + await().untilAsserted(() -> assertThat(expected).contains("1", "2", "3", "4", "5")); + + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(5, spans.size()); + + assertEquals(5, spans.stream().map(SpanData::getTraceId).collect(Collectors.toSet()).size()); + + SpanData span = spans.get(0); + assertEquals(SpanKind.PRODUCER, span.getKind()); + }); + } + + @Test + void processor() { + String processedTopic = topic + "/processed"; + MapBasedConfig config = commonConfig() + .with("mp.messaging.incoming.in.connector", "quarkus-solace") + .with("mp.messaging.incoming.in.client.tracing-enabled", "true") + .with("mp.messaging.incoming.in.consumer.queue.name", queue) + .with("mp.messaging.incoming.in.consumer.queue.add-additional-subscriptions", "true") + .with("mp.messaging.incoming.in.consumer.queue.missing-resource-creation-strategy", "create-on-start") + .with("mp.messaging.incoming.in.consumer.queue.subscriptions", topic) + .with("mp.messaging.outgoing.out.connector", "quarkus-solace") + .with("mp.messaging.outgoing.out.client.tracing-enabled", "true") + .with("mp.messaging.outgoing.out.producer.topic", processedTopic); + + // Run app that processes messages + MyProcessor app = runApplication(config, MyProcessor.class); + + List expected = new CopyOnWriteArrayList<>(); + + // Start listening processed messages + PersistentMessageReceiver receiver = messagingService.createPersistentMessageReceiverBuilder() + .withSubscriptions(TopicSubscription.of(processedTopic)) + .build(Queue.nonDurableExclusiveQueue()); + receiver.receiveAsync(inboundMessage -> expected.add(inboundMessage.getPayloadAsString())); + receiver.start(); + + // Produce messages + PersistentMessagePublisher publisher = messagingService.createPersistentMessagePublisherBuilder() + .build() + .start(); + Topic tp = Topic.of(topic); + publisher.publish("1", tp); + publisher.publish("2", tp); + publisher.publish("3", tp); + publisher.publish("4", tp); + publisher.publish("5", tp); + + // Assert on received messages + await().untilAsserted(() -> assertThat(app.getReceived()).contains("1", "2", "3", "4", "5")); + // Assert on processed messages + await().untilAsserted(() -> assertThat(expected).contains("1", "2", "3", "4", "5")); + + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); + + assertEquals(5, spans.stream().map(SpanData::getTraceId).collect(Collectors.toSet()).size()); + + SpanData span = spans.get(0); + assertEquals(SpanKind.CONSUMER, span.getKind()); + + span = spans.get(5); + assertEquals(SpanKind.PRODUCER, span.getKind()); + }); + } + + @ApplicationScoped + static class MyConsumer { + private final List received = new CopyOnWriteArrayList<>(); + + @Incoming("in") + CompletionStage in(Message msg) { + SolaceInboundMetadata solaceInboundMetadata = msg.getMetadata(SolaceInboundMetadata.class).orElseThrow(); + received.add(solaceInboundMetadata.getPayloadAsString()); + return msg.ack(); + } + + public List getReceived() { + return received; + } + } + + @ApplicationScoped + static class MyApp { + private final List acked = new CopyOnWriteArrayList<>(); + + @Outgoing("out") + Multi> out() { + + return Multi.createFrom().items("1", "2", "3", "4", "5") + .map(payload -> Message.of(payload).withAck(() -> { + acked.add(payload); + return CompletableFuture.completedFuture(null); + })); + } + + public List getAcked() { + return acked; + } + } + + @ApplicationScoped + static class MyProcessor { + private final List received = new CopyOnWriteArrayList<>(); + + @Incoming("in") + @Outgoing("out") + OutboundMessage in(InboundMessage msg) { + String payload = msg.getPayloadAsString(); + received.add(payload); + return messagingService.messageBuilder().build(payload); + } + + public List getReceived() { + return received; + } + } +} diff --git a/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.crt b/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.crt new file mode 100644 index 0000000..1a6238c --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIJAIHrWDyBHPjWMA0GCSqGSIb3DQEBCwUAMIGJMQswCQYD +VQQGEwJDQTEMMAoGA1UECAwDTi9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoM +BlNvbGFjZTEgMB4GCSqGSIb3DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNV +BAsMCm15b3JnLXVuaXQxEzARBgNVBAMMCm15a2V5Y2xvYWswIBcNMjQwMzExMDky +NjIxWhgPMjEyNDAyMTYwOTI2MjFaMIGJMQswCQYDVQQGEwJDQTEMMAoGA1UECAwD +Ti9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoMBlNvbGFjZTEgMB4GCSqGSIb3 +DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNVBAsMCm15b3JnLXVuaXQxEzAR +BgNVBAMMCm15a2V5Y2xvYWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQC6zmPMXjha+0jor9iVEQhu2WUKKRKubTS3eAY72ez9IA+ml1HXh2rqVIPv8F5v +GasxUEbHzDgBrsuQyROwpXei69EuDWY6g3w7ifu6rkx+xAB9ipv9DjUXc3sH37nE +2xK5HnB7CO8O/HoSBbxXw0sJ7SYyDaYujn17fOJyr4Rljqfkj20ok7HzmUH/Wph1 +9WZ9ShfkaRw0SVIBeNc+3i/KorERgLfnBydyd9fDsrnIjSJxMWOA2PMDsA8lzvEk +lObn5dlLGIN5nMuNBpefnLQ0y5aA5Ikpzn1I3bwzPqQJt4v2fXem2NPSuMOOa042 +oJFZQD9GJRsmC5Kf7o7oKejjAgMBAAGjLjAsMCoGA1UdEQQjMCGCCm15a2V5Y2xv +YWuCCWxvY2FsaG9zdIIIa2V5Y2xvYWswDQYJKoZIhvcNAQELBQADggEBALGDHoxV +KGxZ/O81fOPPg8YNrkPkAvZu4kEEKoS/0bW+npzCRDZu9nULxwRDZJdfU7mNqjHx +RbLMk/rwI2F1aBnBXHMV22wqW/3d+0B6JqnJFz+9Mb0453f8+Dn6ZpxgMYPh2rn/ +80wW4g8wuofcdcHxqTd/fNbzWN8kRVsjqIysXjD4w2zb9q/yFwRq0+WeOhc6FJiz +PnLINignHS/zqslCPD53T+Crqcx8vLrLSoMucCXCkgJeX/joLYmjhdgG3ewIOz/C ++BWWOT73+mfe8e87rEX4e3hpJ+0ZBkP25u+6Q02OYU9A1PtBLDyZqeEH/WiIrJNO +UaS8kTKbscA5voc= +-----END CERTIFICATE----- diff --git a/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.key b/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.key new file mode 100644 index 0000000..762b81c --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC6zmPMXjha+0jo +r9iVEQhu2WUKKRKubTS3eAY72ez9IA+ml1HXh2rqVIPv8F5vGasxUEbHzDgBrsuQ +yROwpXei69EuDWY6g3w7ifu6rkx+xAB9ipv9DjUXc3sH37nE2xK5HnB7CO8O/HoS +BbxXw0sJ7SYyDaYujn17fOJyr4Rljqfkj20ok7HzmUH/Wph19WZ9ShfkaRw0SVIB +eNc+3i/KorERgLfnBydyd9fDsrnIjSJxMWOA2PMDsA8lzvEklObn5dlLGIN5nMuN +BpefnLQ0y5aA5Ikpzn1I3bwzPqQJt4v2fXem2NPSuMOOa042oJFZQD9GJRsmC5Kf +7o7oKejjAgMBAAECggEBAKvWAI1UumhOsFGCuCrfQS+egEgpYgrbX9vI54sUuuBZ +Jqxqk5k78whc+AS1ylhOd2BkZMeTPo2luZGUta0PeI6Ad6nyH3CB1Lx7//hILwuI +xp606yqLcCEDVE/458yCbKWmr3ctz6Gsc6myZv8gIR2fbTwrvAslfZ4jUbaHZ7V2 +QNcRrcqXrHJjlEZ6VcrFfKwr3Wp3mMvb3ogpZOlgAKxGtSvXNDahq4pUMMn/wD3k +iPSewYz2K1UR4Hz5DV7+tnNk2lXn/gEBpmW3Z5xk5kNpNhtE9ZFOnkFwSGGGpWGy +tlm3Cg+Mc/JGnQ06ZiNth9WUpjc3yMUTRb45ZwL+RKkCgYEA8n+KCbKfoocGnkcU +0+qHiEBFnAN0lWD/66otS07QsZoldxadOAQnZUxiR8Xxnm4ynUCYZvqRoBJisGV8 +30N6TZP/92qrNLq5A2EpAq/VbnL9nVNzxRP+nPRDikdcl5C/4XA0QZoIfIW535Iy +OdNJEAVWucQwhARyYbnpslFu+v8CgYEAxTUKvEZqhkqwnfHAIR8+h9HB2phsy3fP +ZFyr5jky7x+wO8YjGV42yiLtDOauSiNamxjLADy2qEmjDstsQatPFoXAI//386q1 +KG3qmI0fJHaUhBzAYUSjKIZgoVmr+n3Mr+cuTVKV+IhqHPpnT0d5NWD0k3U7oUoZ +7suiiHbKhh0CgYEAqXP+HbC4ZHY+ZbP+Fee5NbjT66VufkP+EcwlQo6cvr6cl48x +5cbhUKQDuWvU34TZ0ZEl7jACOv0eAW2pyMn6WOOm5lmfsYUZbAclBT+hwUCRgLKk +H39NWJhH6gTb6v23V+10VrMwYvN/Y39hoY7Ha26Pn9g8nsQMucWUTIsjJjkCgYAw +6zxzgcAw+dwgAfUYAkkfpe/BiugJ/PlsOvTFUlEJMkIkQb05ML7Em69T8PExIN37 +9UV+FJF243VYWSvMinM+8gS8qWVXg3QWyFVWbENaZzPmJb+vITib9+GGhNj9dTFO +PTmmIqNjGGvCLndsGh2+GQPyhDU7iEcwjkEOOvF4HQKBgQDNcnw0m8kZ+olzrFJ8 +QZXnxPFAU2q2yu23UbXtWuZ9Ld/5ZE4iEd1Pu8ZDsYonZ+hKmtmo3U4f8cFg/pYM +IxX2uNSiioqKMfLhIorkDoHm0+iZZRHGooQbv06R2qe7rPIIoFbSgU3M+CKgxkPN +VGIsOTBuOmJTWCy0hcPW/8n93w== +-----END PRIVATE KEY----- diff --git a/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak/realms/solace-realm.json b/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak/realms/solace-realm.json new file mode 100644 index 0000000..e7551c4 --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak/realms/solace-realm.json @@ -0,0 +1,2266 @@ +{ + "id": "solace", + "realm": "solace", + "displayName": "Keycloak", + "displayNameHtml": "
Keycloak
", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 86400, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 600, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "6af43dbd-b06e-47aa-967c-d7b0c98849e9", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "6d4367b1-0d06-4670-ae11-a6ec3c629f54", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "ee59e325-8dfb-426f-a233-1992768a559d", + "name": "default-roles-master", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "981a4159-7622-437d-9602-9c52d1549fa3", + "name": "admin", + "description": "${role_admin}", + "composite": true, + "composites": { + "realm": [ + "create-realm" + ], + "client": { + "master-realm": [ + "impersonation", + "manage-clients", + "view-users", + "query-groups", + "query-users", + "manage-realm", + "view-authorization", + "view-events", + "manage-users", + "query-clients", + "manage-authorization", + "create-client", + "query-realms", + "view-realm", + "view-clients", + "view-identity-providers", + "manage-identity-providers", + "manage-events" + ] + } + }, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "ba195745-8e0c-481b-bd20-cb99c07a3fe1", + "name": "create-realm", + "description": "${role_create-realm}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + } + ], + "client": { + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "22ab11ee-f680-4809-a49d-ad319c8f5ce1", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "5f9b7d7d-f097-4dd5-83b3-3bf602532df6", + "attributes": {} + } + ], + "master-realm": [ + { + "id": "1ec2babd-b6d7-4247-be98-9f3df6c35ab8", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "e602b75a-fe5b-4250-b516-4933ac013967", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "55a51d1e-c22d-412f-bbf9-eda59fb4aaa7", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "master-realm": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "2854dbc7-b758-4178-89d2-856863cf78d2", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "ab3321f5-cf52-4058-b1d7-3be25e0348f0", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "c055a88d-cddc-49e7-ac69-84d561df56d8", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "d8f301c6-7960-4f22-a10a-cc25a7295f6f", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "98236c38-bfd1-44de-b6cf-29a6496a4f64", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "4a659aeb-76f0-48bb-829d-d3295b0d9ca8", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "aee3ef48-e571-4719-91e7-11b5353a31a1", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "4c52b6f8-bb47-47f3-b836-34c8409886a2", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "287fbb84-c5dc-4262-b01d-0c1cfe12f6bf", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "aba6fe78-f504-46e7-94c0-cd096f6ab4bb", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "f2c0273b-6e38-480d-92e5-76a42e9e9071", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "ed1395c4-5c99-4b85-b985-301a65230c04", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "master-realm": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "c59fb7e1-7bfe-49ca-a953-6a437718d1e2", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "60d63bd9-f508-4eca-a2d1-9ab3542c07b8", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + }, + { + "id": "22d12dc8-90a5-4180-b660-438760307405", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "attributes": {} + } + ], + "account": [ + { + "id": "57444ccb-f476-4619-8527-336f3aee62f9", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "7394f404-2a1a-4670-b7c3-ad3e7b5cf09f", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "ec843402-f7d0-4785-bc3f-d73abe51b0e9", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "0386d08c-374b-400c-ac03-6d06b683af48", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "1596de1b-b78e-49a3-86b3-f2b7bcbb9668", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "85fee210-cfa2-477f-938a-cffc34982adb", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + }, + { + "id": "bf342f28-b040-46ce-94a3-f76cc72df837", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "attributes": {} + } + ], + "solace": [] + } + }, + "groups": [], + "defaultRole": { + "id": "ee59e325-8dfb-426f-a233-1992768a559d", + "name": "default-roles-master", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "master" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "users": [ + { + "id": "6c67c64e-0c0a-480e-a9d5-a93dd8b60151", + "createdTimestamp": 1709310343758, + "username": "service-account-solace", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "solace", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-master" + ], + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "d33fcbf1-a7f4-4ee1-89ac-eb62efd252c9", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/master/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/master/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a85bf90b-bf07-4b48-ab42-96e5ff0a0186", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/master/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/master/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "58780fe6-7bc3-439c-b8fc-ca8c5269336b", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "00ee6480-fb8e-4e37-a8f2-10befdaf9b43", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "5f9b7d7d-f097-4dd5-83b3-3bf602532df6", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2846196f-da15-4d81-a58c-5ec3ba214f4e", + "clientId": "master-realm", + "name": "master Realm", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "edfcd34d-a719-44c0-af83-71cce3f9924c", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/master/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/master/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "0bdd7724-8687-4144-bba9-e88fc204d306", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "3c699672-dde8-41c6-838d-e3dde45325c9", + "clientId": "solace", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "solace-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "id.token.as.detached.signature": "false", + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "exclude.session.state.from.auth.response": "false", + "oidc.ciba.grant.enabled": "false", + "saml.artifact.binding": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "require.pushed.authorization.requests": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "d74bada9-c17c-4fda-8a5a-37ecfda51bc9", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "1a2837a2-c688-4ea1-90ff-9cd355677ec1", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "cab8b971-23ef-4790-9b41-1b7c7daaf76d", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "solace_scope", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "26df2d1b-89c9-4b5f-aaa9-bb9216119d1b", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "2ac54554-f68a-4657-b9b1-1bb0c133a0b7", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "9d57e45d-46e0-4aa8-87a1-105143b06525", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "07318111-38b8-4d3c-9f4e-70118320a4ab", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "96bff8f0-0f4c-4331-902b-d44682cd609d", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "302921eb-6ebe-439b-bc9d-7f48c1fd2cf7", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "49e4cfb0-70a5-459c-97e7-8cca7b82e7a3", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "7c4e36e0-0e53-4e1b-9cbf-60b0f283f462", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "4b9188f6-1034-45fe-923c-f43aca1c3416", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "c24e819f-b1ee-41f5-99c8-e83cbfe0ca60", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "3dbe0889-8886-47d8-ad4e-2cf86708809e", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "86d29826-2aa9-4861-9c7c-7fd82a4c7510", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "7e869280-544e-434d-a912-2daaba46e85d", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "a17661b4-063a-44c5-8731-f9ba2521fb3c", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "2ad197bc-9d30-4e1f-9f58-4c97eb840101", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "c426579a-07bb-4c91-a62e-da68bd90a6bd", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "686b368a-82b9-47dc-aaa1-af0c61d6c7a0", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "25f27b4a-8e21-4ca9-85b3-fc6bb1010cda", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "6ca67292-8c98-4386-ac0e-0a6f8c773f5d", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "aa060031-1422-48b5-a40b-aed592238aeb", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "971e264c-64f8-4cbe-bc92-ee76d491d9b6", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "2d749ebf-d125-4247-b824-e8d7fa3b67ba", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "695b889a-bf3a-4af2-b960-a6de3e110dda", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "ea54b479-5698-4f9c-93c0-e938f2010ca1", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "7d543137-b00b-406e-bbd5-140f11d8dc74", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "5c6301d1-7e0c-48fb-a98f-c63d0efa1149", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "078ad50e-8881-4559-b1c3-12889a0a94b1", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "1f60eeca-c70b-4a54-80d4-7e20998a33cb", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "87e64829-4247-417b-acf0-6f4bc692697f", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "81f9e655-9af0-46b2-abcf-3d0744f884b0", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "660d8c7f-dbcf-4df5-bf6a-78b0b29a0979", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "10c03ddc-0781-46b2-b3e1-dc0bd7c9bc19", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "5ef1ebc2-9bd6-45d0-9c76-9ebbc6cd475d", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "495f9e6a-794b-4c6c-9ea3-d435bf7eea45", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "d26374c0-bec6-448e-88f0-c5090b74994e", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "be3ff804-ecc5-4aaf-8373-51f33fb1285b", + "name": "solace_scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ef768530-e6aa-4846-bf75-98ac60672179", + "name": "pubsub+", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "false", + "access.token.claim": "true", + "included.custom.audience": "pubsub+aud" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "email", + "profile", + "role_list", + "roles", + "web-origins" + ], + "defaultOptionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "d8c7889b-d487-4c17-92c8-c7243f29f2a9", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "7ccc2338-62aa-4271-a9e4-7656549caffd", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "620c2c71-e45f-4791-99fc-1197fdccc9a5", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "c63236fa-cf74-4838-8179-77ec9fa3ef05", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "saml-user-property-mapper" + ] + } + }, + { + "id": "bdbaae20-fcf0-4461-a219-7e1d04a37caa", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "c2416358-7086-4a39-a940-973caeea8191", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "2c832c64-be94-4d2d-9676-4532ce66fcf6", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "09dea1c3-d309-4862-8968-8fcde12d349e", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "70f4173c-d247-46f4-a5e9-747dfa92a6ea", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "b41b27c4-da26-4394-93f0-5421cc3a6e04", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "4add83dd-2df1-4513-a172-53c26b34d1d8", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "950e2e88-dbc1-40f1-a487-50a4514cb2b7", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "9597f99b-40ce-483c-bdac-619c47ef3a3b", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "168b554b-08e0-41f2-8c47-578a4501b171", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "556f5237-fe8e-4f5a-a9f4-f16a1bca041e", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "261852d6-efbd-473f-9209-c7a48aa29504", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "9b81c87d-3be3-4b92-b340-32e7c9081df4", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "826ee75a-84bc-48f8-a718-2451506015d4", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c33dda76-da97-41c8-8e04-f3b9234999fb", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Account verification options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5d2646b6-9d6a-46a3-9a70-bd700b9dd0de", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "b0128d0e-b05f-46fd-9089-1c1614181495", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "7f23c06a-e8d8-4c35-8b8e-83417a2bcb8d", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "40e7ec79-f8cf-4da5-baa1-0688b929fa1d", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "9365facb-be45-4aba-9587-5390f3040272", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "855e65a7-8ff7-42a1-9987-adb605d95744", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "e050340f-a8be-4c85-b94e-98f91acf7ee3", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "fd9238b6-adf9-4713-8913-8961d3049825", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "User creation or linking", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5d89ebe0-fccb-4f22-87ed-ec7e3782ed5e", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "dcb3cd09-48dd-4cc6-b935-3d0b905875ea", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Authentication Options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "1e23398f-2c1a-4791-86b2-ea62427d6605", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f5981653-3268-407d-9692-b4c822520655", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "cd82dd4c-ffed-4eed-91e0-93ef27ea0180", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "3fe4c594-8027-48d1-a9a6-52200251c80f", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "56559a9a-f2e3-4d33-9adf-836cc14efb5e", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "ec3571ad-ea3a-4148-b1a9-f5c9a6079c21", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "600", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5" + }, + "keycloakVersion": "16.1.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/quarkus-solace-messaging-connector/runtime/src/test/resources/solace.pem b/quarkus-solace-messaging-connector/runtime/src/test/resources/solace.pem new file mode 100644 index 0000000..060741a --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/resources/solace.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIDvzCCAqegAwIBAgIJAKWD5vMlbhRKMA0GCSqGSIb3DQEBCwUAMIGHMQswCQYD +VQQGEwJDQTEMMAoGA1UECAwDTi9BMQ8wDQYDVQQHDAZPdHRhd2ExDzANBgNVBAoM +BlNvbGFjZTEgMB4GCSqGSIb3DQEJARYRbXllbWFpbEBlbWFpbC5jb20xEzARBgNV +BAsMCm15b3JnLXVuaXQxETAPBgNVBAMMCG15cHVic3ViMCAXDTI0MDMxMTA5MjY1 +NVoYDzIxMjQwMjE2MDkyNjU1WjCBhzELMAkGA1UEBhMCQ0ExDDAKBgNVBAgMA04v +QTEPMA0GA1UEBwwGT3R0YXdhMQ8wDQYDVQQKDAZTb2xhY2UxIDAeBgkqhkiG9w0B +CQEWEW15ZW1haWxAZW1haWwuY29tMRMwEQYDVQQLDApteW9yZy11bml0MREwDwYD +VQQDDAhteXB1YnN1YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOpw +2cA6+gvALPEFbTuxdJDWJBI0yewOA0wcjYa3T7zO/akyD+EDcxKZPXXfOdOKWMm1 +5ByMT7Qi+1h0uLPR5tSDP2Ya02Qw2d8bSQWLuNlt2p/Hb698A+y1GFrNe2ggKLsc +aYmjoQa7+rEz1LHas88WGqHhXxCIH0zsb7LfsUM35AeXgdeRmoGlHqA9bJMGN5m7 +ktn93ejP1wfMlGn+eHeNVkM6+G6pjdYbn8WRF6jvky54QE0E4A6U94CZflbG9Efh +86MT1pJEtPzhgKriAt/x+tYqZeE1mvHk/L5eWmnik+KPjLoKXDnF2rAJeBJczjO/ +4hP8K6lUfZ9juqIRKB8CAwEAAaMqMCgwJgYDVR0RBB8wHYIIbXlwdWJzdWKCCWxv +Y2FsaG9zdIIGc29sYWNlMA0GCSqGSIb3DQEBCwUAA4IBAQDWgXDnGeaGThiaSCQJ +GtoH3GSsNtStY5faLIAICnnpjFUoPtZL1CDvhXUqm6GIBpF5Y1f++kSFSNoMJ4M7 +wev5w05jHecCJj2Aan0SBO0wjK0R1BUJkZg0HIa0tOylzeHn5WnR56XEPjL6kdxg +sz0QW+J5DCUX2SO/mRjtoQl3Dyxrtut8HwRbWkbe+kdgXh2FIk526PnVkDfoNusD +MRF5WoKM2uYkYCpmV+CAbuRMmJu0JI+HDatbtSXiTn/xosJlG6WYzBQ+4ZL+6PKF +I7LtP6eqIln09Ujetkp27tmPm2gRv6U2tp85AWZTVQ2cWH5py5XFroE2RR3V6j28 +xy6W +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqcNnAOvoLwCzx +BW07sXSQ1iQSNMnsDgNMHI2Gt0+8zv2pMg/hA3MSmT113znTiljJteQcjE+0IvtY +dLiz0ebUgz9mGtNkMNnfG0kFi7jZbdqfx2+vfAPstRhazXtoICi7HGmJo6EGu/qx +M9Sx2rPPFhqh4V8QiB9M7G+y37FDN+QHl4HXkZqBpR6gPWyTBjeZu5LZ/d3oz9cH +zJRp/nh3jVZDOvhuqY3WG5/FkReo75MueEBNBOAOlPeAmX5WxvRH4fOjE9aSRLT8 +4YCq4gLf8frWKmXhNZrx5Py+Xlpp4pPij4y6Clw5xdqwCXgSXM4zv+IT/CupVH2f +Y7qiESgfAgMBAAECggEAB64oxA5qkKX8Eu1Nlc4Ldo89YUdPcidHXl/1Fvu8ZgAV ++UwFjyaQx4Qzqj/k4hQ/MmR+E51ZIxqeR1iTkHiI6l9eXVb1o+uhx5haPQ9FwAHE +TsW21/XlHwUTxi3DJDchfnfA0VyF8vWHkfSTvDvg9iDQQItklOMQu3Fne2GuqfgD +tpTYz1qmiUOG2gS1P+0d/BE4HGtQyu2UPwymeJIVH+2dfWMRCUzLdqfZyLGIpSe8 +mJheyPSri/2XFsC/9fxuO3OkNMOwVDYIYOutECQr2i3qVo/0tPd0zqKB/wAV5GN0 +WcYYulgLy4YAJNAAW6+IeMcZWz9Mk0qjgRTZe9VFgQKBgQD78OQXb8zbSScmWqx1 +k0ffJzkWoJkJUnwOAtCLowvQbpi88RaCAhxQHsISXD7KJrfWE/y00QtY7omIq9Jc +WOJnmUpnrG7DqYRJEiz29hLHJSHa4U//onhhH4NQgtHwR6kX8s5rlN6DZ2jm5ctn +v64/5uxz5gdXIW6ixDNyl+f1wQKBgQDuN8gdtmjUN2lUBMEasbgn5W3WDVhJDUfX +QLvxQuMxJ7/YwRW+/sFgHyrCaBdcF7G/NcqsHblRoOwu/7jbyw0u23CY6WwRXv2j +fIZGt6Sqsl/7LL1klcMYbME5lgn7qhzHgUnVYLJysAwmXlEAgHtD+pqocdfB+pPY +RDGGjqlV3wKBgQDi9Ay05By5iXt//HyQ6dz7tBykOoXBtRFVmcl9kKIK4CYtRkzN +TtNshVi0K27QsfI3IggqZon/UdqJSKcWU2eYhalWHSomjiVBoeLpkaA2z0dhIkjr +ctNYQogLVd2Cwzsa/LpghVmxK81++pCyZCS3IfHtMdF49v/wFih2WUs2wQKBgEgp +s8B0eosXAhxGmGzKu3uyf7RhNIZktIebf5OVbJd+cBpsW3cRW2kP5/ceaz0lnF3N +IMlE89erhQCzzL8gYqz4IsLfqzIT8Yft+AtCJGrlQDgplHH9AC3M/DfCoOGQ5cj1 +/HTcJxKhC/0vgyBAy5aLOwCeA/sqOlFATzRw0RFHAoGBAIydEqgzBQW5i+19Filo +8mADdtQmwSIyRJ2L3nsMbdej+2wPVxQItRUVLMumfKmhffW2E3bC5y0j/rvOqxKa +igiyDlOoBEnonnYODhSNOwFRaRqqSWjjRKnoA517+XYtrlLtxxEQIATLPhXuF3b9 +i1RRMw3EhJL+BHQYIXm65A/q +-----END PRIVATE KEY-----