From 9b1725b13f97a01e937e444e6c2ea33b8313d3e2 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:19:11 +0530 Subject: [PATCH 01/15] Implemented Quarkus Client Side Tracing --- .../runtime/pom.xml | 5 + .../quarkus/messaging/SolaceConnector.java | 1 + .../incoming/SolaceIncomingChannel.java | 44 ++- .../outgoing/SolaceOutgoingChannel.java | 38 ++- .../tracing/SolaceAttributeExtractor.java | 72 +++++ .../SolaceOpenTelemetryInstrumenter.java | 60 ++++ .../messaging/tracing/SolaceTrace.java | 101 +++++++ .../tracing/SolaceTraceTextMapGetter.java | 27 ++ .../tracing/SolaceTraceTextMapSetter.java | 17 ++ .../tracing/TracingPropogationTest.java | 257 ++++++++++++++++++ 10 files changed, 612 insertions(+), 10 deletions(-) create mode 100644 quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceAttributeExtractor.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceOpenTelemetryInstrumenter.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTrace.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapGetter.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/tracing/SolaceTraceTextMapSetter.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/tracing/TracingPropogationTest.java diff --git a/quarkus-solace-messaging-connector/runtime/pom.xml b/quarkus-solace-messaging-connector/runtime/pom.xml index 4ba4c00..9a286f0 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 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..6f61982 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 @@ -22,6 +22,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 +30,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; @@ -51,6 +54,7 @@ public class SolaceIncomingChannel implements ReceiverActivationPassivationConfi 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 +111,47 @@ 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(); + 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(consumedMessage.getProperties()) + .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..df8ad1f 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; @@ -44,6 +46,7 @@ public class SolaceOutgoingChannel private final long gracefulShutdownWaitTimeout; private final AtomicBoolean alive = new AtomicBoolean(false); 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..18366d9 --- /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 "solace"; + } + + @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/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..e7adf57 --- /dev/null +++ b/quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/tracing/TracingPropogationTest.java @@ -0,0 +1,257 @@ +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 com.solace.messaging.publisher.OutboundMessage; +import com.solace.messaging.receiver.InboundMessage; +import com.solace.messaging.receiver.PersistentMessageReceiver; +import com.solace.messaging.resources.Queue; +import com.solace.messaging.resources.TopicSubscription; +import com.solace.quarkus.messaging.SolaceProcessorTest; +import com.solace.quarkus.messaging.SolacePublisherTest; +import io.smallrye.mutiny.Multi; +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.PersistentMessagePublisher; +import com.solace.messaging.resources.Topic; +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.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.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; + } + } +} From 3b94300ec689ff175ebadb5c21c116a5b206d557 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:20:17 +0530 Subject: [PATCH 02/15] Code formatting --- .../messaging/tracing/TracingPropogationTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 index e7adf57..9455b73 100644 --- 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 @@ -10,14 +10,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; -import com.solace.messaging.publisher.OutboundMessage; -import com.solace.messaging.receiver.InboundMessage; -import com.solace.messaging.receiver.PersistentMessageReceiver; -import com.solace.messaging.resources.Queue; -import com.solace.messaging.resources.TopicSubscription; -import com.solace.quarkus.messaging.SolaceProcessorTest; -import com.solace.quarkus.messaging.SolacePublisherTest; -import io.smallrye.mutiny.Multi; import jakarta.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.reactive.messaging.Incoming; @@ -27,8 +19,13 @@ 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; @@ -44,6 +41,7 @@ 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 { From fbf01560dd2786be99c5ce3a62d8b3516a162b51 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:45:26 +0530 Subject: [PATCH 03/15] changed system name in tracing --- .../quarkus/messaging/tracing/SolaceAttributeExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 18366d9..0b4428c 100644 --- 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 @@ -30,7 +30,7 @@ public MessagingAttributesGetter getMessagingAttributesGetter private static final class SolaceMessagingAttributesGetter implements MessagingAttributesGetter { @Override public String getSystem(final SolaceTrace solaceTrace) { - return "solace"; + return "SolacePubSub+"; } @Override From 843c7fb7fde54ddba3014fd98470ba93bafa90d0 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Tue, 20 Feb 2024 18:14:36 +0530 Subject: [PATCH 04/15] Configured tracing in test broker and added user properties --- .../messaging/incoming/SolaceIncomingChannel.java | 14 ++++++++++---- .../quarkus/messaging/base/SolaceContainer.java | 10 ++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) 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 6f61982..7f68298 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; @@ -124,6 +122,14 @@ public SolaceIncomingChannel(Vertx vertx, SolaceConnectorIncomingConfiguration i 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()) @@ -137,7 +143,7 @@ public SolaceIncomingChannel(Vertx vertx, SolaceConnectorIncomingConfiguration i SolaceConstants.MessageUserPropertyConstants.QUEUE_PARTITION_KEY) : null) .withPayloadSize(Long.valueOf(consumedMessage.getPayloadAsBytes().length)) - .withProperties(consumedMessage.getProperties()) + .withProperties(messageProperties) .build(); return solaceOpenTelemetryInstrumenter.traceIncoming(message, solaceTrace, true); }); 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 8beae7e..404fd65 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 @@ -96,6 +96,16 @@ 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"); + // create replay log updateConfigScript(scriptBuilder, "message-spool message-vpn default"); updateConfigScript(scriptBuilder, "create replay-log integration-test-replay-log"); From f511069e4db97bb306fbc250a6776c9c14c5859e Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:05:52 +0530 Subject: [PATCH 05/15] OAuth implementation --- quarkus-solace-client/deployment/pom.xml | 4 + .../quarkus/deployment/SolaceProcessor.java | 15 +- quarkus-solace-client/runtime/pom.xml | 5 +- .../solace/quarkus/runtime/OidcProvider.java | 59 + .../quarkus/runtime/SolaceRecorder.java | 15 +- .../runtime/pom.xml | 7 + .../incoming/SolaceIncomingChannel.java | 2 +- .../quarkus/messaging/SolaceOAuthTest.java | 172 ++ .../messaging/base/KeyCloakContainer.java | 102 + .../messaging/base/SolaceBrokerExtension.java | 6 +- .../messaging/base/SolaceContainer.java | 83 +- .../runtime/src/test/resources/keycloak.crt | 23 + .../runtime/src/test/resources/keycloak.key | 28 + .../keycloak/realms/solace-realm.json | 2266 +++++++++++++++++ .../runtime/src/test/resources/solace.pem | 51 + 15 files changed, 2812 insertions(+), 26 deletions(-) create mode 100644 quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/OidcProvider.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/SolaceOAuthTest.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/test/java/com/solace/quarkus/messaging/base/KeyCloakContainer.java create mode 100644 quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.crt create mode 100644 quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak.key create mode 100644 quarkus-solace-messaging-connector/runtime/src/test/resources/keycloak/realms/solace-realm.json create mode 100644 quarkus-solace-messaging-connector/runtime/src/test/resources/solace.pem diff --git a/quarkus-solace-client/deployment/pom.xml b/quarkus-solace-client/deployment/pom.xml index ba6846b..0d6971d 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 3645f90..cedad1b 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..a3a08bf --- /dev/null +++ b/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/OidcProvider.java @@ -0,0 +1,59 @@ +package com.solace.quarkus.runtime; + +import java.time.Duration; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import com.solace.messaging.MessagingService; +import com.solace.messaging.config.SolaceProperties; + +import io.quarkus.oidc.client.OidcClient; +import io.quarkus.oidc.client.Tokens; +import io.quarkus.runtime.StartupEvent; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.infrastructure.Infrastructure; + +@ApplicationScoped +public class OidcProvider { + + @ConfigProperty(name = "quarkus.solace.oidc.refresh.interval", defaultValue = "60s") + Duration duration; + + @Inject + OidcClient client; + + private volatile Tokens lastToken; + private MessagingService service; + + Tokens getToken() { + Tokens firstToken = client.getTokens().await().indefinitely(); + lastToken = firstToken; + return firstToken; + } + + void init(MessagingService service) { + this.service = service; + } + + void startup(@Observes StartupEvent event) { + Multi.createFrom().ticks().every(duration) + .emitOn(Infrastructure.getDefaultWorkerPool()) + // .filter(aLong -> { + // if (lastToken.isAccessTokenWithinRefreshInterval()) { + // return true; + // } else + // return false; + // }) + .call(() -> client.getTokens().invoke(tokens -> { + lastToken = tokens; + })) + .invoke(() -> service.updateProperty(SolaceProperties.AuthenticationProperties.SCHEME_OAUTH2_ACCESS_TOKEN, + lastToken.getAccessToken())) + .subscribe().with(aLong -> { + }); + } +} 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..fb6c927 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 = config.extra().get("authentication.scheme"); + + if (oidcProvider != null && authScheme != null && authScheme.equals("AUTHENTICATION_SCHEME_OAUTH2")) { + 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,14 @@ public MessagingService apply(SyntheticCreationalContext conte } } + 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 2bfb5fd..00ff391 100644 --- a/quarkus-solace-messaging-connector/runtime/pom.xml +++ b/quarkus-solace-messaging-connector/runtime/pom.xml @@ -137,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/incoming/SolaceIncomingChannel.java b/quarkus-solace-messaging-connector/runtime/src/main/java/com/solace/quarkus/messaging/incoming/SolaceIncomingChannel.java index 7f68298..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 @@ -45,7 +45,7 @@ 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; 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 4b5badf..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 @@ -110,6 +115,7 @@ private Transferable createConfigurationScript() { 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"); @@ -187,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"); @@ -200,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"); @@ -385,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/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----- From c8c0230fb34bc761934ac5a78f9991259c309139 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:23:49 +0530 Subject: [PATCH 06/15] changed queue name in tracing test --- .../solace/quarkus/messaging/tracing/TracingPropogationTest.java | 1 + 1 file changed, 1 insertion(+) 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 index 9455b73..c119aa7 100644 --- 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 @@ -151,6 +151,7 @@ void processor() { 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) From 5fa5d97c3273823eb06d6558e37d4d8fb16d3ae0 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:35:01 +0530 Subject: [PATCH 07/15] Integration test for Solace OAuth client authentication --- integration-tests/pom.xml | 1 + .../pom.xml | 128 + .../com/solace/quarkus/SolaceConsumer.java | 60 + .../com/solace/quarkus/SolaceCustomizer.java | 13 + .../com/solace/quarkus/SolaceResource.java | 61 + .../src/main/resources/application.properties | 1 + .../src/main/resources/keycloak.crt | 23 + .../quarkus/oauth/KeyCloakContainer.java | 102 + .../quarkus/oauth/KeycloakResource.java | 97 + .../solace/quarkus/oauth/SolaceContainer.java | 540 ++++ .../solace/quarkus/oauth/SolaceOAuthIT.java | 8 + .../solace/quarkus/oauth/SolaceOAuthTest.java | 62 + .../src/test/resources/keycloak.crt | 23 + .../src/test/resources/keycloak.key | 28 + .../keycloak/realms/solace-realm.json | 2266 +++++++++++++++++ .../src/test/resources/solace.pem | 51 + .../solace/quarkus/runtime/OidcProvider.java | 69 +- .../quarkus/runtime/SolaceRecorder.java | 8 +- 18 files changed, 3515 insertions(+), 26 deletions(-) create mode 100644 integration-tests/solace-client-oauth-integration-tests/pom.xml create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceConsumer.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceCustomizer.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/main/java/com/solace/quarkus/SolaceResource.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/main/resources/application.properties create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/main/resources/keycloak.crt create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeyCloakContainer.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/KeycloakResource.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceContainer.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthIT.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/java/com/solace/quarkus/oauth/SolaceOAuthTest.java create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.crt create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak.key create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/resources/keycloak/realms/solace-realm.json create mode 100644 integration-tests/solace-client-oauth-integration-tests/src/test/resources/solace.pem diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 08e1183..1312fe8 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..752233d --- /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 + 1.0.1-SNAPSHOT + + + 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..414162e --- /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..b3604b3 --- /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 io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.awaitility.Awaitility.await; + +@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); + } + +} \ No newline at end of file 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/runtime/src/main/java/com/solace/quarkus/runtime/OidcProvider.java b/quarkus-solace-client/runtime/src/main/java/com/solace/quarkus/runtime/OidcProvider.java index a3a08bf..d667c55 100644 --- 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 @@ -1,59 +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.enterprise.event.Observes; import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; import com.solace.messaging.MessagingService; -import com.solace.messaging.config.SolaceProperties; +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.quarkus.runtime.StartupEvent; 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 - OidcClient client; + OidcClients clients; private volatile Tokens lastToken; - private MessagingService service; Tokens getToken() { + OidcClient client = getClient(); Tokens firstToken = client.getTokens().await().indefinitely(); lastToken = firstToken; return firstToken; } void init(MessagingService service) { - this.service = service; - } - - void startup(@Observes StartupEvent event) { + OidcClient client = getClient(); Multi.createFrom().ticks().every(duration) .emitOn(Infrastructure.getDefaultWorkerPool()) - // .filter(aLong -> { - // if (lastToken.isAccessTokenWithinRefreshInterval()) { - // return true; - // } else - // return false; - // }) - .call(() -> client.getTokens().invoke(tokens -> { - lastToken = tokens; - })) - .invoke(() -> service.updateProperty(SolaceProperties.AuthenticationProperties.SCHEME_OAUTH2_ACCESS_TOKEN, - lastToken.getAccessToken())) - .subscribe().with(aLong -> { + .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 fb6c927..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 @@ -41,9 +41,9 @@ public MessagingService apply(SyntheticCreationalContext conte Instance reference = context.getInjectedReference(CUSTOMIZER); OidcProvider oidcProvider = context.getInjectedReference(OidcProvider.class); - String authScheme = config.extra().get("authentication.scheme"); + String authScheme = (String) properties.get(SolaceProperties.AuthenticationProperties.SCHEME); - if (oidcProvider != null && authScheme != null && authScheme.equals("AUTHENTICATION_SCHEME_OAUTH2")) { + if (oidcProvider != null && authScheme != null && "AUTHENTICATION_SCHEME_OAUTH2".equals(authScheme)) { properties.put(SolaceProperties.AuthenticationProperties.SCHEME_OAUTH2_ACCESS_TOKEN, oidcProvider.getToken().getAccessToken()); } @@ -61,7 +61,9 @@ public MessagingService apply(SyntheticCreationalContext conte } } - oidcProvider.init(service); + if ("AUTHENTICATION_SCHEME_OAUTH2".equals(authScheme)) { + oidcProvider.init(service); + } var tmp = service; shutdown.addLastShutdownTask(() -> { if (tmp.isConnected()) { From 0aa31cb7609a2ba08ee37516dc7097fede7c1c2c Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:38:10 +0530 Subject: [PATCH 08/15] code formatting --- .../solace/quarkus/oauth/KeycloakResource.java | 2 +- .../solace/quarkus/oauth/SolaceOAuthTest.java | 16 ++++++++-------- .../com/solace/quarkus/runtime/OidcProvider.java | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) 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 index 414162e..04259b3 100644 --- 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 @@ -50,7 +50,7 @@ public Map start() { 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.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"), 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 index b3604b3..5105a57 100644 --- 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 @@ -1,15 +1,16 @@ 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; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.awaitility.Awaitility.await; @QuarkusTest @QuarkusTestResource(value = KeycloakResource.class, restrictToAnnotatedClass = true) @@ -53,10 +54,9 @@ void testPersistent() throws InterruptedException { Thread.sleep(5000); } - await().until(() -> RestAssured .given().header("Accept", "application/json") .get("/solace/persistent").as(listOfString).size() == 3); } -} \ No newline at end of file +} 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 index d667c55..67fd962 100644 --- 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 @@ -62,7 +62,7 @@ void init(MessagingService service) { return Uni.createFrom().voidItem(); }) .subscribe().with(x -> { - if(service.isConnected()) { + 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"); From d8fb8f529b7a571638b73130c13cdf7bc4d51e20 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:46:55 +0530 Subject: [PATCH 09/15] updated alive to set to true by default --- .../quarkus/messaging/outgoing/SolaceOutgoingChannel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 df8ad1f..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 @@ -44,7 +44,7 @@ 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; From 6e4b0ff1791823b5771c2c55c2780bfb826f8cf2 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Thu, 14 Mar 2024 19:52:05 +0530 Subject: [PATCH 10/15] Documentation update --- .../ROOT/pages/includes/quarkus-solace.adoc | 32 +++++++++++++++++++ docs/modules/ROOT/pages/index.adoc | 24 ++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/includes/quarkus-solace.adoc b/docs/modules/ROOT/pages/includes/quarkus-solace.adoc index b5eedf9..9a9ec4d 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-solace.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-solace.adoc @@ -193,4 +193,36 @@ endif::add-copy-button-to-env-var[] --|`Map` | +a| [[quarkus-solace_quarkus.solace-oidc-client-name]]`link:#quarkus-solace_quarkus.solace-oidc-client-name[quarkus.solace.oidc.client-name]` + + +[.description] +-- +Optional value to configure client name provided in oidc client configuration in extension. + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_SOLACE_OIDC_CLIENT-NAMEL+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_SOLACE_OIDC_CLIENT-NAME+++` +endif::add-copy-button-to-env-var[] +--|`Map` +| + +a| [[quarkus-solace_quarkus.solace-oidc-refresh-interval]]`link:#quarkus-solace_quarkus.solace-oidc-refresh-interval[quarkus.solace.oidc.refresh.interval]` + + +[.description] +-- +Interval in seconds to fetch new access token by extension and update current solace session. This interval should be less than access token expiry time. For example if access token expires in 60s then refresh interval should be configured to 50s. The interval time should make sure that extension has sufficient time to fetch and update access token. + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_SOLACE_OIDC_REFRESH_INTERVAL+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_SOLACE_OIDC_REFRESH_INTERVAL+++` +endif::add-copy-button-to-env-var[] +--|`Map` +| + |=== \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index ebbd2fc..04a677b 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -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,27 @@ 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 +quarkus.solace.oidc.refresh.interval=50s + +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 +---- + +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] + +NOTE: The current version is tested with client_credentials grant type where Solace broker is configured as Resource Server. + +{empty}6. 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 From 88cdb2e05052245c46d5c0ecd01c490381188c00 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:35:13 +0530 Subject: [PATCH 11/15] Updated documentation --- .../ROOT/pages/includes/quarkus-solace.adoc | 32 ------------------- docs/modules/ROOT/pages/index.adoc | 4 +-- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/docs/modules/ROOT/pages/includes/quarkus-solace.adoc b/docs/modules/ROOT/pages/includes/quarkus-solace.adoc index 9a9ec4d..b5eedf9 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-solace.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-solace.adoc @@ -193,36 +193,4 @@ endif::add-copy-button-to-env-var[] --|`Map` | -a| [[quarkus-solace_quarkus.solace-oidc-client-name]]`link:#quarkus-solace_quarkus.solace-oidc-client-name[quarkus.solace.oidc.client-name]` - - -[.description] --- -Optional value to configure client name provided in oidc client configuration in extension. - -ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_SOLACE_OIDC_CLIENT-NAMEL+++[] -endif::add-copy-button-to-env-var[] -ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_SOLACE_OIDC_CLIENT-NAME+++` -endif::add-copy-button-to-env-var[] ---|`Map` -| - -a| [[quarkus-solace_quarkus.solace-oidc-refresh-interval]]`link:#quarkus-solace_quarkus.solace-oidc-refresh-interval[quarkus.solace.oidc.refresh.interval]` - - -[.description] --- -Interval in seconds to fetch new access token by extension and update current solace session. This interval should be less than access token expiry time. For example if access token expires in 60s then refresh interval should be configured to 50s. The interval time should make sure that extension has sufficient time to fetch and update access token. - -ifdef::add-copy-button-to-env-var[] -Environment variable: env_var_with_copy_button:+++QUARKUS_SOLACE_OIDC_REFRESH_INTERVAL+++[] -endif::add-copy-button-to-env-var[] -ifndef::add-copy-button-to-env-var[] -Environment variable: `+++QUARKUS_SOLACE_OIDC_REFRESH_INTERVAL+++` -endif::add-copy-button-to-env-var[] ---|`Map` -| - |=== \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 04a677b..2e0a001 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -121,8 +121,8 @@ quarkus.solace.authentication.client-cert.keystore-format= quarkus.solace.host=tcp://localhost:55555 quarkus.solace.vpn=default quarkus.solace.authentication.scheme=AUTHENTICATION_SCHEME_OAUTH2 -quarkus.solace.oidc.client-name=solace -quarkus.solace.oidc.refresh.interval=50s +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= From a57a4c033ee586262b409f7c6029e412fa3cdc89 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:46:21 +0530 Subject: [PATCH 12/15] Updated documentation to describe TLS based OAuth setup --- docs/modules/ROOT/pages/index.adoc | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 2e0a001..ca16f2e 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -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] @@ -131,11 +131,33 @@ quarkus.oidc-client.solace.credentials.secret= quarkus.oidc-client.solace.grant.type=client ---- -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] +{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}6. 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. +{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 From f262c6f5e146231e21be66815bdd932db80fe455 Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:30:21 +0530 Subject: [PATCH 13/15] updated documentation - added tracing configuration --- .../quarkus-solace-extension-common.adoc | 16 +++++++++++ docs/modules/ROOT/pages/index.adoc | 28 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) 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 ca16f2e..59088d5 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -311,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. @@ -469,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 @@ -485,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. From 1a80bcb986c35883f3bc8dce3604da19955c1f0b Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:30:28 +0530 Subject: [PATCH 14/15] Updated documentation --- docs/modules/ROOT/pages/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 59088d5..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 From 42a7b1eacbf2bbf71c64c6af9a4cb1fcaccfbbdf Mon Sep 17 00:00:00 2001 From: SravanThotakura05 <83568543+SravanThotakura05@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:35:52 +0530 Subject: [PATCH 15/15] Updated pom version of OAuth integration tests --- integration-tests/solace-client-oauth-integration-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/solace-client-oauth-integration-tests/pom.xml b/integration-tests/solace-client-oauth-integration-tests/pom.xml index 752233d..d230e22 100644 --- a/integration-tests/solace-client-oauth-integration-tests/pom.xml +++ b/integration-tests/solace-client-oauth-integration-tests/pom.xml @@ -6,7 +6,7 @@ com.solace.quarkus quarkus-solace-integration-tests-parent - 1.0.1-SNAPSHOT + ${revision}${sha1}${changelist} solace-client-oauth-integration-tests