From 617e257fc521caadcf76ecd4fd7ecf9c4bfe0612 Mon Sep 17 00:00:00 2001 From: sanjuthomas Date: Wed, 3 Sep 2025 21:35:57 -0400 Subject: [PATCH 1/4] RFC-7591 - initial version with interfaces --- .../java/examples/AuthCommonExamples.java | 2 +- .../ext/auth/oauth2/DCROptionsConverter.java | 61 + .../ext/auth/oauth2/DCRRequestConverter.java | 41 + .../ext/auth/oauth2/DCRResponseConverter.java | 53 + .../auth/oauth2/OAuth2OptionsConverter.java | 2 - .../oauth2/Oauth2CredentialsConverter.java | 2 - .../oauth2/ClientRegistrationProvider.java | 11 + .../io/vertx/ext/auth/oauth2/DCROptions.java | 75 + .../io/vertx/ext/auth/oauth2/DCRRequest.java | 36 + .../io/vertx/ext/auth/oauth2/DCRResponse.java | 50 + .../dcr/KeycloakClientRegistration.java | 13 + .../impl/KeycloakClientRegistrationImpl.java | 68 + .../io/vertx/tests/DCRKeycloak25_0_0_IT.java | 68 + .../src/test/resources/loglive.txt | 20 + .../test/resources/vertx-it-dcr-realm.json | 1853 +++++++++++++++++ 15 files changed, 2350 insertions(+), 5 deletions(-) create mode 100644 vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java create mode 100644 vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRRequestConverter.java create mode 100644 vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java create mode 100644 vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java create mode 100644 vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java create mode 100644 vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java create mode 100644 vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java create mode 100644 vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java create mode 100644 vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java create mode 100644 vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java create mode 100644 vertx-auth-oauth2/src/test/resources/loglive.txt create mode 100644 vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json diff --git a/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java b/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java index 80438cea4..c1767f6b0 100644 --- a/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java +++ b/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java @@ -122,4 +122,4 @@ public void example9(User user) { String sub = user.get("sub"); } } -} +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java new file mode 100644 index 000000000..5865db7ac --- /dev/null +++ b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java @@ -0,0 +1,61 @@ +package io.vertx.ext.auth.oauth2; + +import io.vertx.core.json.JsonObject; + +/** + * Converter and mapper for {@link io.vertx.ext.auth.oauth2.DCROptions}. + * NOTE: This class has been automatically generated from the {@link io.vertx.ext.auth.oauth2.DCROptions} original class using Vert.x codegen. + */ +public class DCROptionsConverter { + + static void fromJson(Iterable> json, DCROptions obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "httpClientOptions": + if (member.getValue() instanceof JsonObject) { + obj.setHttpClientOptions(new io.vertx.core.http.HttpClientOptions((io.vertx.core.json.JsonObject)member.getValue())); + } + break; + case "resourceUri": + break; + case "initialAccessToken": + if (member.getValue() instanceof String) { + obj.setInitialAccessToken((String)member.getValue()); + } + break; + case "site": + if (member.getValue() instanceof String) { + obj.setSite((String)member.getValue()); + } + break; + case "tenant": + if (member.getValue() instanceof String) { + obj.setTenant((String)member.getValue()); + } + break; + } + } + } + + static void toJson(DCROptions obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(DCROptions obj, java.util.Map json) { + if (obj.getHttpClientOptions() != null) { + json.put("httpClientOptions", obj.getHttpClientOptions().toJson()); + } + if (obj.resourceUri() != null) { + json.put("resourceUri", obj.resourceUri()); + } + if (obj.getInitialAccessToken() != null) { + json.put("initialAccessToken", obj.getInitialAccessToken()); + } + if (obj.getSite() != null) { + json.put("site", obj.getSite()); + } + if (obj.getTenant() != null) { + json.put("tenant", obj.getTenant()); + } + } +} diff --git a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRRequestConverter.java b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRRequestConverter.java new file mode 100644 index 000000000..b83547e3f --- /dev/null +++ b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRRequestConverter.java @@ -0,0 +1,41 @@ +package io.vertx.ext.auth.oauth2; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; + +/** + * Converter and mapper for {@link io.vertx.ext.auth.oauth2.DCRRequest}. + * NOTE: This class has been automatically generated from the {@link io.vertx.ext.auth.oauth2.DCRRequest} original class using Vert.x codegen. + */ +public class DCRRequestConverter { + + static void fromJson(Iterable> json, DCRRequest obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "clientId": + if (member.getValue() instanceof String) { + obj.setClientId((String)member.getValue()); + } + break; + case "registrationAccessToken": + if (member.getValue() instanceof String) { + obj.setRegistrationAccessToken((String)member.getValue()); + } + break; + } + } + } + + static void toJson(DCRRequest obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(DCRRequest obj, java.util.Map json) { + if (obj.getClientId() != null) { + json.put("clientId", obj.getClientId()); + } + if (obj.getRegistrationAccessToken() != null) { + json.put("registrationAccessToken", obj.getRegistrationAccessToken()); + } + } +} diff --git a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java new file mode 100644 index 000000000..ac3478dcc --- /dev/null +++ b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java @@ -0,0 +1,53 @@ +package io.vertx.ext.auth.oauth2; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; + +/** + * Converter and mapper for {@link io.vertx.ext.auth.oauth2.DCRResponse}. + * NOTE: This class has been automatically generated from the {@link io.vertx.ext.auth.oauth2.DCRResponse} original class using Vert.x codegen. + */ +public class DCRResponseConverter { + + static void fromJson(Iterable> json, DCRResponse obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "id": + break; + case "clientId": + break; + case "enabled": + break; + case "clientAuthenticationType": + break; + case "secret": + break; + case "registrationAccessToken": + break; + } + } + } + + static void toJson(DCRResponse obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(DCRResponse obj, java.util.Map json) { + if (obj.getId() != null) { + json.put("id", obj.getId()); + } + if (obj.getClientId() != null) { + json.put("clientId", obj.getClientId()); + } + json.put("enabled", obj.isEnabled()); + if (obj.getClientAuthenticationType() != null) { + json.put("clientAuthenticationType", obj.getClientAuthenticationType()); + } + if (obj.getSecret() != null) { + json.put("secret", obj.getSecret()); + } + if (obj.getRegistrationAccessToken() != null) { + json.put("registrationAccessToken", obj.getRegistrationAccessToken()); + } + } +} diff --git a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/OAuth2OptionsConverter.java b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/OAuth2OptionsConverter.java index 1d60ba305..65c788a09 100644 --- a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/OAuth2OptionsConverter.java +++ b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/OAuth2OptionsConverter.java @@ -2,8 +2,6 @@ import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonArray; -import java.time.Instant; -import java.time.format.DateTimeFormatter; /** * Converter and mapper for {@link io.vertx.ext.auth.oauth2.OAuth2Options}. diff --git a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/Oauth2CredentialsConverter.java b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/Oauth2CredentialsConverter.java index 90a7362bd..13a7018e7 100644 --- a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/Oauth2CredentialsConverter.java +++ b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/Oauth2CredentialsConverter.java @@ -2,8 +2,6 @@ import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonArray; -import java.time.Instant; -import java.time.format.DateTimeFormatter; /** * Converter and mapper for {@link io.vertx.ext.auth.oauth2.Oauth2Credentials}. diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java new file mode 100644 index 000000000..0a555e753 --- /dev/null +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java @@ -0,0 +1,11 @@ +package io.vertx.ext.auth.oauth2; + +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.Future; + +@VertxGen +public interface ClientRegistrationProvider { + Future create(String clientId); + Future get(DCRRequest dcrRequest); + Future delete(DCRRequest dcrRequest); +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java new file mode 100644 index 000000000..9699e96df --- /dev/null +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java @@ -0,0 +1,75 @@ +package io.vertx.ext.auth.oauth2; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.json.JsonObject; + +@DataObject +@JsonGen(publicConverter = false) +public final class DCROptions { + + /** + * The base url of the OIDC provider like Keycloak. + */ + private String site; + + /** + * Name of the tenant if any. Keycloak call this realm. + */ + private String tenant; + + /** + * Initial access token to authenticate with the OIDC provider. + */ + private String initialAccessToken; + + private HttpClientOptions httpClientOptions = new HttpClientOptions(); + + public DCROptions(JsonObject json) { + DCROptionsConverter.fromJson(json, this); + } + + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + DCROptionsConverter.toJson(this, json); + return json; + } + + public HttpClientOptions getHttpClientOptions() { + return httpClientOptions; + } + + public void setHttpClientOptions(HttpClientOptions httpClientOptions) { + this.httpClientOptions = httpClientOptions; + } + + public String getInitialAccessToken() { + return initialAccessToken; + } + + public void setInitialAccessToken(String initialAccessToken) { + this.initialAccessToken = initialAccessToken; + } + + public String getSite() { + return site; + } + + public void setSite(String site) { + this.site = site; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String resourceUri() { + return String.format("%s/%s/%s", site, tenant, "clients-registrations/default/"); + } + +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java new file mode 100644 index 000000000..8c00eaa6a --- /dev/null +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java @@ -0,0 +1,36 @@ +package io.vertx.ext.auth.oauth2; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.json.JsonObject; + +@DataObject +@JsonGen(publicConverter = false) +public class DCRRequest { + private String clientId; + private String registrationAccessToken; + + public DCRRequest(JsonObject json) { + DCRRequestConverter.fromJson(json, this); + } + + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + DCRRequestConverter.toJson(this, json); + return json; + } + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getRegistrationAccessToken() { + return registrationAccessToken; + } + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java new file mode 100644 index 000000000..37ff7d716 --- /dev/null +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java @@ -0,0 +1,50 @@ +package io.vertx.ext.auth.oauth2; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.json.JsonObject; + +@DataObject +@JsonGen(publicConverter = false) +public class DCRResponse { + private String id; + private String clientId; + private boolean enabled; + private String clientAuthenticationType; + private String secret; + private String registrationAccessToken; + + public DCRResponse(JsonObject json) { + DCRResponseConverter.fromJson(json, this); + } + + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + DCRResponseConverter.toJson(this, json); + return json; + } + + public String getId() { + return id; + } + + public String getClientId() { + return clientId; + } + + public boolean isEnabled() { + return enabled; + } + + public String getClientAuthenticationType() { + return clientAuthenticationType; + } + + public String getSecret() { + return secret; + } + + public String getRegistrationAccessToken() { + return registrationAccessToken; + } +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java new file mode 100644 index 000000000..051aecf2f --- /dev/null +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java @@ -0,0 +1,13 @@ +package io.vertx.ext.auth.oauth2.dcr; + +import io.vertx.core.Vertx; +import io.vertx.ext.auth.oauth2.ClientRegistrationProvider; +import io.vertx.ext.auth.oauth2.DCROptions; +import io.vertx.ext.auth.oauth2.dcr.impl.KeycloakClientRegistrationImpl; + +public interface KeycloakClientRegistration extends ClientRegistrationProvider { + + static KeycloakClientRegistration create(Vertx vertx, DCROptions dcrOptions) { + return new KeycloakClientRegistrationImpl(vertx, dcrOptions); + } +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java new file mode 100644 index 000000000..69aa78d77 --- /dev/null +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java @@ -0,0 +1,68 @@ +package io.vertx.ext.auth.oauth2.dcr.impl; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.impl.http.SimpleHttpClient; +import io.vertx.ext.auth.impl.http.SimpleHttpResponse; +import io.vertx.ext.auth.oauth2.DCROptions; +import io.vertx.ext.auth.oauth2.DCRRequest; +import io.vertx.ext.auth.oauth2.DCRResponse; +import io.vertx.ext.auth.oauth2.dcr.KeycloakClientRegistration; + +public final class KeycloakClientRegistrationImpl implements KeycloakClientRegistration { + + private final Vertx vertx; + private final SimpleHttpClient simpleHttpClient; + + private final DCROptions dcrOptions; + + public KeycloakClientRegistrationImpl(Vertx vertx, DCROptions dcrOptions) { + this.vertx = vertx; + this.dcrOptions = dcrOptions; + this.simpleHttpClient = new SimpleHttpClient(vertx, "dcr-client", dcrOptions.getHttpClientOptions()); + } + + @Override + public Future create(String clientId) { + JsonObject initialAccessToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrOptions.getInitialAccessToken())); + JsonObject payload = JsonObject.of("clientId", clientId); + return simpleHttpClient.fetch(HttpMethod.POST, dcrOptions.resourceUri(), initialAccessToken, + payload.toBuffer()).compose(response -> constructResponse(response)); + } + @Override + public Future get(DCRRequest dcrRequest) { + JsonObject registrationToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); + return simpleHttpClient.fetch(HttpMethod.GET, String.format("%s/%s", dcrOptions.resourceUri(), + dcrRequest.getClientId()), registrationToken, null) + .compose(response -> constructResponse(response)); + } + + @Override + public Future delete(DCRRequest dcrRequest) { + JsonObject registrationToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); + return simpleHttpClient.fetch(HttpMethod.DELETE, String.format("%s/%s", dcrOptions.resourceUri(), + dcrRequest.getClientId()), registrationToken, null) + .compose(response -> { + if (response.statusCode() != 204) { + return Future.failedFuture("Bad Response [" + response.statusCode() + "] " + response.body()); + } + return Future.succeededFuture(); + }); + } + + private Future constructResponse(SimpleHttpResponse response) { + if (response.statusCode() != 201) { + return Future.failedFuture("Bad Response [" + response.statusCode() + "] " + response.body()); + } + if (!response.is("application/json")) { + return Future.failedFuture("Cannot handle Content-Type: " + response.headers().get("Content-Type")); + } + final JsonObject json = response.jsonObject(); + if (json == null) { + return Future.failedFuture("Cannot handle null JSON"); + } + return Future.succeededFuture(new DCRResponse(json)); + } +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java new file mode 100644 index 000000000..45164d25e --- /dev/null +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java @@ -0,0 +1,68 @@ +package io.vertx.tests; + +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.RunTestOnContext; +import io.vertx.ext.unit.junit.VertxUnitRunnerWithParametersFactory; +import java.util.Arrays; +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +@RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(VertxUnitRunnerWithParametersFactory.class) +public class DCRKeycloak25_0_0_IT { + + @ClassRule + public static final GenericContainer container = new GenericContainer<>("quay.io/keycloak/keycloak:25.0.0") + .withEnv("KEYCLOAK_USER", "admin") + .withEnv("KEYCLOAK_PASSWORD", "secret") + .withEnv("DB_VENDOR", "H2") + .withExposedPorts(8080, 8443) + .withClasspathResourceMapping("vertx-it-dcr-realm.json", "/tmp/fixtures.json", BindMode.READ_ONLY) + .withCommand("-b", "0.0.0.0", "-Dkeycloak.migration.action=import", "-Dkeycloak.migration.provider=singleFile", "-Dkeycloak.migration.file=/tmp/fixtures.json", "-Dkeycloak.migration.strategy=OVERWRITE_EXISTING") + .waitingFor(Wait.forLogMessage(".*Keycloak.*started.*", 1)); + + + @Parameterized.Parameters + public static List sites() { + return Arrays.asList("http", "https"); + } + + @Rule + public final RunTestOnContext rule = new RunTestOnContext(); + + private final String proto; + private String site; + + public DCRKeycloak25_0_0_IT(String proto) { + this.proto = proto; + } + + @Before + public void setUp() { + switch (proto) { + case "http": + site = proto + "://" + container.getHost() + ":" + container.getMappedPort(8080); + break; + case "https": + site = proto + "://" + container.getHost() + ":" + container.getMappedPort(8443); + break; + default: + throw new IllegalArgumentException("Invalid proto: " + proto); + } + } + + @Test + public void test(TestContext should) { + final Async test = should.async(); + + } +} diff --git a/vertx-auth-oauth2/src/test/resources/loglive.txt b/vertx-auth-oauth2/src/test/resources/loglive.txt new file mode 100644 index 000000000..4e9215289 --- /dev/null +++ b/vertx-auth-oauth2/src/test/resources/loglive.txt @@ -0,0 +1,20 @@ +please stop the keycloak before export +export configs: ./kc.sh export --file master.json --realm master + +to generate initial access token: +kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin +create long live initial access token: kcadm.sh create clients-initial-access -s expiration=157788000 -o + + + +{ + "id" : "031d7068-4a52-4e76-b0f4-d3ae9ef575a8", + "token" : "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxZmI0MjFhNy04ZGZjLTQ1OTctOTM1Ni01MGEwNTA3YjM4MmEifQ.eyJleHAiOjE5MTYyMDQ0NzEsImlhdCI6MTc1ODQxNjQ3MSwianRpIjoiMDMxZDcwNjgtNGE1Mi00ZTc2LWIwZjQtZDNhZTllZjU3NWE4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsInR5cCI6IkluaXRpYWxBY2Nlc3NUb2tlbiJ9.eRVfRpmKrGsaq2FowQDfMysOXdsS7rBeoB5UYkANQNHVDPoNxooEbr-MF7MPrkEMsY-Z4IAlnsCjQ837HpZbnA", + "timestamp" : 1758416471, + "expiration" : 157788000, + "count" : 1, + "remainingCount" : 1 +} + +mvn clean install -PIT + diff --git a/vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json b/vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json new file mode 100644 index 000000000..bf7326a61 --- /dev/null +++ b/vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json @@ -0,0 +1,1853 @@ +{ + "id" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", + "realm" : "master", + "displayName" : "Keycloak", + "displayNameHtml" : "
Keycloak
", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 60, + "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" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "7716301c-6991-4562-abbd-5ce775bb1b19", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", + "attributes" : { } + }, { + "id" : "66f62a2b-f1b7-4429-ab49-16d15535ab89", + "name" : "create-realm", + "description" : "${role_create-realm}", + "composite" : false, + "clientRole" : false, + "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", + "attributes" : { } + }, { + "id" : "39fb4301-b1d7-4f31-983f-c4ee62f60228", + "name" : "admin", + "description" : "${role_admin}", + "composite" : true, + "composites" : { + "realm" : [ "create-realm" ], + "client" : { + "master-realm" : [ "manage-realm", "manage-users", "view-authorization", "create-client", "query-groups", "view-events", "view-identity-providers", "manage-events", "manage-clients", "query-users", "view-clients", "query-clients", "view-users", "manage-authorization", "query-realms", "manage-identity-providers", "view-realm", "impersonation" ] + } + }, + "clientRole" : false, + "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", + "attributes" : { } + }, { + "id" : "5167eed8-7d3d-41d2-839d-daf26f681c3e", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", + "attributes" : { } + }, { + "id" : "4a3f17bf-a2dd-4356-a3ae-08b8200385de", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", + "attributes" : { } + } ], + "client" : { + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "0fc968b8-8468-47ef-9c92-e4463068eca5", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "a1595a1f-3858-4f85-bd80-d4dda54b4405", + "attributes" : { } + } ], + "master-realm" : [ { + "id" : "2065332e-8851-4209-b2e9-d864d1b042bb", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "f3cb2a4d-9e09-4a2f-bfaa-9305ef366e12", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "b50015e7-54f7-4c1a-923b-a52cd8e6b41e", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "637800b5-4b18-4e43-b6fa-316ec1a01524", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "c801f85f-7882-46ac-8492-7113fa5bec31", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "8b6ca459-a2d4-4247-ac68-0a9d8b88afca", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "a236dc7c-0ac8-41aa-b724-d2a0bcfe6325", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "7be47ced-47a6-4847-9c64-8feb998b2421", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "bd6645b8-483a-4ce5-941f-c3042187224d", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "8cd00641-0dbd-4694-89c8-23ec68574dcc", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "6feba329-356c-47b1-9a51-a7008a99333b", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "426cacd6-f801-4734-9f1a-141e4035a385", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "ac500a5b-24a7-4404-b0e5-fb987ec9eb1f", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "60daff22-350a-4ee2-ae84-9001acf618b1", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "f4b8086d-bb02-455d-8575-06bd53d3dbd0", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "46693d4f-33ec-48f1-8268-daf5300f4488", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "d684ef77-1b65-479b-80b9-ceda51af0bb9", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + }, { + "id" : "009a8ad2-0ccd-4741-b8f4-410abeae2859", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", + "attributes" : { } + } ], + "account" : [ { + "id" : "89646bb0-11ab-4e7d-9f68-ee1f5091cc07", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + }, { + "id" : "ac75ba01-bc48-4dc7-a69a-0e2f704acd7d", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + }, { + "id" : "8e871d80-419b-4084-bf3e-8599db79fcdd", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + }, { + "id" : "b6bf4c36-5c0b-4b4e-bb22-b8b1bc662175", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + }, { + "id" : "2bd41666-1cc4-482e-bae8-6aa55106ab20", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + }, { + "id" : "6e0a0cbd-1e61-42ab-928e-89e49991d437", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + }, { + "id" : "ad8c9f11-3fb2-464f-95b3-f97335618c34", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + }, { + "id" : "e1b0c173-0ca2-4a58-93f6-d0fb6cda2eda", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "5167eed8-7d3d-41d2-839d-daf26f681c3e", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "users" : [ { + "id" : "39dc37fd-c4a4-4179-a7a6-1e06139c1685", + "username" : "admin", + "emailVerified" : false, + "createdTimestamp" : 1758416373805, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "ada7b84e-0c91-4b49-93b6-be468a7dd89d", + "type" : "password", + "createdDate" : 1758416373919, + "secretData" : "{\"value\":\"O0aeKu270eMjzoxdzHPaIFACxcqs2CHNDI42WlwO9y0=\",\"salt\":\"wPaChOD0tkbqJBUaGJdjVA==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "admin", "default-roles-master" ], + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "42643532-6734-41ea-b45c-ae5c1c679915", + "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" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "b559d8a4-0226-4dd2-a9ce-973e0a5fbaa8", + "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" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "5b164e92-b9fc-4a2e-a9e0-3ad276591b48", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "59b4b024-933e-406f-89d1-1907cf9f607d", + "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", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "a1595a1f-3858-4f85-bd80-d4dda54b4405", + "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", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "d02b948a-3614-4daa-9014-b36481f77f59", + "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", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "24d596da-de44-433e-a0de-1e5a041e2817", + "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" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "2f5446e2-f1b1-4b24-a71c-2dff507cd63b", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "c8a82573-e3b5-4276-9228-3e4f22b020eb", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "0540e220-e6bf-4d72-8729-994842eec4a6", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + }, { + "id" : "fff0d674-c9ab-48f4-8646-8c262fe5e448", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "ceff1ce5-6bf2-43c8-b1e7-477e3a36b5e5", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "767307bc-77a8-4407-a03c-c0a1b31744bd", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "9cc89c6a-d0f5-4549-89c6-43919675ad1c", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "f20b7dea-8c40-4b4f-97ed-77bfd826ab0d", + "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", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "6f4f8582-d3c7-452c-892e-d45aea242236", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "e2c4b2fc-fd81-4c67-815e-8c9126616917", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "4125708a-9667-4388-b9ed-e6457d5fac3f", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "ed694a3a-54f8-479c-9156-96e5c25c757a", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "2f626cd6-6556-4785-8b7e-8ffb743de5ab", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "e83aea7b-5451-400f-b309-9614e9f920e9", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "fb6205b9-8c27-4da8-8579-e3b970638adb", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "df160bdf-f419-4f0e-a885-79183b8e7793", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "8c1c0e8a-c07d-4d5c-a3b7-45e9c05eb5e3", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "99bfb11b-5699-42a1-a206-741bc65c91c8", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "01989967-6e93-4197-89eb-5cbb9bd1d8b3", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "f43d799b-7328-4cce-853e-089d457b0276", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "21bba731-7ef6-413e-9036-402319d35f41", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "66901781-04e6-462e-bef9-b19f06bae8c5", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "c7eec4d9-47fd-4881-bb42-7416495ab2e1", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "e5ff0af2-beb0-4cb7-90c4-e68638ded86e", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "53a913ed-e264-4039-9fcb-68c368dcd5db", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "119e76e4-30c6-45a0-a4e1-f0905f62b029", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "b18d8358-cbfb-431a-81ab-e89679932d81", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "d7028221-398e-45f0-83e1-9f979515dd0f", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "a4189f9b-fce4-46da-8eef-978e4626e8ab", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "06b6d147-5a3c-48db-9c1f-094c4ee90da8", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "4882fd8c-f362-40c3-9e82-68175026673b", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "de209b11-aa1f-4c24-844a-e06dc5bd69b2", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "56f75ee1-d6a1-49a8-88ce-1022ff736a91", + "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" : "51f512a5-a4a5-4d30-a382-9fe98c4d5533", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "63a432bd-a932-4537-b14d-427665f48aca", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "4ada5faa-7bf0-4c09-a444-c0ca00669bdc", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + } ] + }, { + "id" : "b737ee4c-f4a8-40ac-9fc8-b2deaff5fa43", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "c8ea684d-bfe7-4d16-8660-576e1ee390a8", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "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" : "68d49e3f-d86b-4bea-91d0-1ea66ed1bebc", + "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" : "8369f077-09d5-4e2e-ad03-0125def41789", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "e74c8a8d-5ac0-439e-a0e6-fdc93e730aa9", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "xXSSProtection" : "1; mode=block", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "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" : "3a5e2609-cfd8-49b4-82b0-58654bc1111f", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "79241dac-7387-40db-bec6-f4879ead3094", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "ca6ecdc9-704b-4064-986c-e156e3606b48", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "76053469-e02e-4de1-b085-8f1fb4ff31fb", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "55da18d7-355b-4ee3-8cd9-528d5d634a0e", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "c27a136f-268e-4347-a36d-17e890511808", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper" ] + } + }, { + "id" : "1c400270-6444-421d-bfe8-0d2005d6d22e", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "1ccbdd23-5dac-4f3d-8991-47d80e035bf8", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "c959e8ed-d656-4f33-97d9-c1f97be831ba", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "9b389689-2a59-4d69-94d4-6765533730d9", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAwgxpQ5hcXQ/ehlHGKDMjQpHoQAOxdcMDv3mYCsjA/yA+zQwusjnrMP4FSI4QLTXxYd0ySiWdKy0s83jF2CtpWKDCyL2Jm6it0pV1l7d0uJK6sqo8KHOd3IYMzKEB8VkOxChLhxskdgy+thPKEAVCuY+PO6Py+c+33TRU7kLpcGxElf6TxEfvPEIi4QqWhUVJx5nbhHflzEoHRElwMg/yP2WFam3mK323nMoaCT6wAA3sfrK2Fxg/3VlcFHis3eeDPZ8/MDzJyLMDxP+jyKzuN6zgVjMFC0YzYVg8Z8+Wyk04b1eMDwOAQt50Wz0kVjeL76SRlw4+N2m3PVrkpe2ILQIDAQABAoIBAB3lrkzigKM7UAb/OmSfMbjj+hXte74FGeK6Z+6MF7rhy6CUAUMnIVF0ZyP9SgjCiDo9IQPDin+d1OXTnx07MwlGBjzKLGrLMQeCTOffNXXqmtFENpiU7Pw5EGfGtsyUnW3ON4oivcS3xKUQ+iTrFT4PHFENZYuPsWhnfl5nTh1/4SNB2TyJqoK/kEVJthq+lm8rbSTtIM5g30OnL5Cs1UlUvxqTxS1VaKkqkvTv7/W2u6R5iYg1EAIYxCBDlvqUk4yaSuTdQc5nom4mkpfl9mC9lHW099Dh4TpVR0YZ43S7ya9m40X5S6qA8aSXJugdzLmLJVI+mqF8FCjdOGSizZECgYEA+B8nFDkyfcV9y8nl/NSOJtRQbaL+dIBfk59TRotSvcXzwDTZxkd0DxNt0Hhp5kFMRHEQ6RSUwnVHX9VhZbAELsCKACMJc5KHIMhzZzTX1Um9hDeq2nafKVlVB8Xq8lOv2WXafJSHKuWU5z0aLwu0s0R1Qu1cdToA5JgcOvHB8ScCgYEAyDW6AiLtPX8HvK8+8wYFF9oOMjUMcoaoscwUIhUq6vZ4A1vW/udRnGc0KJXLb3IfF0ocoOsoHILKMj1XHqkVCTXYLLearSoztUWKJ9xCzWA6efPm+4u4Do5A5DpJiuiN7dAs+lQ0bIaDWlarxuVzv5GZJL58a3N8n9BPbohyqIsCgYBInH1IOtDe46vDtqsaiW2sSVfcLeX/XA4cWnT5YQ5uwP5rUkwk7YY2Xz400vhSzOsv4FLNkxtRqRahIgXlx1QCBpCcJ6S9cLUgz+iN3HYJUhInHk9erXY4mI78vlvaXMlKercs3B6OXC9uWIBQIkHYcIFMe8AiAj5+p1EjotpZ4wKBgGE2pUEQUH3BxrH5rek3DRYNzULXu1mIxTS/0S7TdGluj8uhtn1DOL9m5U6nnYZZtXsLg2cnh+yAHUnTuw+qLgqw2GyuIi7fBQiJ4ZGFQ/KRPROmOIv/xblbnWq0Wq1WSkQlBxHt/R6NGg6tcu86zr9AJUpQK/3Ir8Sx0z/qhTINAoGBALgDo8vCvYtPgra4eaSU67UFYMXdN6vwgx7KyI+nxks75tpLC00js+CvWc9kcEaf7Aa1qSovho8HfNjZHvrsPCgZLYxPPuJr8w0RKr3hh8IfAq9AgB3kIQ8VAmdaf8PKbv07vbER50xqZFG6bqZBbEm9GCQtm1IT6ngMRUn/HVqH" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICmzCCAYMCBgGZacdO4zANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwOTIxMDA1NjMzWhcNMzUwOTIxMDA1ODEzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCDGlDmFxdD96GUcYoMyNCkehAA7F1wwO/eZgKyMD/ID7NDC6yOesw/gVIjhAtNfFh3TJKJZ0rLSzzeMXYK2lYoMLIvYmbqK3SlXWXt3S4krqyqjwoc53chgzMoQHxWQ7EKEuHGyR2DL62E8oQBUK5j487o/L5z7fdNFTuQulwbESV/pPER+88QiLhCpaFRUnHmduEd+XMSgdESXAyD/I/ZYVqbeYrfbecyhoJPrAADex+srYXGD/dWVwUeKzd54M9nz8wPMnIswPE/6PIrO43rOBWMwULRjNhWDxnz5bKTThvV4wPA4BC3nRbPSRWN4vvpJGXDj43abc9WuSl7YgtAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEXGyf+4kQqgfKhK8GcK14qG1AbKQQXi31JvAKN2OLOj80f283HcbgUFKFnZ18fh56tJrvr+TfqYCDLNQq0x3oYbhHrMkG23HUT7Cp/Mzi4Gajr1DloFlsDkMn3NQq62Dd5UCV9K9PmqcP2KIyEnXNPgICrFOVzbYN2fbordvC0u/LvSq83/fohGBk9T1bdPMoNFApwrX9sL9zd25gGFyEvlpCSUcoX5Zsgi7rg6M3mKxBorvBXbJKYm8tnodPnWBO7YXhnNmOZbNEMxx48W+DXfp12OacAKp84bHdsM1HJPB5dfpaqAiZmvO/j58mU/CU1xxjXCmry6nRbUG1ez7LQ=" ], + "priority" : [ "100" ] + } + }, { + "id" : "9b2fed93-48ed-4346-b766-146026ea64c8", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAz7pGgi5Mehau/0s0sztHUBxr7+Eqdd0FlSbGgBlgC0tzsurd9ueL8bnCdVYc3DFYbZ8RySqZ6LkLHlpU1lKMMvU2jrhhDT5HQjwmSjHhdArYMZN5LmkTMUCrZvTPvlWtTVYtvdNdn+SPqm5u7BqWSPCO00cBbnJs5l2KVDSOD+kNXvt1dOLORwZ5nSxfaEUJeE3+3wspib87XADDSAmkmC4Jv0UwucRc8qGgdePzDBtHOsc69MYNvqV4ukOwYp43lbsp/iMuUaq5AApgmx5QpAWzktAH24cFC/MsaqevUdunMDcDR4BNZnv4Evtme629FCUZv5IEcaShidjvAxo7sQIDAQABAoIBAADERFFMCN+5VRjrd4R73XXR6HEAXUXIJTAVSHhcbwWZk2c7brxYK6Jluv7rLvQadtqFiT8Exy+7/Kh42DqNymwQbcDQX2nrIHiXHqrfdBb/4D8wclNkqeeSax0ow/IAO8iciYuWsI3HshKHATQdOqxNq1s0NkPHhoBP736bKKSDEZW/S/0FmYlKgzh6+bq4Vo3m7j5/jw972VQjhz7TqY4QauIJ/lAfUp7rjhlwnZ7EXlNox11PNT0j1X1q+tTl2KPtlZlUKBCMoNOCOJH6H1Jfo1zkezC0qo/mZnTTgi79yO1V7w7u4ewcQUO6+iNeE6+k+f5wgtW81/tHBlqKYh0CgYEA6vU4vDAZfvXTaUoCk0fKiqSufbxGfHQ+vFUHrq5zCuo3fO6Fr3IlAVj7rgdWtB7ADfBYPMln2jo85J0/3CLI9vPzo9TWFS7IfsxZ31F7pJ5cUq8asOC6R8i7jbNlwgw0KY11O5D0BSjjhGyIHZ3/0cd/unXdujMo651Da/OFhW0CgYEA4lTB5WmjaUjba/DXFKnq858GHsClSzUiL3xKafZm2n2rYgHFI8Plo69p5I+Gm0bwP7qv8zpLdkSLbSW9SmrXnO4Y+JFhBrXc14AgffEvOkzAqFOy5nTj9KtILUdfCp9imywSZcmQOksRnxGrO8z6cqKuwE54s3c9ptdrZpKAGNUCgYEAml8htqtXpQ34qD3jMUdC8RLAPoZcPVrW+UBYutGNbodyi5ffp+U0IQ2I1s+ljNSDVOUr5Exj+oK3DXGvyn/kUDT6SBMH0YUaYAz9o3dS6FbPy9eWjs4oNg/SAojy+6qu/vodI1TaryUtTw1L5UGxGfCcoDLeyn7gjOWXra6jdIUCgYEAzyPbQWdpU1aG20dU0Wihy83jAAo4PZinbOZIBKqqf5U8YTpVfuIvLZvBQpnJDBvkja8CkYzsu5UINiCwGYenRczztar4LfgL4n81PY6bNrRALs5LkXexmZ+ZMzd62HH/AR1YySeshG1n9zloihaTsI8LeJ1ZAodDOYs2lG3IJvECgYAuAghxnjnR44H14t54sKAJ72tXfOeizyHFGDJVSL6+OUs4GoxG1ECBiJScu7xXUEl+hsPFnClmhvkd1lUWHkW3QBJxROnzA3nb4gnHBpHLzA2Sadc1KnEC+KOCbopjYnWruLXHv90apctdEvSqCcwBsQkAxFGJjAp7ZvRPpAXTDg==" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICmzCCAYMCBgGZacdP+jANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwOTIxMDA1NjMzWhcNMzUwOTIxMDA1ODEzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPukaCLkx6Fq7/SzSzO0dQHGvv4Sp13QWVJsaAGWALS3Oy6t3254vxucJ1VhzcMVhtnxHJKpnouQseWlTWUowy9TaOuGENPkdCPCZKMeF0Ctgxk3kuaRMxQKtm9M++Va1NVi29012f5I+qbm7sGpZI8I7TRwFucmzmXYpUNI4P6Q1e+3V04s5HBnmdLF9oRQl4Tf7fCymJvztcAMNICaSYLgm/RTC5xFzyoaB14/MMG0c6xzr0xg2+pXi6Q7BinjeVuyn+Iy5RqrkACmCbHlCkBbOS0AfbhwUL8yxqp69R26cwNwNHgE1me/gS+2Z7rb0UJRm/kgRxpKGJ2O8DGjuxAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAMYctnHyhV63/C5Xwd3pYEwaLK3UnbXvVNByecb95QtBF9x+0N0pc7R0zpQrqkEuBGzaNZ4RtkdPGQd1uIdYs9PeM4SxTvo9mIueFE738GIImTjZeeEkRggWk8vexcyRhIO+WLa2RNAqqfWuFvYBVFfKT43jR0iewc45q6BW85YoC+PZdevHPtgRtSXGOMUGQeOL7iKdQ9M8wzx5SHSDzgxTpQResxNILpvaCgyWIvWk+JEH7aA2pkGuqzoIGqThSslLBONXzvmpZtJFmksS26lvrEBcFcA8SWx/V1flOSzKYmWR86W6ef/XgG498tkiLMLG0XQkENXMpjS0H55Nw0g=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "fe588f97-51b2-4125-8ce6-63e3ad1ba627", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "1fb421a7-8dfc-4597-9356-50a0507b382a" ], + "secret" : [ "P4HTBOu7g5apeUjxCRwHjGWaB2R356zpucQWr39_3g_4Ly423xWx5gfBDiRQ7C-okp2-am95t6W1ackWeE3ANltCIRNllglwz6sJ8K5H6a3XGdFikb1rRXjXvDfA7Psv7-iHBUzxtNFo-7t6yq22jw0BQwmy4B-FSdh1qw_FI4g" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "fc403847-be4c-43af-92d1-786f3768beb7", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "9d7cd70d-7320-4490-923c-b0322c37fdf6" ], + "secret" : [ "KXBTjGK_MOQUFoTBOH1aVA" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "df4eada7-a7c3-4e72-85fd-18e8af604f79", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "b80cd966-7189-4614-ad3d-ddc94a9c1231", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "d360138e-47f9-4128-82f5-fd362f033e53", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "7a3f62bb-1cb0-4cfb-9c51-c774d7397ed5", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "a2fb1bca-84d1-4ceb-a129-cca590898826", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "8749b9ee-ba24-4ffc-b7ef-daffdd67860f", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "c570398a-915c-4892-9087-71154c0b6492", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "c73f5a22-9c0f-4498-bd28-a30db04f4c2a", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "b3152584-2f35-46ed-9956-0859489c96da", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "94595f44-ec8d-48b4-a427-502963b2b966", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "acbd8976-d3ac-406a-ad25-dc677b00fe86", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "3149a669-8033-44ea-a449-f04ae44f465d", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "60ad2537-26fe-4998-9641-69dfccb7d430", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "9eecad10-1b67-4986-9409-6d07bb411e70", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "9b7a26a4-557c-4b81-a054-4441279ff88f", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "2376188e-ab6d-4eed-a24e-e4ad1a63daea", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "d4d5674d-d1f3-4786-8c3f-157587b20334", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "33d8ccd5-fc53-4e14-ae82-ed8aa7fa033b", + "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, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "8b6781b7-f19e-4f03-acb0-fb0e8f2f3c9d", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "404beb50-b053-4a3c-b0bc-1513b1a4e51b", + "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" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "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", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "parRequestUriLifespan" : "60", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false" + }, + "keycloakVersion" : "25.0.0", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file From 130edafcf117def7752fb31a2c2d136de0e2eeb5 Mon Sep 17 00:00:00 2001 From: sanjuthomas Date: Sat, 20 Sep 2025 22:37:17 -0400 Subject: [PATCH 2/4] incorporating code review comments --- .../src/main/asciidoc/index.adoc | 2 +- .../ext/auth/oauth2/DCROptionsConverter.java | 6 +- .../oauth2/ClientRegistrationProvider.java | 13 + .../io/vertx/ext/auth/oauth2/DCROptions.java | 12 +- .../io/vertx/ext/auth/oauth2/DCRRequest.java | 18 + .../io/vertx/ext/auth/oauth2/DCRResponse.java | 33 + .../dcr/KeycloakClientRegistration.java | 10 + .../impl/KeycloakClientRegistrationImpl.java | 20 +- .../io/vertx/tests/DCRKeycloak25_0_0_IT.java | 86 +- .../src/test/resources/loglive.txt | 29 +- .../test/resources/registration-response.txt | 43 + .../test/resources/vertx-it-dcr-realm.json | 1853 ----------------- 12 files changed, 213 insertions(+), 1912 deletions(-) create mode 100644 vertx-auth-oauth2/src/test/resources/registration-response.txt delete mode 100644 vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json diff --git a/vertx-auth-oauth2/src/main/asciidoc/index.adoc b/vertx-auth-oauth2/src/main/asciidoc/index.adoc index 7fb5d9615..71006652f 100644 --- a/vertx-auth-oauth2/src/main/asciidoc/index.adoc +++ b/vertx-auth-oauth2/src/main/asciidoc/index.adoc @@ -1,6 +1,6 @@ = OAuth2 auth provider -This component contains an out of the box OAuth2 (and to some extent OpenID Connect) relying party implementation. +This component contains an out of the box OAuth2 (and to some extent OpenID Connect) relying on party implementation. To use this project, add the following dependency to the _dependencies_ section of your build descriptor: * Maven (in your `pom.xml`): diff --git a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java index 5865db7ac..a073c9658 100644 --- a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java +++ b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCROptionsConverter.java @@ -1,6 +1,7 @@ package io.vertx.ext.auth.oauth2; import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; /** * Converter and mapper for {@link io.vertx.ext.auth.oauth2.DCROptions}. @@ -16,8 +17,6 @@ static void fromJson(Iterable> json, DCROpti obj.setHttpClientOptions(new io.vertx.core.http.HttpClientOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "resourceUri": - break; case "initialAccessToken": if (member.getValue() instanceof String) { obj.setInitialAccessToken((String)member.getValue()); @@ -45,9 +44,6 @@ static void toJson(DCROptions obj, java.util.Map json) { if (obj.getHttpClientOptions() != null) { json.put("httpClientOptions", obj.getHttpClientOptions().toJson()); } - if (obj.resourceUri() != null) { - json.put("resourceUri", obj.resourceUri()); - } if (obj.getInitialAccessToken() != null) { json.put("initialAccessToken", obj.getInitialAccessToken()); } diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java index 0a555e753..29fd7e77e 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java @@ -1,8 +1,21 @@ +/* + * Copyright (c) 2025 Sanju Thomas + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ package io.vertx.ext.auth.oauth2; import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Future; +/** + * + */ @VertxGen public interface ClientRegistrationProvider { Future create(String clientId); diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java index 9699e96df..9d20ecf30 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2025 Sanju Thomas + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ package io.vertx.ext.auth.oauth2; import io.vertx.codegen.annotations.DataObject; @@ -69,7 +79,7 @@ public void setTenant(String tenant) { } public String resourceUri() { - return String.format("%s/%s/%s", site, tenant, "clients-registrations/default/"); + return String.format("%s/realms/%s/clients-registrations/default/", site, tenant); } } \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java index 8c00eaa6a..194984b57 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2025 Sanju Thomas + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ package io.vertx.ext.auth.oauth2; import io.vertx.codegen.annotations.DataObject; @@ -7,7 +17,15 @@ @DataObject @JsonGen(publicConverter = false) public class DCRRequest { + + /** + * The client id you want to give it to the client you want to create. + */ private String clientId; + + /** + * The token you received when you registered your client with Keycloak. + */ private String registrationAccessToken; public DCRRequest(JsonObject json) { diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java index 37ff7d716..01f854b7a 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2025 Sanju Thomas + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ package io.vertx.ext.auth.oauth2; import io.vertx.codegen.annotations.DataObject; @@ -7,11 +17,34 @@ @DataObject @JsonGen(publicConverter = false) public class DCRResponse { + + /** + * A system generated unique identifier. + */ private String id; + + /** + * User given client identifier. + */ private String clientId; + /** + * Whether the client is currently enabled or not. + */ private boolean enabled; + + /** + * Client authentication type, by default it is client-secret. + */ private String clientAuthenticationType; + + /** + * Client secret for client_secret_post or client_secret_basic. + */ private String secret; + + /** + * RegistrationAccessToken is used for subsequent communication with Keycloak to GET or DELETE the client. + */ private String registrationAccessToken; public DCRResponse(JsonObject json) { diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java index 051aecf2f..b79e8af4d 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2025 Sanju Thomas + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ package io.vertx.ext.auth.oauth2.dcr; import io.vertx.core.Vertx; diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java index 69aa78d77..d23e7e2c9 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2025 Sanju Thomas + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ package io.vertx.ext.auth.oauth2.dcr.impl; import io.vertx.core.Future; @@ -10,16 +20,18 @@ import io.vertx.ext.auth.oauth2.DCRRequest; import io.vertx.ext.auth.oauth2.DCRResponse; import io.vertx.ext.auth.oauth2.dcr.KeycloakClientRegistration; +import java.util.Objects; public final class KeycloakClientRegistrationImpl implements KeycloakClientRegistration { - private final Vertx vertx; private final SimpleHttpClient simpleHttpClient; private final DCROptions dcrOptions; public KeycloakClientRegistrationImpl(Vertx vertx, DCROptions dcrOptions) { - this.vertx = vertx; + Objects.requireNonNull(dcrOptions.getInitialAccessToken(), "initialAccessToken cannot be null"); + Objects.requireNonNull(dcrOptions.getSite(), "site cannot be null"); + Objects.requireNonNull(dcrOptions.getTenant(), "tenant cannot be null"); this.dcrOptions = dcrOptions; this.simpleHttpClient = new SimpleHttpClient(vertx, "dcr-client", dcrOptions.getHttpClientOptions()); } @@ -33,6 +45,8 @@ public Future create(String clientId) { } @Override public Future get(DCRRequest dcrRequest) { + Objects.requireNonNull(dcrRequest.getClientId(), "clientId cannot be null."); + Objects.requireNonNull(dcrRequest.getRegistrationAccessToken(), "registrationAccessToken cannot be null."); JsonObject registrationToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); return simpleHttpClient.fetch(HttpMethod.GET, String.format("%s/%s", dcrOptions.resourceUri(), dcrRequest.getClientId()), registrationToken, null) @@ -41,6 +55,8 @@ public Future get(DCRRequest dcrRequest) { @Override public Future delete(DCRRequest dcrRequest) { + Objects.requireNonNull(dcrRequest.getClientId(), "clientId cannot be null."); + Objects.requireNonNull(dcrRequest.getRegistrationAccessToken(), "registrationAccessToken cannot be null."); JsonObject registrationToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); return simpleHttpClient.fetch(HttpMethod.DELETE, String.format("%s/%s", dcrOptions.resourceUri(), dcrRequest.getClientId()), registrationToken, null) diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java index 45164d25e..4b26be3ef 100644 --- a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java @@ -1,11 +1,31 @@ package io.vertx.tests; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.impl.http.SimpleHttpClient; +import io.vertx.ext.auth.oauth2.DCROptions; +import io.vertx.ext.auth.oauth2.DCRResponse; +import io.vertx.ext.auth.oauth2.dcr.KeycloakClientRegistration; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.RunTestOnContext; import io.vertx.ext.unit.junit.VertxUnitRunnerWithParametersFactory; + +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; @@ -14,55 +34,41 @@ import org.junit.runners.Parameterized; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; import org.testcontainers.containers.wait.strategy.Wait; -@RunWith(Parameterized.class) -@Parameterized.UseParametersRunnerFactory(VertxUnitRunnerWithParametersFactory.class) public class DCRKeycloak25_0_0_IT { - @ClassRule - public static final GenericContainer container = new GenericContainer<>("quay.io/keycloak/keycloak:25.0.0") - .withEnv("KEYCLOAK_USER", "admin") - .withEnv("KEYCLOAK_PASSWORD", "secret") - .withEnv("DB_VENDOR", "H2") - .withExposedPorts(8080, 8443) - .withClasspathResourceMapping("vertx-it-dcr-realm.json", "/tmp/fixtures.json", BindMode.READ_ONLY) - .withCommand("-b", "0.0.0.0", "-Dkeycloak.migration.action=import", "-Dkeycloak.migration.provider=singleFile", "-Dkeycloak.migration.file=/tmp/fixtures.json", "-Dkeycloak.migration.strategy=OVERWRITE_EXISTING") - .waitingFor(Wait.forLogMessage(".*Keycloak.*started.*", 1)); - - - @Parameterized.Parameters - public static List sites() { - return Arrays.asList("http", "https"); - } - @Rule public final RunTestOnContext rule = new RunTestOnContext(); - private final String proto; - private String site; - - public DCRKeycloak25_0_0_IT(String proto) { - this.proto = proto; - } - - @Before - public void setUp() { - switch (proto) { - case "http": - site = proto + "://" + container.getHost() + ":" + container.getMappedPort(8080); - break; - case "https": - site = proto + "://" + container.getHost() + ":" + container.getMappedPort(8443); - break; - default: - throw new IllegalArgumentException("Invalid proto: " + proto); + @Test + public void testCreateDynamicClient() throws Exception { + try ( + GenericContainer keycloak = new GenericContainer<>(DockerImageName.parse("quay.io/keycloak/keycloak:25.0.0")) + .withExposedPorts(8080) + .withEnv("KEYCLOAK_ADMIN", "admin") + .withEnv("KEYCLOAK_ADMIN_PASSWORD", "secret") + .withCommand("start-dev") + .waitingFor( + Wait.forHttp("/realms/master/.well-known/openid-configuration") + .forStatusCode(200) + .withStartupTimeout(Duration.ofSeconds(90)));) { + keycloak.start(); + String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); + System.out.printf("Keycloak is running at %s", baseUrl); + String accessToken = getAdminAccessToken(baseUrl).await(60, TimeUnit.SECONDS); + System.out.println(accessToken); } } - @Test - public void test(TestContext should) { - final Async test = should.async(); - + private Future getAdminAccessToken(String baseUrl) throws Exception { + SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, new HttpClientOptions()); + JsonObject header = new JsonObject().put("Content-Type", "application/x-www-form-urlencoded"); + Buffer body = Buffer.buffer("grant_type=password&client_id=admin-cli&username=admin&password=secret"); + return simpleHttpClient.fetch(HttpMethod.POST, baseUrl + "/realms/master/protocol/openid-connect/token", header, body) + .compose(response -> Future.succeededFuture(response.jsonObject().getString("access_token"))); } + } diff --git a/vertx-auth-oauth2/src/test/resources/loglive.txt b/vertx-auth-oauth2/src/test/resources/loglive.txt index 4e9215289..86a11b956 100644 --- a/vertx-auth-oauth2/src/test/resources/loglive.txt +++ b/vertx-auth-oauth2/src/test/resources/loglive.txt @@ -2,19 +2,28 @@ please stop the keycloak before export export configs: ./kc.sh export --file master.json --realm master to generate initial access token: -kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin +kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin create long live initial access token: kcadm.sh create clients-initial-access -s expiration=157788000 -o +mvn clean install -PIT +mvn clean install -PIT -Dtest=io.vertx.tests.DCRKeycloak25_0_0_IT +first, tunnel the traffic from remote devser to local +ssh -N -L 9090:127.0.0.1:8080 user@devserver.local +open http://localhost:9090/ -{ - "id" : "031d7068-4a52-4e76-b0f4-d3ae9ef575a8", - "token" : "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxZmI0MjFhNy04ZGZjLTQ1OTctOTM1Ni01MGEwNTA3YjM4MmEifQ.eyJleHAiOjE5MTYyMDQ0NzEsImlhdCI6MTc1ODQxNjQ3MSwianRpIjoiMDMxZDcwNjgtNGE1Mi00ZTc2LWIwZjQtZDNhZTllZjU3NWE4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsInR5cCI6IkluaXRpYWxBY2Nlc3NUb2tlbiJ9.eRVfRpmKrGsaq2FowQDfMysOXdsS7rBeoB5UYkANQNHVDPoNxooEbr-MF7MPrkEMsY-Z4IAlnsCjQ837HpZbnA", - "timestamp" : 1758416471, - "expiration" : 157788000, - "count" : 1, - "remainingCount" : 1 -} +imagename for keycloak: 14 trusting_gates 24 recursing_meitner 25 naughty_joliot friendly_hamilton +docker logs --since 5m trusting_gates -mvn clean install -PIT +docker run \ + -p 8080:8080 \ + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=secret \ + -v $(pwd)/keycloak-config:/opt/keycloak/data/import \ + quay.io/keycloak/keycloak:25.0.0 \ + start-dev --import-realm + +list all containers: +docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}" +docker logs -f jolly_knuth \ No newline at end of file diff --git a/vertx-auth-oauth2/src/test/resources/registration-response.txt b/vertx-auth-oauth2/src/test/resources/registration-response.txt new file mode 100644 index 000000000..6bfb65c08 --- /dev/null +++ b/vertx-auth-oauth2/src/test/resources/registration-response.txt @@ -0,0 +1,43 @@ +{ + "id": "b491a869-d755-487c-acea-26b8a184543c", + "clientId": "token_test", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "WUpB8PJ4U1VTTsmzD3tLdbIDfAhhiKWo", + "registrationAccessToken": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxZmI0MjFhNy04ZGZjLTQ1OTctOTM1Ni01MGEwNTA3YjM4MmEifQ.eyJleHAiOjAsImlhdCI6MTc1ODk5ODc2NywianRpIjoiNTNmMDA2NjMtMjI2NC00OTRkLTg2MjItZjViNGE4Y2I2M2E1IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsInR5cCI6IlJlZ2lzdHJhdGlvbkFjY2Vzc1Rva2VuIiwicmVnaXN0cmF0aW9uX2F1dGgiOiJhdXRoZW50aWNhdGVkIn0.fQs2OrSsEUfDJvDZnmGsgHXtyECsyLaIm3Cau9-I4iRjCkR9jOkX7pJ13Il9gH9avaNl0b8AF9Y1N3SUx5iVow", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": "1758998767" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] +} \ No newline at end of file diff --git a/vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json b/vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json deleted file mode 100644 index bf7326a61..000000000 --- a/vertx-auth-oauth2/src/test/resources/vertx-it-dcr-realm.json +++ /dev/null @@ -1,1853 +0,0 @@ -{ - "id" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", - "realm" : "master", - "displayName" : "Keycloak", - "displayNameHtml" : "
Keycloak
", - "notBefore" : 0, - "defaultSignatureAlgorithm" : "RS256", - "revokeRefreshToken" : false, - "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 60, - "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" : 5, - "enabled" : true, - "sslRequired" : "external", - "registrationAllowed" : false, - "registrationEmailAsUsername" : false, - "rememberMe" : false, - "verifyEmail" : false, - "loginWithEmailAllowed" : true, - "duplicateEmailsAllowed" : false, - "resetPasswordAllowed" : false, - "editUsernameAllowed" : false, - "bruteForceProtected" : false, - "permanentLockout" : false, - "maxTemporaryLockouts" : 0, - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "roles" : { - "realm" : [ { - "id" : "7716301c-6991-4562-abbd-5ce775bb1b19", - "name" : "offline_access", - "description" : "${role_offline-access}", - "composite" : false, - "clientRole" : false, - "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", - "attributes" : { } - }, { - "id" : "66f62a2b-f1b7-4429-ab49-16d15535ab89", - "name" : "create-realm", - "description" : "${role_create-realm}", - "composite" : false, - "clientRole" : false, - "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", - "attributes" : { } - }, { - "id" : "39fb4301-b1d7-4f31-983f-c4ee62f60228", - "name" : "admin", - "description" : "${role_admin}", - "composite" : true, - "composites" : { - "realm" : [ "create-realm" ], - "client" : { - "master-realm" : [ "manage-realm", "manage-users", "view-authorization", "create-client", "query-groups", "view-events", "view-identity-providers", "manage-events", "manage-clients", "query-users", "view-clients", "query-clients", "view-users", "manage-authorization", "query-realms", "manage-identity-providers", "view-realm", "impersonation" ] - } - }, - "clientRole" : false, - "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", - "attributes" : { } - }, { - "id" : "5167eed8-7d3d-41d2-839d-daf26f681c3e", - "name" : "default-roles-master", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "manage-account", "view-profile" ] - } - }, - "clientRole" : false, - "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", - "attributes" : { } - }, { - "id" : "4a3f17bf-a2dd-4356-a3ae-08b8200385de", - "name" : "uma_authorization", - "description" : "${role_uma_authorization}", - "composite" : false, - "clientRole" : false, - "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2", - "attributes" : { } - } ], - "client" : { - "security-admin-console" : [ ], - "admin-cli" : [ ], - "account-console" : [ ], - "broker" : [ { - "id" : "0fc968b8-8468-47ef-9c92-e4463068eca5", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false, - "clientRole" : true, - "containerId" : "a1595a1f-3858-4f85-bd80-d4dda54b4405", - "attributes" : { } - } ], - "master-realm" : [ { - "id" : "2065332e-8851-4209-b2e9-d864d1b042bb", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "f3cb2a4d-9e09-4a2f-bfaa-9305ef366e12", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "b50015e7-54f7-4c1a-923b-a52cd8e6b41e", - "name" : "view-authorization", - "description" : "${role_view-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "637800b5-4b18-4e43-b6fa-316ec1a01524", - "name" : "create-client", - "description" : "${role_create-client}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "c801f85f-7882-46ac-8492-7113fa5bec31", - "name" : "query-groups", - "description" : "${role_query-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "8b6ca459-a2d4-4247-ac68-0a9d8b88afca", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "a236dc7c-0ac8-41aa-b724-d2a0bcfe6325", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "7be47ced-47a6-4847-9c64-8feb998b2421", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "bd6645b8-483a-4ce5-941f-c3042187224d", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "8cd00641-0dbd-4694-89c8-23ec68574dcc", - "name" : "query-users", - "description" : "${role_query-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "6feba329-356c-47b1-9a51-a7008a99333b", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : true, - "composites" : { - "client" : { - "master-realm" : [ "query-clients" ] - } - }, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "426cacd6-f801-4734-9f1a-141e4035a385", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "ac500a5b-24a7-4404-b0e5-fb987ec9eb1f", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "master-realm" : [ "query-users", "query-groups" ] - } - }, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "60daff22-350a-4ee2-ae84-9001acf618b1", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "f4b8086d-bb02-455d-8575-06bd53d3dbd0", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "46693d4f-33ec-48f1-8268-daf5300f4488", - "name" : "query-realms", - "description" : "${role_query-realms}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "d684ef77-1b65-479b-80b9-ceda51af0bb9", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - }, { - "id" : "009a8ad2-0ccd-4741-b8f4-410abeae2859", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "d02b948a-3614-4daa-9014-b36481f77f59", - "attributes" : { } - } ], - "account" : [ { - "id" : "89646bb0-11ab-4e7d-9f68-ee1f5091cc07", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } - }, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - }, { - "id" : "ac75ba01-bc48-4dc7-a69a-0e2f704acd7d", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", - "composite" : false, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - }, { - "id" : "8e871d80-419b-4084-bf3e-8599db79fcdd", - "name" : "delete-account", - "description" : "${role_delete-account}", - "composite" : false, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - }, { - "id" : "b6bf4c36-5c0b-4b4e-bb22-b8b1bc662175", - "name" : "view-applications", - "description" : "${role_view-applications}", - "composite" : false, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - }, { - "id" : "2bd41666-1cc4-482e-bae8-6aa55106ab20", - "name" : "view-consent", - "description" : "${role_view-consent}", - "composite" : false, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - }, { - "id" : "6e0a0cbd-1e61-42ab-928e-89e49991d437", - "name" : "manage-consent", - "description" : "${role_manage-consent}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "view-consent" ] - } - }, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - }, { - "id" : "ad8c9f11-3fb2-464f-95b3-f97335618c34", - "name" : "view-groups", - "description" : "${role_view-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - }, { - "id" : "e1b0c173-0ca2-4a58-93f6-d0fb6cda2eda", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false, - "clientRole" : true, - "containerId" : "42643532-6734-41ea-b45c-ae5c1c679915", - "attributes" : { } - } ] - } - }, - "groups" : [ ], - "defaultRole" : { - "id" : "5167eed8-7d3d-41d2-839d-daf26f681c3e", - "name" : "default-roles-master", - "description" : "${role_default-roles}", - "composite" : true, - "clientRole" : false, - "containerId" : "c3653b34-3c58-421d-bd05-feb5e7a76cd2" - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], - "localizationTexts" : { }, - "webAuthnPolicyRpEntityName" : "keycloak", - "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyRpId" : "", - "webAuthnPolicyAttestationConveyancePreference" : "not specified", - "webAuthnPolicyAuthenticatorAttachment" : "not specified", - "webAuthnPolicyRequireResidentKey" : "not specified", - "webAuthnPolicyUserVerificationRequirement" : "not specified", - "webAuthnPolicyCreateTimeout" : 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyAcceptableAaguids" : [ ], - "webAuthnPolicyExtraOrigins" : [ ], - "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyPasswordlessRpId" : "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", - "webAuthnPolicyPasswordlessCreateTimeout" : 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], - "webAuthnPolicyPasswordlessExtraOrigins" : [ ], - "users" : [ { - "id" : "39dc37fd-c4a4-4179-a7a6-1e06139c1685", - "username" : "admin", - "emailVerified" : false, - "createdTimestamp" : 1758416373805, - "enabled" : true, - "totp" : false, - "credentials" : [ { - "id" : "ada7b84e-0c91-4b49-93b6-be468a7dd89d", - "type" : "password", - "createdDate" : 1758416373919, - "secretData" : "{\"value\":\"O0aeKu270eMjzoxdzHPaIFACxcqs2CHNDI42WlwO9y0=\",\"salt\":\"wPaChOD0tkbqJBUaGJdjVA==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "admin", "default-roles-master" ], - "notBefore" : 0, - "groups" : [ ] - } ], - "scopeMappings" : [ { - "clientScope" : "offline_access", - "roles" : [ "offline_access" ] - } ], - "clientScopeMappings" : { - "account" : [ { - "client" : "account-console", - "roles" : [ "manage-account", "view-groups" ] - } ] - }, - "clients" : [ { - "id" : "42643532-6734-41ea-b45c-ae5c1c679915", - "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" : { - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "b559d8a4-0226-4dd2-a9ce-973e0a5fbaa8", - "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" : { - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "5b164e92-b9fc-4a2e-a9e0-3ad276591b48", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "59b4b024-933e-406f-89d1-1907cf9f607d", - "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", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "a1595a1f-3858-4f85-bd80-d4dda54b4405", - "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", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "d02b948a-3614-4daa-9014-b36481f77f59", - "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", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "24d596da-de44-433e-a0de-1e5a041e2817", - "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" : { - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "2f5446e2-f1b1-4b24-a71c-2dff507cd63b", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - } ], - "clientScopes" : [ { - "id" : "c8a82573-e3b5-4276-9228-3e4f22b020eb", - "name" : "phone", - "description" : "OpenID Connect built-in scope: phone", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${phoneScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "0540e220-e6bf-4d72-8729-994842eec4a6", - "name" : "phone number", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumber", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number", - "jsonType.label" : "String" - } - }, { - "id" : "fff0d674-c9ab-48f4-8646-8c262fe5e448", - "name" : "phone number verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumberVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" - } - } ] - }, { - "id" : "ceff1ce5-6bf2-43c8-b1e7-477e3a36b5e5", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${emailScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "767307bc-77a8-4407-a03c-c0a1b31744bd", - "name" : "email verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "9cc89c6a-d0f5-4549-89c6-43919675ad1c", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "f20b7dea-8c40-4b4f-97ed-77bfd826ab0d", - "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", - "consent.screen.text" : "", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "6f4f8582-d3c7-452c-892e-d45aea242236", - "name" : "allowed web origins", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "e2c4b2fc-fd81-4c67-815e-8c9126616917", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "consent.screen.text" : "${rolesScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "4125708a-9667-4388-b9ed-e6457d5fac3f", - "name" : "client roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "ed694a3a-54f8-479c-9156-96e5c25c757a", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "2f626cd6-6556-4785-8b7e-8ffb743de5ab", - "name" : "realm roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - } ] - }, { - "id" : "e83aea7b-5451-400f-b309-9614e9f920e9", - "name" : "profile", - "description" : "OpenID Connect built-in scope: profile", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${profileScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "fb6205b9-8c27-4da8-8579-e3b970638adb", - "name" : "profile", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "profile", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "profile", - "jsonType.label" : "String" - } - }, { - "id" : "df160bdf-f419-4f0e-a885-79183b8e7793", - "name" : "updated at", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" - } - }, { - "id" : "8c1c0e8a-c07d-4d5c-a3b7-45e9c05eb5e3", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "99bfb11b-5699-42a1-a206-741bc65c91c8", - "name" : "picture", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "picture", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "picture", - "jsonType.label" : "String" - } - }, { - "id" : "01989967-6e93-4197-89eb-5cbb9bd1d8b3", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "f43d799b-7328-4cce-853e-089d457b0276", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - }, { - "id" : "21bba731-7ef6-413e-9036-402319d35f41", - "name" : "gender", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "gender", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "gender", - "jsonType.label" : "String" - } - }, { - "id" : "66901781-04e6-462e-bef9-b19f06bae8c5", - "name" : "middle name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "middleName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "middle_name", - "jsonType.label" : "String" - } - }, { - "id" : "c7eec4d9-47fd-4881-bb42-7416495ab2e1", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "e5ff0af2-beb0-4cb7-90c4-e68638ded86e", - "name" : "website", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "website", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "website", - "jsonType.label" : "String" - } - }, { - "id" : "53a913ed-e264-4039-9fcb-68c368dcd5db", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "119e76e4-30c6-45a0-a4e1-f0905f62b029", - "name" : "zoneinfo", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "zoneinfo", - "jsonType.label" : "String" - } - }, { - "id" : "b18d8358-cbfb-431a-81ab-e89679932d81", - "name" : "birthdate", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" - } - }, { - "id" : "d7028221-398e-45f0-83e1-9f979515dd0f", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "true" - } - } ] - }, { - "id" : "a4189f9b-fce4-46da-8eef-978e4626e8ab", - "name" : "role_list", - "description" : "SAML role list", - "protocol" : "saml", - "attributes" : { - "consent.screen.text" : "${samlRoleListScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "06b6d147-5a3c-48db-9c1f-094c4ee90da8", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - } ] - }, { - "id" : "4882fd8c-f362-40c3-9e82-68175026673b", - "name" : "acr", - "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "de209b11-aa1f-4c24-844a-e06dc5bd69b2", - "name" : "acr loa level", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "56f75ee1-d6a1-49a8-88ce-1022ff736a91", - "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" : "51f512a5-a4a5-4d30-a382-9fe98c4d5533", - "name" : "basic", - "description" : "OpenID Connect scope for add all basic claims to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "63a432bd-a932-4537-b14d-427665f48aca", - "name" : "sub", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-sub-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "4ada5faa-7bf0-4c09-a444-c0ca00669bdc", - "name" : "auth_time", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "AUTH_TIME", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "auth_time", - "jsonType.label" : "long" - } - } ] - }, { - "id" : "b737ee4c-f4a8-40ac-9fc8-b2deaff5fa43", - "name" : "address", - "description" : "OpenID Connect built-in scope: address", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${addressScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "c8ea684d-bfe7-4d16-8660-576e1ee390a8", - "name" : "address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-address-mapper", - "consentRequired" : false, - "config" : { - "user.attribute.formatted" : "formatted", - "user.attribute.country" : "country", - "introspection.token.claim" : "true", - "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" : "68d49e3f-d86b-4bea-91d0-1ea66ed1bebc", - "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" : "8369f077-09d5-4e2e-ad03-0125def41789", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "upn", - "jsonType.label" : "String" - } - }, { - "id" : "e74c8a8d-5ac0-439e-a0e6-fdc93e730aa9", - "name" : "groups", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", - "user.attribute" : "foo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" - } - } ] - } ], - "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic" ], - "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], - "browserSecurityHeaders" : { - "contentSecurityPolicyReportOnly" : "", - "xContentTypeOptions" : "nosniff", - "referrerPolicy" : "no-referrer", - "xRobotsTag" : "none", - "xFrameOptions" : "SAMEORIGIN", - "xXSSProtection" : "1; mode=block", - "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "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" : "3a5e2609-cfd8-49b4-82b0-58654bc1111f", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "79241dac-7387-40db-bec6-f4879ead3094", - "name" : "Consent Required", - "providerId" : "consent-required", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "ca6ecdc9-704b-4064-986c-e156e3606b48", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] - } - }, { - "id" : "76053469-e02e-4de1-b085-8f1fb4ff31fb", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "55da18d7-355b-4ee3-8cd9-528d5d634a0e", - "name" : "Max Clients Limit", - "providerId" : "max-clients", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "max-clients" : [ "200" ] - } - }, { - "id" : "c27a136f-268e-4347-a36d-17e890511808", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper" ] - } - }, { - "id" : "1c400270-6444-421d-bfe8-0d2005d6d22e", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "1ccbdd23-5dac-4f3d-8991-47d80e035bf8", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper" ] - } - } ], - "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "c959e8ed-d656-4f33-97d9-c1f97be831ba", - "providerId" : "declarative-user-profile", - "subComponents" : { }, - "config" : { - "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] - } - } ], - "org.keycloak.keys.KeyProvider" : [ { - "id" : "9b389689-2a59-4d69-94d4-6765533730d9", - "name" : "rsa-generated", - "providerId" : "rsa-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEowIBAAKCAQEAwgxpQ5hcXQ/ehlHGKDMjQpHoQAOxdcMDv3mYCsjA/yA+zQwusjnrMP4FSI4QLTXxYd0ySiWdKy0s83jF2CtpWKDCyL2Jm6it0pV1l7d0uJK6sqo8KHOd3IYMzKEB8VkOxChLhxskdgy+thPKEAVCuY+PO6Py+c+33TRU7kLpcGxElf6TxEfvPEIi4QqWhUVJx5nbhHflzEoHRElwMg/yP2WFam3mK323nMoaCT6wAA3sfrK2Fxg/3VlcFHis3eeDPZ8/MDzJyLMDxP+jyKzuN6zgVjMFC0YzYVg8Z8+Wyk04b1eMDwOAQt50Wz0kVjeL76SRlw4+N2m3PVrkpe2ILQIDAQABAoIBAB3lrkzigKM7UAb/OmSfMbjj+hXte74FGeK6Z+6MF7rhy6CUAUMnIVF0ZyP9SgjCiDo9IQPDin+d1OXTnx07MwlGBjzKLGrLMQeCTOffNXXqmtFENpiU7Pw5EGfGtsyUnW3ON4oivcS3xKUQ+iTrFT4PHFENZYuPsWhnfl5nTh1/4SNB2TyJqoK/kEVJthq+lm8rbSTtIM5g30OnL5Cs1UlUvxqTxS1VaKkqkvTv7/W2u6R5iYg1EAIYxCBDlvqUk4yaSuTdQc5nom4mkpfl9mC9lHW099Dh4TpVR0YZ43S7ya9m40X5S6qA8aSXJugdzLmLJVI+mqF8FCjdOGSizZECgYEA+B8nFDkyfcV9y8nl/NSOJtRQbaL+dIBfk59TRotSvcXzwDTZxkd0DxNt0Hhp5kFMRHEQ6RSUwnVHX9VhZbAELsCKACMJc5KHIMhzZzTX1Um9hDeq2nafKVlVB8Xq8lOv2WXafJSHKuWU5z0aLwu0s0R1Qu1cdToA5JgcOvHB8ScCgYEAyDW6AiLtPX8HvK8+8wYFF9oOMjUMcoaoscwUIhUq6vZ4A1vW/udRnGc0KJXLb3IfF0ocoOsoHILKMj1XHqkVCTXYLLearSoztUWKJ9xCzWA6efPm+4u4Do5A5DpJiuiN7dAs+lQ0bIaDWlarxuVzv5GZJL58a3N8n9BPbohyqIsCgYBInH1IOtDe46vDtqsaiW2sSVfcLeX/XA4cWnT5YQ5uwP5rUkwk7YY2Xz400vhSzOsv4FLNkxtRqRahIgXlx1QCBpCcJ6S9cLUgz+iN3HYJUhInHk9erXY4mI78vlvaXMlKercs3B6OXC9uWIBQIkHYcIFMe8AiAj5+p1EjotpZ4wKBgGE2pUEQUH3BxrH5rek3DRYNzULXu1mIxTS/0S7TdGluj8uhtn1DOL9m5U6nnYZZtXsLg2cnh+yAHUnTuw+qLgqw2GyuIi7fBQiJ4ZGFQ/KRPROmOIv/xblbnWq0Wq1WSkQlBxHt/R6NGg6tcu86zr9AJUpQK/3Ir8Sx0z/qhTINAoGBALgDo8vCvYtPgra4eaSU67UFYMXdN6vwgx7KyI+nxks75tpLC00js+CvWc9kcEaf7Aa1qSovho8HfNjZHvrsPCgZLYxPPuJr8w0RKr3hh8IfAq9AgB3kIQ8VAmdaf8PKbv07vbER50xqZFG6bqZBbEm9GCQtm1IT6ngMRUn/HVqH" ], - "keyUse" : [ "SIG" ], - "certificate" : [ "MIICmzCCAYMCBgGZacdO4zANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwOTIxMDA1NjMzWhcNMzUwOTIxMDA1ODEzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCDGlDmFxdD96GUcYoMyNCkehAA7F1wwO/eZgKyMD/ID7NDC6yOesw/gVIjhAtNfFh3TJKJZ0rLSzzeMXYK2lYoMLIvYmbqK3SlXWXt3S4krqyqjwoc53chgzMoQHxWQ7EKEuHGyR2DL62E8oQBUK5j487o/L5z7fdNFTuQulwbESV/pPER+88QiLhCpaFRUnHmduEd+XMSgdESXAyD/I/ZYVqbeYrfbecyhoJPrAADex+srYXGD/dWVwUeKzd54M9nz8wPMnIswPE/6PIrO43rOBWMwULRjNhWDxnz5bKTThvV4wPA4BC3nRbPSRWN4vvpJGXDj43abc9WuSl7YgtAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEXGyf+4kQqgfKhK8GcK14qG1AbKQQXi31JvAKN2OLOj80f283HcbgUFKFnZ18fh56tJrvr+TfqYCDLNQq0x3oYbhHrMkG23HUT7Cp/Mzi4Gajr1DloFlsDkMn3NQq62Dd5UCV9K9PmqcP2KIyEnXNPgICrFOVzbYN2fbordvC0u/LvSq83/fohGBk9T1bdPMoNFApwrX9sL9zd25gGFyEvlpCSUcoX5Zsgi7rg6M3mKxBorvBXbJKYm8tnodPnWBO7YXhnNmOZbNEMxx48W+DXfp12OacAKp84bHdsM1HJPB5dfpaqAiZmvO/j58mU/CU1xxjXCmry6nRbUG1ez7LQ=" ], - "priority" : [ "100" ] - } - }, { - "id" : "9b2fed93-48ed-4346-b766-146026ea64c8", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEpAIBAAKCAQEAz7pGgi5Mehau/0s0sztHUBxr7+Eqdd0FlSbGgBlgC0tzsurd9ueL8bnCdVYc3DFYbZ8RySqZ6LkLHlpU1lKMMvU2jrhhDT5HQjwmSjHhdArYMZN5LmkTMUCrZvTPvlWtTVYtvdNdn+SPqm5u7BqWSPCO00cBbnJs5l2KVDSOD+kNXvt1dOLORwZ5nSxfaEUJeE3+3wspib87XADDSAmkmC4Jv0UwucRc8qGgdePzDBtHOsc69MYNvqV4ukOwYp43lbsp/iMuUaq5AApgmx5QpAWzktAH24cFC/MsaqevUdunMDcDR4BNZnv4Evtme629FCUZv5IEcaShidjvAxo7sQIDAQABAoIBAADERFFMCN+5VRjrd4R73XXR6HEAXUXIJTAVSHhcbwWZk2c7brxYK6Jluv7rLvQadtqFiT8Exy+7/Kh42DqNymwQbcDQX2nrIHiXHqrfdBb/4D8wclNkqeeSax0ow/IAO8iciYuWsI3HshKHATQdOqxNq1s0NkPHhoBP736bKKSDEZW/S/0FmYlKgzh6+bq4Vo3m7j5/jw972VQjhz7TqY4QauIJ/lAfUp7rjhlwnZ7EXlNox11PNT0j1X1q+tTl2KPtlZlUKBCMoNOCOJH6H1Jfo1zkezC0qo/mZnTTgi79yO1V7w7u4ewcQUO6+iNeE6+k+f5wgtW81/tHBlqKYh0CgYEA6vU4vDAZfvXTaUoCk0fKiqSufbxGfHQ+vFUHrq5zCuo3fO6Fr3IlAVj7rgdWtB7ADfBYPMln2jo85J0/3CLI9vPzo9TWFS7IfsxZ31F7pJ5cUq8asOC6R8i7jbNlwgw0KY11O5D0BSjjhGyIHZ3/0cd/unXdujMo651Da/OFhW0CgYEA4lTB5WmjaUjba/DXFKnq858GHsClSzUiL3xKafZm2n2rYgHFI8Plo69p5I+Gm0bwP7qv8zpLdkSLbSW9SmrXnO4Y+JFhBrXc14AgffEvOkzAqFOy5nTj9KtILUdfCp9imywSZcmQOksRnxGrO8z6cqKuwE54s3c9ptdrZpKAGNUCgYEAml8htqtXpQ34qD3jMUdC8RLAPoZcPVrW+UBYutGNbodyi5ffp+U0IQ2I1s+ljNSDVOUr5Exj+oK3DXGvyn/kUDT6SBMH0YUaYAz9o3dS6FbPy9eWjs4oNg/SAojy+6qu/vodI1TaryUtTw1L5UGxGfCcoDLeyn7gjOWXra6jdIUCgYEAzyPbQWdpU1aG20dU0Wihy83jAAo4PZinbOZIBKqqf5U8YTpVfuIvLZvBQpnJDBvkja8CkYzsu5UINiCwGYenRczztar4LfgL4n81PY6bNrRALs5LkXexmZ+ZMzd62HH/AR1YySeshG1n9zloihaTsI8LeJ1ZAodDOYs2lG3IJvECgYAuAghxnjnR44H14t54sKAJ72tXfOeizyHFGDJVSL6+OUs4GoxG1ECBiJScu7xXUEl+hsPFnClmhvkd1lUWHkW3QBJxROnzA3nb4gnHBpHLzA2Sadc1KnEC+KOCbopjYnWruLXHv90apctdEvSqCcwBsQkAxFGJjAp7ZvRPpAXTDg==" ], - "keyUse" : [ "ENC" ], - "certificate" : [ "MIICmzCCAYMCBgGZacdP+jANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwOTIxMDA1NjMzWhcNMzUwOTIxMDA1ODEzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPukaCLkx6Fq7/SzSzO0dQHGvv4Sp13QWVJsaAGWALS3Oy6t3254vxucJ1VhzcMVhtnxHJKpnouQseWlTWUowy9TaOuGENPkdCPCZKMeF0Ctgxk3kuaRMxQKtm9M++Va1NVi29012f5I+qbm7sGpZI8I7TRwFucmzmXYpUNI4P6Q1e+3V04s5HBnmdLF9oRQl4Tf7fCymJvztcAMNICaSYLgm/RTC5xFzyoaB14/MMG0c6xzr0xg2+pXi6Q7BinjeVuyn+Iy5RqrkACmCbHlCkBbOS0AfbhwUL8yxqp69R26cwNwNHgE1me/gS+2Z7rb0UJRm/kgRxpKGJ2O8DGjuxAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAMYctnHyhV63/C5Xwd3pYEwaLK3UnbXvVNByecb95QtBF9x+0N0pc7R0zpQrqkEuBGzaNZ4RtkdPGQd1uIdYs9PeM4SxTvo9mIueFE738GIImTjZeeEkRggWk8vexcyRhIO+WLa2RNAqqfWuFvYBVFfKT43jR0iewc45q6BW85YoC+PZdevHPtgRtSXGOMUGQeOL7iKdQ9M8wzx5SHSDzgxTpQResxNILpvaCgyWIvWk+JEH7aA2pkGuqzoIGqThSslLBONXzvmpZtJFmksS26lvrEBcFcA8SWx/V1flOSzKYmWR86W6ef/XgG498tkiLMLG0XQkENXMpjS0H55Nw0g=" ], - "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] - } - }, { - "id" : "fe588f97-51b2-4125-8ce6-63e3ad1ba627", - "name" : "hmac-generated-hs512", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "1fb421a7-8dfc-4597-9356-50a0507b382a" ], - "secret" : [ "P4HTBOu7g5apeUjxCRwHjGWaB2R356zpucQWr39_3g_4Ly423xWx5gfBDiRQ7C-okp2-am95t6W1ackWeE3ANltCIRNllglwz6sJ8K5H6a3XGdFikb1rRXjXvDfA7Psv7-iHBUzxtNFo-7t6yq22jw0BQwmy4B-FSdh1qw_FI4g" ], - "priority" : [ "100" ], - "algorithm" : [ "HS512" ] - } - }, { - "id" : "fc403847-be4c-43af-92d1-786f3768beb7", - "name" : "aes-generated", - "providerId" : "aes-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "9d7cd70d-7320-4490-923c-b0322c37fdf6" ], - "secret" : [ "KXBTjGK_MOQUFoTBOH1aVA" ], - "priority" : [ "100" ] - } - } ] - }, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "id" : "df4eada7-a7c3-4e72-85fd-18e8af604f79", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Verify Existing Account by Re-authentication", - "userSetupAllowed" : false - } ] - }, { - "id" : "b80cd966-7189-4614-ad3d-ddc94a9c1231", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "d360138e-47f9-4128-82f5-fd362f033e53", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "7a3f62bb-1cb0-4cfb-9c51-c774d7397ed5", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "a2fb1bca-84d1-4ceb-a129-cca590898826", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Account verification options", - "userSetupAllowed" : false - } ] - }, { - "id" : "8749b9ee-ba24-4ffc-b7ef-daffdd67860f", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "c570398a-915c-4892-9087-71154c0b6492", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Handle Existing Account", - "userSetupAllowed" : false - } ] - }, { - "id" : "c73f5a22-9c0f-4498-bd28-a30db04f4c2a", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "First broker login - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "b3152584-2f35-46ed-9956-0859489c96da", - "alias" : "browser", - "description" : "browser based authentication", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-cookie", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "identity-provider-redirector", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 25, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "forms", - "userSetupAllowed" : false - } ] - }, { - "id" : "94595f44-ec8d-48b4-a427-502963b2b966", - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-secret-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-x509", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 40, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "acbd8976-d3ac-406a-ad25-dc677b00fe86", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "Direct Grant - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "3149a669-8033-44ea-a449-f04ae44f465d", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "60ad2537-26fe-4998-9641-69dfccb7d430", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "User creation or linking", - "userSetupAllowed" : false - } ] - }, { - "id" : "9eecad10-1b67-4986-9409-6d07bb411e70", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Browser - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "9b7a26a4-557c-4b81-a054-4441279ff88f", - "alias" : "registration", - "description" : "registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : true, - "flowAlias" : "registration form", - "userSetupAllowed" : false - } ] - }, { - "id" : "2376188e-ab6d-4eed-a24e-e4ad1a63daea", - "alias" : "registration form", - "description" : "registration form", - "providerId" : "form-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-password-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 50, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-recaptcha-action", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 60, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-terms-and-conditions", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 70, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "d4d5674d-d1f3-4786-8c3f-157587b20334", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-credential-email", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 40, - "autheticatorFlow" : true, - "flowAlias" : "Reset - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "33d8ccd5-fc53-4e14-ae82-ed8aa7fa033b", - "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, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - } ], - "authenticatorConfig" : [ { - "id" : "8b6781b7-f19e-4f03-acb0-fb0e8f2f3c9d", - "alias" : "create unique user config", - "config" : { - "require.password.update.after.registration" : "false" - } - }, { - "id" : "404beb50-b053-4a3c-b0bc-1513b1a4e51b", - "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" : "webauthn-register", - "name" : "Webauthn Register", - "providerId" : "webauthn-register", - "enabled" : true, - "defaultAction" : false, - "priority" : 70, - "config" : { } - }, { - "alias" : "webauthn-register-passwordless", - "name" : "Webauthn Register Passwordless", - "providerId" : "webauthn-register-passwordless", - "enabled" : true, - "defaultAction" : false, - "priority" : 80, - "config" : { } - }, { - "alias" : "VERIFY_PROFILE", - "name" : "Verify Profile", - "providerId" : "VERIFY_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 90, - "config" : { } - }, { - "alias" : "delete_credential", - "name" : "Delete Credential", - "providerId" : "delete_credential", - "enabled" : true, - "defaultAction" : false, - "priority" : 100, - "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", - "firstBrokerLoginFlow" : "first broker login", - "attributes" : { - "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaExpiresIn" : "120", - "cibaAuthRequestedUserHint" : "login_hint", - "parRequestUriLifespan" : "60", - "cibaInterval" : "5", - "realmReusableOtpCode" : "false" - }, - "keycloakVersion" : "25.0.0", - "userManagedAccessAllowed" : false, - "organizationsEnabled" : false, - "clientProfiles" : { - "profiles" : [ ] - }, - "clientPolicies" : { - "policies" : [ ] - } -} \ No newline at end of file From 3a795c74ce2f891956fe6a60c11d14d4d7b62956 Mon Sep 17 00:00:00 2001 From: sanjuthomas Date: Sun, 28 Sep 2025 21:07:54 +0000 Subject: [PATCH 3/4] adding integration test cases --- .gitignore | 2 +- .../java/examples/AuthCommonExamples.java | 2 +- .../src/main/asciidoc/index.adoc | 33 +- .../ext/auth/oauth2/DCRResponseConverter.java | 24 +- .../java/examples/AuthOAuth2Examples.java | 451 ++++++++++-------- .../oauth2/ClientRegistrationProvider.java | 3 + .../io/vertx/ext/auth/oauth2/DCROptions.java | 3 +- .../io/vertx/ext/auth/oauth2/DCRRequest.java | 2 + .../io/vertx/ext/auth/oauth2/DCRResponse.java | 35 +- .../io/vertx/ext/auth/oauth2/OAuth2Auth.java | 1 - .../impl/KeycloakClientRegistrationImpl.java | 30 +- .../io/vertx/tests/DCRKeycloak25_0_0_IT.java | 161 +++++-- .../java/io/vertx/tests/DCROptionsTest.java | 46 ++ .../java/io/vertx/tests/DCRRequestTest.java | 35 ++ .../java/io/vertx/tests/DCRResponseTest.java | 46 ++ .../src/test/resources/loglive.txt | 29 -- .../test/resources/registration-response.txt | 43 -- 17 files changed, 630 insertions(+), 316 deletions(-) create mode 100644 vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java create mode 100644 vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java create mode 100644 vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java delete mode 100644 vertx-auth-oauth2/src/test/resources/loglive.txt delete mode 100644 vertx-auth-oauth2/src/test/resources/registration-response.txt diff --git a/.gitignore b/.gitignore index dbd874932..bdc3d43c8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ src/main/java/io/vertx/java/**/*.java src/main/groovy/io/vertx/groovy/**/*.groovy *.swp node_modules - +.vscode diff --git a/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java b/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java index c1767f6b0..80438cea4 100644 --- a/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java +++ b/vertx-auth-common/src/main/java/examples/AuthCommonExamples.java @@ -122,4 +122,4 @@ public void example9(User user) { String sub = user.get("sub"); } } -} \ No newline at end of file +} diff --git a/vertx-auth-oauth2/src/main/asciidoc/index.adoc b/vertx-auth-oauth2/src/main/asciidoc/index.adoc index 71006652f..3aeb18541 100644 --- a/vertx-auth-oauth2/src/main/asciidoc/index.adoc +++ b/vertx-auth-oauth2/src/main/asciidoc/index.adoc @@ -1,6 +1,6 @@ = OAuth2 auth provider -This component contains an out of the box OAuth2 (and to some extent OpenID Connect) relying on party implementation. +This component contains an out of the box OAuth2 (and to some extent OpenID Connect) relying party implementation. To use this project, add the following dependency to the _dependencies_ section of your build descriptor: * Maven (in your `pom.xml`): @@ -400,3 +400,34 @@ In such event the server will start emitting tokens with a different kid than th ---- A special note on this is that if a user will send many requests with a missing key, your handler should throttle the calls to refresh the new key set, or you might end up DDoS your IdP server. + +== Dynamic Client Registration (RFC 7591) +[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) was written to standardize the process of client registration in OAuth 2.0 and OpenID Connect ecosystems to address the limitations of manual, inconsistent, and non-scalable client registration methods that were common before its publication. +The current implementation has been tested with Keycloak. If you want to enable dynamic client registration, here are the steps to follow: +Prerequisite: Create an initial access token from the Keycloak server. You can create an initial access token using either the [admin](https://www.keycloak.org/securing-apps/client-registration#_initial_access_token) console or programmatically. + +To create a client in Keycloak using the initial access token and a client ID of your choice, follow the example below. +[source,$lang] +---- +{@link examples.AuthOAuth2Examples#example23} +---- +To read a client that you have already presented in Keycloak, follow the following example. +[source,$lang] +---- +{@link examples.AuthOAuth2Examples#example24} +---- +To remove a client that is already present in Keycloak, follow the following example. +[source,$lang] +---- +{@link examples.AuthOAuth2Examples#example27} +---- +To create an initial access token programmatically, refer to the following example. +[source,$lang] +---- +{@link examples.AuthOAuth2Examples#example28} +---- +To create an admin access token programmatically, refer to the following example. +[source,$lang] +---- +{@link examples.AuthOAuth2Examples#example29} +---- diff --git a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java index ac3478dcc..75f7d5d6e 100644 --- a/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java +++ b/vertx-auth-oauth2/src/main/generated/io/vertx/ext/auth/oauth2/DCRResponseConverter.java @@ -13,16 +13,34 @@ static void fromJson(Iterable> json, DCRResp for (java.util.Map.Entry member : json) { switch (member.getKey()) { case "id": + if (member.getValue() instanceof String) { + obj.setId((String)member.getValue()); + } break; case "clientId": + if (member.getValue() instanceof String) { + obj.setClientId((String)member.getValue()); + } break; case "enabled": + if (member.getValue() instanceof Boolean) { + obj.setEnabled((Boolean)member.getValue()); + } break; - case "clientAuthenticationType": + case "clientAuthenticatorType": + if (member.getValue() instanceof String) { + obj.setClientAuthenticatorType((String)member.getValue()); + } break; case "secret": + if (member.getValue() instanceof String) { + obj.setSecret((String)member.getValue()); + } break; case "registrationAccessToken": + if (member.getValue() instanceof String) { + obj.setRegistrationAccessToken((String)member.getValue()); + } break; } } @@ -40,8 +58,8 @@ static void toJson(DCRResponse obj, java.util.Map json) { json.put("clientId", obj.getClientId()); } json.put("enabled", obj.isEnabled()); - if (obj.getClientAuthenticationType() != null) { - json.put("clientAuthenticationType", obj.getClientAuthenticationType()); + if (obj.getClientAuthenticatorType() != null) { + json.put("clientAuthenticatorType", obj.getClientAuthenticatorType()); } if (obj.getSecret() != null) { json.put("secret", obj.getSecret()); diff --git a/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java b/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java index 86c555bcc..49747c383 100644 --- a/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java +++ b/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java @@ -16,7 +16,11 @@ package examples; +import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.User; @@ -26,8 +30,10 @@ import io.vertx.ext.auth.authorization.AuthorizationProvider; import io.vertx.ext.auth.authorization.PermissionBasedAuthorization; import io.vertx.ext.auth.authorization.RoleBasedAuthorization; +import io.vertx.ext.auth.impl.http.SimpleHttpClient; import io.vertx.ext.auth.oauth2.*; import io.vertx.ext.auth.oauth2.authorization.KeycloakAuthorization; +import io.vertx.ext.auth.oauth2.dcr.KeycloakClientRegistration; import io.vertx.ext.auth.oauth2.providers.*; /** @@ -38,21 +44,20 @@ public class AuthOAuth2Examples { public void example1(Vertx vertx) { OAuth2Auth oauth2 = OAuth2Auth.create(vertx, new OAuth2Options() - .setClientId("YOUR_CLIENT_ID") - .setClientSecret("YOUR_CLIENT_SECRET") - .setSite("https://github.com/login") - .setTokenPath("/oauth/access_token") - .setAuthorizationPath("/oauth/authorize") - ); + .setClientId("YOUR_CLIENT_ID") + .setClientSecret("YOUR_CLIENT_SECRET") + .setSite("https://github.com/login") + .setTokenPath("/oauth/access_token") + .setAuthorizationPath("/oauth/authorize")); // when there is a need to access a protected resource // or call a protected method, call the authZ url for // a challenge String authorization_uri = oauth2.authorizeURL(new OAuth2AuthorizationURL() - .setRedirectUri("http://localhost:8080/callback") - .addScope("notifications") - .setState("3(#0/!~")); + .setRedirectUri("http://localhost:8080/callback") + .addScope("notifications") + .setState("3(#0/!~")); // when working with web application use the above string as a redirect url @@ -64,53 +69,52 @@ public void example1(Vertx vertx) { oauth2.authenticate( new Oauth2Credentials() - .setCode(code) - .setRedirectUri("http://localhost:8080/callback")) - .onSuccess(user -> { - // save the token and continue... - }) - .onFailure(err -> { - // error, the code provided is not valid - }); + .setCode(code) + .setRedirectUri("http://localhost:8080/callback")) + .onSuccess(user -> { + // save the token and continue... + }) + .onFailure(err -> { + // error, the code provided is not valid + }); } public void example2(Vertx vertx, HttpServerResponse response) { // Set the client credentials and the OAuth2 server OAuth2Options credentials = new OAuth2Options() - .setClientId("") - .setClientSecret("") - .setSite("https://api.oauth.com"); - + .setClientId("") + .setClientSecret("") + .setSite("https://api.oauth.com"); // Initialize the OAuth2 Library OAuth2Auth oauth2 = OAuth2Auth.create(vertx, credentials); // Authorization oauth2 URI String authorization_uri = oauth2.authorizeURL(new OAuth2AuthorizationURL() - .setRedirectUri("http://localhost:8080/callback") - .addScope("") - .setState("")); + .setRedirectUri("http://localhost:8080/callback") + .addScope("") + .setState("")); // Redirect example using Vert.x response.putHeader("Location", authorization_uri) - .setStatusCode(302) - .end(); + .setStatusCode(302) + .end(); Credentials tokenConfig = new Oauth2Credentials() - .setCode("") - .setRedirectUri("http://localhost:3000/callback"); + .setCode("") + .setRedirectUri("http://localhost:3000/callback"); // Callbacks // Save the access token oauth2.authenticate(tokenConfig) - .onSuccess(user -> { - // Get the access token object - // (the authorization code is given from the previous step). - }) - .onFailure(err -> { - System.err.println("Access Token Error: " + err.getMessage()); - }); + .onSuccess(user -> { + // Get the access token object + // (the authorization code is given from the previous step). + }) + .onFailure(err -> { + System.err.println("Access Token Error: " + err.getMessage()); + }); } public void example3(Vertx vertx) { @@ -119,32 +123,31 @@ public void example3(Vertx vertx) { OAuth2Auth oauth2 = OAuth2Auth.create(vertx); Credentials tokenConfig = new UsernamePasswordCredentials( - "username", "password"); + "username", "password"); oauth2.authenticate(tokenConfig) - .onSuccess(user -> { - // Get the access token object - // (the authorization code is given from the previous step). - - // you can now make requests using the - // `Authorization` header and the value: - String httpAuthorizationHeader = user.principal() - .getString("access_token"); - - }) - .onFailure(err -> { - System.err.println("Access Token Error: " + err.getMessage()); - }); + .onSuccess(user -> { + // Get the access token object + // (the authorization code is given from the previous step). + + // you can now make requests using the + // `Authorization` header and the value: + String httpAuthorizationHeader = user.principal() + .getString("access_token"); + + }) + .onFailure(err -> { + System.err.println("Access Token Error: " + err.getMessage()); + }); } public void example4(Vertx vertx) { // Set the client credentials and the OAuth2 server OAuth2Options credentials = new OAuth2Options() - .setClientId("") - .setClientSecret("") - .setSite("https://api.oauth.com"); - + .setClientId("") + .setClientSecret("") + .setSite("https://api.oauth.com"); // Initialize the OAuth2 Library OAuth2Auth oauth2 = OAuth2Auth.create(vertx, credentials); @@ -152,12 +155,12 @@ public void example4(Vertx vertx) { Credentials tokenConfig = new TokenCredentials(""); oauth2.authenticate(tokenConfig) - .onSuccess(user -> { - // Success - }) - .onFailure(err -> { - System.err.println("Access Token Error: " + err.getMessage()); - }); + .onSuccess(user -> { + // Success + }) + .onFailure(err -> { + System.err.println("Access Token Error: " + err.getMessage()); + }); } public void example5(OAuth2Auth oauth2, User user) { @@ -165,61 +168,60 @@ public void example5(OAuth2Auth oauth2, User user) { if (user.expired()) { // Callbacks oauth2.refresh(user) - .onSuccess(refreshedUser -> { - // the refreshed user is now available - }) - .onFailure(err -> { - // error handling... - }); + .onSuccess(refreshedUser -> { + // the refreshed user is now available + }) + .onFailure(err -> { + // error handling... + }); } } public void example6(OAuth2Auth oauth2, User user) { // Revoke only the access token oauth2.revoke(user, "access_token") - .onSuccess(v -> { - // Session ended. But the refresh_token is still valid. - - // Revoke the refresh_token - oauth2.revoke(user, "refresh_token") - .onSuccess(v2 -> { - System.out.println("token revoked."); - }); - }); + .onSuccess(v -> { + // Session ended. But the refresh_token is still valid. + + // Revoke the refresh_token + oauth2.revoke(user, "refresh_token") + .onSuccess(v2 -> { + System.out.println("token revoked."); + }); + }); } public void example13(Vertx vertx) { // you would get this config from the keycloak admin console JsonObject keycloakJson = new JsonObject() - .put("realm", "master") - .put("realm-public-key", "MIIBIjANBgkqhk...wIDAQAB") - .put("auth-server-url", "http://localhost:9000/auth") - .put("ssl-required", "external") - .put("resource", "frontend") - .put("credentials", new JsonObject() - .put("secret", "2fbf5e18-b923-4a83-9657-b4ebd5317f60")); + .put("realm", "master") + .put("realm-public-key", "MIIBIjANBgkqhk...wIDAQAB") + .put("auth-server-url", "http://localhost:9000/auth") + .put("ssl-required", "external") + .put("resource", "frontend") + .put("credentials", new JsonObject() + .put("secret", "2fbf5e18-b923-4a83-9657-b4ebd5317f60")); // Initialize the OAuth2 Library OAuth2Auth oauth2 = KeycloakAuth - .create(vertx, OAuth2FlowType.PASSWORD, keycloakJson); + .create(vertx, OAuth2FlowType.PASSWORD, keycloakJson); // first get a token (authenticate) oauth2.authenticate( new UsernamePasswordCredentials("user", "secret")) - .onSuccess(user -> { - // now check for permissions - AuthorizationProvider authz = KeycloakAuthorization.create(); - - authz.getAuthorizations(user) - .onSuccess(v -> { - if ( - RoleBasedAuthorization.create("manage-account") - .setResource("account") - .match(user)) { - // this user is authorized to manage its account - } - }); - }); + .onSuccess(user -> { + // now check for permissions + AuthorizationProvider authz = KeycloakAuthorization.create(); + + authz.getAuthorizations(user) + .onSuccess(v -> { + if (RoleBasedAuthorization.create("manage-account") + .setResource("account") + .match(user)) { + // this user is authorized to manage its account + } + }); + }); } public void example14(User user) { @@ -231,30 +233,28 @@ public void example14(User user) { String username = user.principal().getString("preferred_username"); } - public void example15(OAuth2Auth oauth2, User user) { // OAuth2Auth level oauth2.authenticate(new TokenCredentials("opaque string")) - .onSuccess(theUser -> { - // token is valid! - }); + .onSuccess(theUser -> { + // token is valid! + }); // User level oauth2.authenticate(new TokenCredentials(user.get("access_token"))) - .onSuccess(authenticatedUser -> { - // Token is valid! - }); + .onSuccess(authenticatedUser -> { + // Token is valid! + }); } public void example16(OAuth2Auth oauth2) { // OAuth2Auth level oauth2.authenticate(new TokenCredentials("jwt-token")) - .onSuccess(theUser -> { - // token is valid! - }); + .onSuccess(theUser -> { + // token is valid! + }); } - public void example17(User user) { // in this case it is assumed that the role is the current application if (PermissionBasedAuthorization.create("print").match(user)) { @@ -265,8 +265,7 @@ public void example17(User user) { public void example18(User user) { // the resource is "realm" // the authority is "add-user" - if ( - PermissionBasedAuthorization.create("add-user") + if (PermissionBasedAuthorization.create("add-user") .setResource("realm") .match(user)) { // Yes the user can add users to the application @@ -276,8 +275,7 @@ public void example18(User user) { public void example19(User user) { // the role is "finance" // the authority is "year-report" - if ( - PermissionBasedAuthorization.create("year-report") + if (PermissionBasedAuthorization.create("year-report") .setResource("finance") .match(user)) { // Yes the user can access the year report from the finance department @@ -297,37 +295,37 @@ public void example21(User user) { public void example22(OAuth2Auth oauth2, User user) { oauth2.refresh(user) - .onSuccess(refreshedUser -> { - // the refresh call succeeded - }) - .onFailure(err -> { - // the token was not refreshed, a best practise would be - // to forcefully logout the user since this could be a - // symptom that you're logged out by the server and this - // token is not valid anymore. - }); + .onSuccess(refreshedUser -> { + // the refresh call succeeded + }) + .onFailure(err -> { + // the token was not refreshed, a best practise would be + // to forcefully logout the user since this could be a + // symptom that you're logged out by the server and this + // token is not valid anymore. + }); } public void example23(OAuth2Auth oauth2, User user) { oauth2.revoke(user, "access_token") - .onSuccess(v -> { - // the revoke call succeeded - }) - .onFailure(err -> { - // the token was not revoked. - }); + .onSuccess(v -> { + // the revoke call succeeded + }) + .onFailure(err -> { + // the token was not revoked. + }); } public void example24(OAuth2Auth oauth2, User user) { oauth2.authenticate(new TokenCredentials(user.get("access_token"))) - .onSuccess(validUser -> { - // the introspection call succeeded - }) - .onFailure(err -> { - // the token failed the introspection. You should proceed - // to logout the user since this means that this token is - // not valid anymore. - }); + .onSuccess(validUser -> { + // the introspection call succeeded + }) + .onFailure(err -> { + // the token failed the introspection. You should proceed + // to logout the user since this means that this token is + // not valid anymore. + }); } public void example25(Vertx vertx) { @@ -335,17 +333,17 @@ public void example25(Vertx vertx) { OpenIDConnectAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setSite("https://accounts.google.com")) - .onSuccess(oauth2 -> { - // the setup call succeeded. - // at this moment your auth is ready to use and - // google signature keys are loaded so tokens can be decoded and verified. - }) - .onFailure(err -> { - // the setup failed. - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setSite("https://accounts.google.com")) + .onSuccess(oauth2 -> { + // the setup call succeeded. + // at this moment your auth is ready to use and + // google signature keys are loaded so tokens can be decoded and verified. + }) + .onFailure(err -> { + // the setup failed. + }); } public void example25b(Vertx vertx) { @@ -353,56 +351,56 @@ public void example25b(Vertx vertx) { KeycloakAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setSite("https://keycloakhost:keycloakport/auth/realms/{realm}") - .setTenant("your-realm")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setSite("https://keycloakhost:keycloakport/auth/realms/{realm}") + .setTenant("your-realm")) + .onSuccess(oauth2 -> { + // ... + }); // Google example GoogleAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret")) + .onSuccess(oauth2 -> { + // ... + }); // Salesforce example SalesforceAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret")) + .onSuccess(oauth2 -> { + // ... + }); // Azure AD example AzureADAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setTenant("your-app-guid")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setTenant("your-app-guid")) + .onSuccess(oauth2 -> { + // ... + }); // IBM Cloud example IBMCloudAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setSite("https://.appid.cloud.ibm.com/oauth/v4/{tenant}") - .setTenant("your-tenant-id")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setSite("https://.appid.cloud.ibm.com/oauth/v4/{tenant}") + .setTenant("your-tenant-id")) + .onSuccess(oauth2 -> { + // ... + }); } public void example26(Vertx vertx) { @@ -410,23 +408,23 @@ public void example26(Vertx vertx) { OpenIDConnectAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setTenant("your_realm") - .setSite("https://server:port/auth/realms/{tenant}")) - .onSuccess(oauth2 -> { - // the setup call succeeded. - // at this moment your auth is ready to use - }); + .setClientId("clientId") + .setTenant("your_realm") + .setSite("https://server:port/auth/realms/{tenant}")) + .onSuccess(oauth2 -> { + // the setup call succeeded. + // at this moment your auth is ready to use + }); } public void example21(OAuth2Auth oauth2) { // OAuth2Auth level oauth2.jWKSet() - .onSuccess(v -> { - // load was successful, if the server returned the header - // `Cache-Control` with a `max-age` then a periodic task - // will run at that time to refresh the keys - }); + .onSuccess(v -> { + // load was successful, if the server returned the header + // `Cache-Control` with a `max-age` then a periodic task + // will run at that time to refresh the keys + }); } public void example22(OAuth2Auth oauth2) { @@ -437,10 +435,83 @@ public void example22(OAuth2Auth oauth2) { if (keyId.equals("the-new-id")) { // 2. refresh the keys oauth2.jWKSet() - .onSuccess(v -> { - // ... - }); + .onSuccess(v -> { + // ... + }); } }); } + + // create a dynamic client in keycloak 25.0.0 + public void example23(Vertx vertx) { + JsonObject options = new JsonObject().put("site", "https://server:port") + .put("tenant", "master") + .put("initialAccessToken", "initial-access-token"); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(vertx, + new DCROptions(options)); + keycloakClientRegistration.create("junit-test-client").onSuccess(v -> { + // ... + }); + } + + // get a dynamic client from keycloak 25.0.0 + public void example24(Vertx vertx) { + JsonObject options = new JsonObject().put("site", "https://server:port") + .put("tenant", "master") + .put("initialAccessToken", "initial-access-token"); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(vertx, + new DCROptions(options)); + // registrationAccessToken is unique for Keycloak implementation + JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", "registration-access-token") + .put("clientId", "junit-test-client"); + keycloakClientRegistration.get(new DCRRequest(requJsonObject)).onSuccess(v -> { + // ... + }); + } + + // delete a dynamic client from keycloak 25.0.0 + public void example27(Vertx vertx) { + JsonObject options = new JsonObject().put("site", "https://server:port") + .put("tenant", "master") + .put("initialAccessToken", "initial-access-token"); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(vertx, + new DCROptions(options)); + // registrationAccessToken is unique for Keycloak implementation + JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", "registration-access-token") + .put("clientId", "junit-test-client"); + keycloakClientRegistration.delete(new DCRRequest(requJsonObject)).onSuccess(v -> { + // ... + }); + } + + // create initial access token in keycloak 25.0.0 + public void example28(Vertx vertx) { + JsonObject header = new JsonObject().put("Authorization", String.format("Bearer %s", "admin-access-token")) + .put("Content-Type", "application/json"); + JsonObject payload = new JsonObject() + .put("expiration", 180) + .put("count", 1); + new SimpleHttpClient(vertx, "https://server:port", new HttpClientOptions()) + .fetch(HttpMethod.POST, + "https://server:port/admin/realms/master/clients-initial-access", + header, payload.toBuffer()) + .onSuccess(v -> { + // get the initial access token from v.jsonObject().getString("token") + // ... + }); + } + + // create a admin token to create initial access token in keycloak 25.0.0 + public void example29(Vertx vertx) { + JsonObject header = new JsonObject().put("Content-Type", "application/x-www-form-urlencoded"); + Buffer body = Buffer.buffer("grant_type=password&client_id=admin-cli&username=admin&password=secret"); + new SimpleHttpClient(vertx, "https://server:port", new HttpClientOptions()) + .fetch(HttpMethod.POST, + "https://server:port/realms/master/protocol/openid-connect/token", + header, body) + .onSuccess(v -> { + // get the admin access token from v.jsonObject().getString("access_token") + // ... + }); + } } diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java index 29fd7e77e..ffb04d297 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java @@ -19,6 +19,9 @@ @VertxGen public interface ClientRegistrationProvider { Future create(String clientId); + Future get(DCRRequest dcrRequest); + Future delete(DCRRequest dcrRequest); + } \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java index 9d20ecf30..0b2db9126 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java @@ -79,7 +79,6 @@ public void setTenant(String tenant) { } public String resourceUri() { - return String.format("%s/realms/%s/clients-registrations/default/", site, tenant); + return String.format("%s/realms/%s/clients-registrations/default", site, tenant); } - } \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java index 194984b57..af8549c64 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java @@ -37,6 +37,7 @@ public JsonObject toJson() { DCRRequestConverter.toJson(this, json); return json; } + public String getClientId() { return clientId; } @@ -48,6 +49,7 @@ public void setClientId(String clientId) { public String getRegistrationAccessToken() { return registrationAccessToken; } + public void setRegistrationAccessToken(String registrationAccessToken) { this.registrationAccessToken = registrationAccessToken; } diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java index 01f854b7a..a631fe5ca 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java @@ -33,9 +33,9 @@ public class DCRResponse { private boolean enabled; /** - * Client authentication type, by default it is client-secret. + * Client authenticator type, by default it is client-secret. */ - private String clientAuthenticationType; + private String clientAuthenticatorType; /** * Client secret for client_secret_post or client_secret_basic. @@ -43,7 +43,8 @@ public class DCRResponse { private String secret; /** - * RegistrationAccessToken is used for subsequent communication with Keycloak to GET or DELETE the client. + * RegistrationAccessToken is used for subsequent communication with Keycloak to + * GET or DELETE the client. */ private String registrationAccessToken; @@ -61,23 +62,47 @@ public String getId() { return id; } + public void setId(String id) { + this.id = id; + } + public String getClientId() { return clientId; } + public void setClientId(String clientId) { + this.clientId = clientId; + } + public boolean isEnabled() { return enabled; } - public String getClientAuthenticationType() { - return clientAuthenticationType; + public void setEnabled(boolean isEnabled) { + this.enabled = isEnabled; + } + + public String getClientAuthenticatorType() { + return clientAuthenticatorType; + } + + public void setClientAuthenticatorType(String clientAuthenticatorType) { + this.clientAuthenticatorType = clientAuthenticatorType; } public String getSecret() { return secret; } + public void setSecret(String secret) { + this.secret = secret; + } + public String getRegistrationAccessToken() { return registrationAccessToken; } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } } \ No newline at end of file diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/OAuth2Auth.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/OAuth2Auth.java index 1fe0e6500..96ac22c6c 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/OAuth2Auth.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/OAuth2Auth.java @@ -18,7 +18,6 @@ import io.vertx.codegen.annotations.Fluent; import io.vertx.codegen.annotations.VertxGen; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java index d23e7e2c9..9353f3a18 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java @@ -38,38 +38,42 @@ public KeycloakClientRegistrationImpl(Vertx vertx, DCROptions dcrOptions) { @Override public Future create(String clientId) { - JsonObject initialAccessToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrOptions.getInitialAccessToken())); + JsonObject initialAccessToken = JsonObject.of("Authorization", + String.format("Bearer %s", dcrOptions.getInitialAccessToken())); JsonObject payload = JsonObject.of("clientId", clientId); return simpleHttpClient.fetch(HttpMethod.POST, dcrOptions.resourceUri(), initialAccessToken, - payload.toBuffer()).compose(response -> constructResponse(response)); + payload.toBuffer()).compose(response -> constructResponse(response, 201)); } + @Override public Future get(DCRRequest dcrRequest) { Objects.requireNonNull(dcrRequest.getClientId(), "clientId cannot be null."); Objects.requireNonNull(dcrRequest.getRegistrationAccessToken(), "registrationAccessToken cannot be null."); - JsonObject registrationToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); + JsonObject registrationToken = JsonObject.of("Authorization", + String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); return simpleHttpClient.fetch(HttpMethod.GET, String.format("%s/%s", dcrOptions.resourceUri(), dcrRequest.getClientId()), registrationToken, null) - .compose(response -> constructResponse(response)); + .compose(response -> constructResponse(response, 200)); } @Override public Future delete(DCRRequest dcrRequest) { Objects.requireNonNull(dcrRequest.getClientId(), "clientId cannot be null."); Objects.requireNonNull(dcrRequest.getRegistrationAccessToken(), "registrationAccessToken cannot be null."); - JsonObject registrationToken = JsonObject.of("Authorization", String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); + JsonObject registrationToken = JsonObject.of("Authorization", + String.format("Bearer %s", dcrRequest.getRegistrationAccessToken())); return simpleHttpClient.fetch(HttpMethod.DELETE, String.format("%s/%s", dcrOptions.resourceUri(), dcrRequest.getClientId()), registrationToken, null) - .compose(response -> { - if (response.statusCode() != 204) { - return Future.failedFuture("Bad Response [" + response.statusCode() + "] " + response.body()); - } - return Future.succeededFuture(); - }); + .compose(response -> { + if (response.statusCode() != 204) { + return Future.failedFuture("Bad Response [" + response.statusCode() + "] " + response.body()); + } + return Future.succeededFuture(); + }); } - private Future constructResponse(SimpleHttpResponse response) { - if (response.statusCode() != 201) { + private Future constructResponse(SimpleHttpResponse response, int expectedStatusCode) { + if (response.statusCode() != expectedStatusCode) { return Future.failedFuture("Bad Response [" + response.statusCode() + "] " + response.body()); } if (!response.is("application/json")) { diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java index 4b26be3ef..48e5dfcaf 100644 --- a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2025 Sanju Thomas + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ package io.vertx.tests; import io.vertx.core.Future; @@ -8,6 +18,7 @@ import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.impl.http.SimpleHttpClient; import io.vertx.ext.auth.oauth2.DCROptions; +import io.vertx.ext.auth.oauth2.DCRRequest; import io.vertx.ext.auth.oauth2.DCRResponse; import io.vertx.ext.auth.oauth2.dcr.KeycloakClientRegistration; import io.vertx.ext.unit.Async; @@ -15,6 +26,9 @@ import io.vertx.ext.unit.junit.RunTestOnContext; import io.vertx.ext.unit.junit.VertxUnitRunnerWithParametersFactory; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -26,7 +40,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -40,35 +56,126 @@ public class DCRKeycloak25_0_0_IT { - @Rule - public final RunTestOnContext rule = new RunTestOnContext(); + private static final String REALM = "master"; + @Rule + public final RunTestOnContext rule = new RunTestOnContext(); + + private static GenericContainer keycloak; + + @BeforeClass + public static void setupDocker() { + keycloak = new GenericContainer<>(DockerImageName.parse("quay.io/keycloak/keycloak:25.0.0")) + .withExposedPorts(8080) + .withEnv("KEYCLOAK_ADMIN", "admin") + .withEnv("KEYCLOAK_ADMIN_PASSWORD", "secret") + .withCommand("start-dev") + .waitingFor( + Wait.forHttp(String.format("/realms/%s/.well-known/openid-configuration", REALM)) + .forStatusCode(200) + .withStartupTimeout(Duration.ofSeconds(90))); + } + + @Before + public void setup() { + keycloak.start(); + } + + @After + public void tearDown() { + keycloak.stop(); + } + + @Test + public void testCreateDynamicClient() throws Exception { + String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); + String initialAccessToken = createInitialAccessToken(baseUrl, + getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) + .await(10, TimeUnit.SECONDS); + JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put("initialAccessToken", + initialAccessToken); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(rule.vertx(), + new DCROptions(options)); + Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); + DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); + assertNotNull(client.getId()); + assertEquals("junit-test-client", client.getClientId()); + assertEquals("client-secret", client.getClientAuthenticatorType()); + assertNotNull(client.getRegistrationAccessToken()); + assertNotNull(client.getSecret()); + } - @Test - public void testCreateDynamicClient() throws Exception { - try ( - GenericContainer keycloak = new GenericContainer<>(DockerImageName.parse("quay.io/keycloak/keycloak:25.0.0")) - .withExposedPorts(8080) - .withEnv("KEYCLOAK_ADMIN", "admin") - .withEnv("KEYCLOAK_ADMIN_PASSWORD", "secret") - .withCommand("start-dev") - .waitingFor( - Wait.forHttp("/realms/master/.well-known/openid-configuration") - .forStatusCode(200) - .withStartupTimeout(Duration.ofSeconds(90)));) { - keycloak.start(); - String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); - System.out.printf("Keycloak is running at %s", baseUrl); - String accessToken = getAdminAccessToken(baseUrl).await(60, TimeUnit.SECONDS); - System.out.println(accessToken); + @Test + public void testGetDynamicClient() throws Exception { + String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); + String initialAccessToken = createInitialAccessToken(baseUrl, + getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) + .await(10, TimeUnit.SECONDS); + JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put("initialAccessToken", + initialAccessToken); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(rule.vertx(), + new DCROptions(options)); + Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); + DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); + assertNotNull(client.getId()); + assertEquals("junit-test-client", client.getClientId()); + JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", client.getRegistrationAccessToken()) + .put("clientId", "junit-test-client"); + DCRRequest dcrRequest = new DCRRequest(requJsonObject); + DCRResponse getResopnse = keycloakClientRegistration.get(dcrRequest).await(10, TimeUnit.SECONDS); + assertEquals("junit-test-client", getResopnse.getClientId()); + assertEquals(client.getRegistrationAccessToken(), getResopnse.getRegistrationAccessToken()); } - } - private Future getAdminAccessToken(String baseUrl) throws Exception { - SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, new HttpClientOptions()); - JsonObject header = new JsonObject().put("Content-Type", "application/x-www-form-urlencoded"); - Buffer body = Buffer.buffer("grant_type=password&client_id=admin-cli&username=admin&password=secret"); - return simpleHttpClient.fetch(HttpMethod.POST, baseUrl + "/realms/master/protocol/openid-connect/token", header, body) - .compose(response -> Future.succeededFuture(response.jsonObject().getString("access_token"))); - } + @Test + public void testDleteDynamicClient() throws Exception { + String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); + String initialAccessToken = createInitialAccessToken(baseUrl, + getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) + .await(10, TimeUnit.SECONDS); + JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put("initialAccessToken", + initialAccessToken); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(rule.vertx(), + new DCROptions(options)); + Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); + DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); + assertNotNull(client.getId()); + assertEquals("junit-test-client", client.getClientId()); + JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", client.getRegistrationAccessToken()) + .put("clientId", "junit-test-client"); + DCRRequest dcrRequest = new DCRRequest(requJsonObject); + DCRResponse getResopnse = keycloakClientRegistration.get(dcrRequest).await(10, TimeUnit.SECONDS); + assertEquals("junit-test-client", getResopnse.getClientId()); + assertEquals(client.getRegistrationAccessToken(), getResopnse.getRegistrationAccessToken()); + keycloakClientRegistration.delete(dcrRequest).await(10, TimeUnit.SECONDS); + keycloakClientRegistration.get(dcrRequest).onFailure(load -> { + assertEquals( + "Unauthorized: {\"error\":\"invalid_token\",\"error_description\":\"Not authorized to view client. Not valid token or client credentials provided.\"}", + load.getMessage()); + }); + } + private Future getAdminAccessToken(String baseUrl) throws Exception { + SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, new HttpClientOptions()); + JsonObject header = new JsonObject().put("Content-Type", "application/x-www-form-urlencoded"); + Buffer body = Buffer.buffer("grant_type=password&client_id=admin-cli&username=admin&password=secret"); + return simpleHttpClient + .fetch(HttpMethod.POST, String.format("%s/realms/%s/protocol/openid-connect/token", baseUrl, REALM), + header, + body) + .compose(response -> Future.succeededFuture(response.jsonObject().getString("access_token"))); + } + + private Future createInitialAccessToken(String baseUrl, String adminBearer) throws Exception { + CompletableFuture future = new CompletableFuture<>(); + JsonObject header = new JsonObject().put("Authorization", String.format("Bearer %s", adminBearer)) + .put("Content-Type", "application/json"); + JsonObject payload = new JsonObject() + .put("expiration", 180) + .put("count", 1); + SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, new HttpClientOptions()); + return simpleHttpClient + .fetch(HttpMethod.POST, String.format("%s/admin/realms/%s/clients-initial-access", baseUrl, REALM), + header, payload.toBuffer()) + .compose(response -> Future.succeededFuture(response.jsonObject().getString("token"))); + } } diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java new file mode 100644 index 000000000..11e2f4472 --- /dev/null +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java @@ -0,0 +1,46 @@ +package io.vertx.tests; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import io.vertx.core.json.JsonObject; + +import io.vertx.ext.auth.oauth2.DCROptions; + +public class DCROptionsTest { + + private static final String DCR_OPTION_JSO_STRING = "{\n" + + " \"site\": \"https://auth.example.com\",\n" + + " \"tenant\": \"master\",\n" + + " \"initialAccessToken\": \"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0\",\n" + + " \"httpClientOptions\": {\n" + + " \"defaultHost\": \"auth.example.com\"\n" + + " }\n" + + "}\n" + + ""; + + private DCROptions dcrOptions; + + @Before + public void setup() { + dcrOptions = new DCROptions(new JsonObject(DCR_OPTION_JSO_STRING)); + } + + @Test + public void testCreateFromJson() { + assertEquals("https://auth.example.com", dcrOptions.getSite()); + assertEquals("master", dcrOptions.getTenant()); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0", dcrOptions.getInitialAccessToken()); + assertEquals("auth.example.com", dcrOptions.getHttpClientOptions().getDefaultHost()); + } + + @Test + public void testSerializationToJson() { + io.vertx.core.json.JsonObject json = dcrOptions.toJson(); + assertEquals("https://auth.example.com", json.getString("site")); + assertEquals("master", json.getString("tenant")); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0", json.getString("initialAccessToken")); + assertEquals("auth.example.com", json.getJsonObject("httpClientOptions").getString("defaultHost")); + } +} diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java new file mode 100644 index 000000000..dc62879f7 --- /dev/null +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java @@ -0,0 +1,35 @@ +package io.vertx.tests; + +import org.junit.Before; +import org.junit.Test; +import io.vertx.core.json.JsonObject; + +import io.vertx.ext.auth.oauth2.DCRRequest; + +public class DCRRequestTest { + + private static final String DCR_REQUEST_STRING = "{\n" + + " \"clientId\": \"my-client-id\",\n" + + " \"registrationAccessToken\": \"my-registration-access-token\"\n" + + "}"; + + private DCRRequest dcrRequest; + + @Before + public void setup() { + this.dcrRequest = new DCRRequest(new JsonObject(DCR_REQUEST_STRING)); + } + + @Test + public void testCreateFromJson() { + assert "my-client-id".equals(dcrRequest.getClientId()); + assert "my-registration-access-token".equals(dcrRequest.getRegistrationAccessToken()); + } + + @Test + public void testToJson() { + io.vertx.core.json.JsonObject json = dcrRequest.toJson(); + assert "my-client-id".equals(json.getString("clientId")); + assert "my-registration-access-token".equals(json.getString("registrationAccessToken")); + } +} diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java new file mode 100644 index 000000000..4294f24d7 --- /dev/null +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java @@ -0,0 +1,46 @@ +package io.vertx.tests; + +import org.junit.Before; +import org.junit.Test; + +import io.vertx.core.json.JsonObject; + +import io.vertx.ext.auth.oauth2.DCRResponse; + +public class DCRResponseTest { + private static final String DCR_RESPPONSE_STRING = "{\n" + + " \"id\": \"12345\",\n" + + " \"clientId\": \"my-client-id\",\n" + + " \"enabled\": true,\n" + + " \"clientAuthenticatorType\": \"client-secret\",\n" + + " \"secret\": \"my-secret\",\n" + + " \"registrationAccessToken\": \"my-registration-access-token\"\n" + + "}"; + private DCRResponse dcrResponse; + + @Before + public void setup() { + this.dcrResponse = new DCRResponse(new JsonObject(DCR_RESPPONSE_STRING)); + } + + @Test + public void testCreateFromJson() { + assert "12345".equals(dcrResponse.getId()); + assert "my-client-id".equals(dcrResponse.getClientId()); + assert dcrResponse.isEnabled(); + assert "client-secret".equals(dcrResponse.getClientAuthenticatorType()); + assert "my-secret".equals(dcrResponse.getSecret()); + assert "my-registration-access-token".equals(dcrResponse.getRegistrationAccessToken()); + } + + @Test + public void testSerializationToJson() { + JsonObject json = dcrResponse.toJson(); + assert "12345".equals(json.getString("id")); + assert "my-client-id".equals(json.getString("clientId")); + assert json.getBoolean("enabled"); + assert "client-secret".equals(json.getString("clientAuthenticatorType")); + assert "my-secret".equals(json.getString("secret")); + assert "my-registration-access-token".equals(json.getString("registrationAccessToken")); + } +} diff --git a/vertx-auth-oauth2/src/test/resources/loglive.txt b/vertx-auth-oauth2/src/test/resources/loglive.txt deleted file mode 100644 index 86a11b956..000000000 --- a/vertx-auth-oauth2/src/test/resources/loglive.txt +++ /dev/null @@ -1,29 +0,0 @@ -please stop the keycloak before export -export configs: ./kc.sh export --file master.json --realm master - -to generate initial access token: -kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin -create long live initial access token: kcadm.sh create clients-initial-access -s expiration=157788000 -o - -mvn clean install -PIT -mvn clean install -PIT -Dtest=io.vertx.tests.DCRKeycloak25_0_0_IT - -first, tunnel the traffic from remote devser to local -ssh -N -L 9090:127.0.0.1:8080 user@devserver.local -open http://localhost:9090/ - -imagename for keycloak: 14 trusting_gates 24 recursing_meitner 25 naughty_joliot friendly_hamilton -docker logs --since 5m trusting_gates - - -docker run \ - -p 8080:8080 \ - -e KEYCLOAK_ADMIN=admin \ - -e KEYCLOAK_ADMIN_PASSWORD=secret \ - -v $(pwd)/keycloak-config:/opt/keycloak/data/import \ - quay.io/keycloak/keycloak:25.0.0 \ - start-dev --import-realm - -list all containers: -docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}" -docker logs -f jolly_knuth \ No newline at end of file diff --git a/vertx-auth-oauth2/src/test/resources/registration-response.txt b/vertx-auth-oauth2/src/test/resources/registration-response.txt deleted file mode 100644 index 6bfb65c08..000000000 --- a/vertx-auth-oauth2/src/test/resources/registration-response.txt +++ /dev/null @@ -1,43 +0,0 @@ -{ - "id": "b491a869-d755-487c-acea-26b8a184543c", - "clientId": "token_test", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "secret": "WUpB8PJ4U1VTTsmzD3tLdbIDfAhhiKWo", - "registrationAccessToken": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxZmI0MjFhNy04ZGZjLTQ1OTctOTM1Ni01MGEwNTA3YjM4MmEifQ.eyJleHAiOjAsImlhdCI6MTc1ODk5ODc2NywianRpIjoiNTNmMDA2NjMtMjI2NC00OTRkLTg2MjItZjViNGE4Y2I2M2E1IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsInR5cCI6IlJlZ2lzdHJhdGlvbkFjY2Vzc1Rva2VuIiwicmVnaXN0cmF0aW9uX2F1dGgiOiJhdXRoZW50aWNhdGVkIn0.fQs2OrSsEUfDJvDZnmGsgHXtyECsyLaIm3Cau9-I4iRjCkR9jOkX7pJ13Il9gH9avaNl0b8AF9Y1N3SUx5iVow", - "defaultRoles": [], - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "client.secret.creation.time": "1758998767" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": true, - "nodeReRegistrationTimeout": -1, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] -} \ No newline at end of file From 0bf53ff7966b67bcc40d9d21ef9fa236eda8ab19 Mon Sep 17 00:00:00 2001 From: sanjuthomas Date: Sun, 12 Oct 2025 16:49:13 +0000 Subject: [PATCH 4/4] format code using .editorconfig --- .../src/main/asciidoc/index.adoc | 4 +- .../java/examples/AuthOAuth2Examples.java | 436 +++++++++--------- .../src/main/java/examples/package-info.java | 2 +- .../oauth2/ClientRegistrationProvider.java | 2 +- .../io/vertx/ext/auth/oauth2/DCROptions.java | 2 +- .../io/vertx/ext/auth/oauth2/DCRRequest.java | 2 +- .../io/vertx/ext/auth/oauth2/DCRResponse.java | 2 +- .../dcr/KeycloakClientRegistration.java | 2 +- .../impl/KeycloakClientRegistrationImpl.java | 2 +- .../io/vertx/tests/DCRKeycloak25_0_0_IT.java | 250 +++++----- .../java/io/vertx/tests/DCROptionsTest.java | 67 +-- .../java/io/vertx/tests/DCRRequestTest.java | 48 +- .../java/io/vertx/tests/DCRResponseTest.java | 71 +-- 13 files changed, 460 insertions(+), 430 deletions(-) diff --git a/vertx-auth-oauth2/src/main/asciidoc/index.adoc b/vertx-auth-oauth2/src/main/asciidoc/index.adoc index 3aeb18541..a8e84b674 100644 --- a/vertx-auth-oauth2/src/main/asciidoc/index.adoc +++ b/vertx-auth-oauth2/src/main/asciidoc/index.adoc @@ -402,9 +402,9 @@ In such event the server will start emitting tokens with a different kid than th A special note on this is that if a user will send many requests with a missing key, your handler should throttle the calls to refresh the new key set, or you might end up DDoS your IdP server. == Dynamic Client Registration (RFC 7591) -[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) was written to standardize the process of client registration in OAuth 2.0 and OpenID Connect ecosystems to address the limitations of manual, inconsistent, and non-scalable client registration methods that were common before its publication. +https://datatracker.ietf.org/doc/html/rfc7591[RFC 7591] was written to standardize the process of client registration in OAuth 2.0 and OpenID Connect ecosystems to address the limitations of manual, inconsistent, and non-scalable client registration methods that were common before its publication. The current implementation has been tested with Keycloak. If you want to enable dynamic client registration, here are the steps to follow: -Prerequisite: Create an initial access token from the Keycloak server. You can create an initial access token using either the [admin](https://www.keycloak.org/securing-apps/client-registration#_initial_access_token) console or programmatically. +Prerequisite: Create an initial access token from the Keycloak server. You can create an initial access token using either the https://www.keycloak.org/securing-apps/client-registration#_initial_access_token[admin] console or programmatically. To create a client in Keycloak using the initial access token and a client ID of your choice, follow the example below. [source,$lang] diff --git a/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java b/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java index 49747c383..e644e58e6 100644 --- a/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java +++ b/vertx-auth-oauth2/src/main/java/examples/AuthOAuth2Examples.java @@ -44,20 +44,20 @@ public class AuthOAuth2Examples { public void example1(Vertx vertx) { OAuth2Auth oauth2 = OAuth2Auth.create(vertx, new OAuth2Options() - .setClientId("YOUR_CLIENT_ID") - .setClientSecret("YOUR_CLIENT_SECRET") - .setSite("https://github.com/login") - .setTokenPath("/oauth/access_token") - .setAuthorizationPath("/oauth/authorize")); + .setClientId("YOUR_CLIENT_ID") + .setClientSecret("YOUR_CLIENT_SECRET") + .setSite("https://github.com/login") + .setTokenPath("/oauth/access_token") + .setAuthorizationPath("/oauth/authorize")); // when there is a need to access a protected resource // or call a protected method, call the authZ url for // a challenge String authorization_uri = oauth2.authorizeURL(new OAuth2AuthorizationURL() - .setRedirectUri("http://localhost:8080/callback") - .addScope("notifications") - .setState("3(#0/!~")); + .setRedirectUri("http://localhost:8080/callback") + .addScope("notifications") + .setState("3(#0/!~")); // when working with web application use the above string as a redirect url @@ -69,52 +69,52 @@ public void example1(Vertx vertx) { oauth2.authenticate( new Oauth2Credentials() - .setCode(code) - .setRedirectUri("http://localhost:8080/callback")) - .onSuccess(user -> { - // save the token and continue... - }) - .onFailure(err -> { - // error, the code provided is not valid - }); + .setCode(code) + .setRedirectUri("http://localhost:8080/callback")) + .onSuccess(user -> { + // save the token and continue... + }) + .onFailure(err -> { + // error, the code provided is not valid + }); } public void example2(Vertx vertx, HttpServerResponse response) { // Set the client credentials and the OAuth2 server OAuth2Options credentials = new OAuth2Options() - .setClientId("") - .setClientSecret("") - .setSite("https://api.oauth.com"); + .setClientId("") + .setClientSecret("") + .setSite("https://api.oauth.com"); // Initialize the OAuth2 Library OAuth2Auth oauth2 = OAuth2Auth.create(vertx, credentials); // Authorization oauth2 URI String authorization_uri = oauth2.authorizeURL(new OAuth2AuthorizationURL() - .setRedirectUri("http://localhost:8080/callback") - .addScope("") - .setState("")); + .setRedirectUri("http://localhost:8080/callback") + .addScope("") + .setState("")); // Redirect example using Vert.x response.putHeader("Location", authorization_uri) - .setStatusCode(302) - .end(); + .setStatusCode(302) + .end(); Credentials tokenConfig = new Oauth2Credentials() - .setCode("") - .setRedirectUri("http://localhost:3000/callback"); + .setCode("") + .setRedirectUri("http://localhost:3000/callback"); // Callbacks // Save the access token oauth2.authenticate(tokenConfig) - .onSuccess(user -> { - // Get the access token object - // (the authorization code is given from the previous step). - }) - .onFailure(err -> { - System.err.println("Access Token Error: " + err.getMessage()); - }); + .onSuccess(user -> { + // Get the access token object + // (the authorization code is given from the previous step). + }) + .onFailure(err -> { + System.err.println("Access Token Error: " + err.getMessage()); + }); } public void example3(Vertx vertx) { @@ -123,31 +123,31 @@ public void example3(Vertx vertx) { OAuth2Auth oauth2 = OAuth2Auth.create(vertx); Credentials tokenConfig = new UsernamePasswordCredentials( - "username", "password"); + "username", "password"); oauth2.authenticate(tokenConfig) - .onSuccess(user -> { - // Get the access token object - // (the authorization code is given from the previous step). - - // you can now make requests using the - // `Authorization` header and the value: - String httpAuthorizationHeader = user.principal() - .getString("access_token"); - - }) - .onFailure(err -> { - System.err.println("Access Token Error: " + err.getMessage()); - }); + .onSuccess(user -> { + // Get the access token object + // (the authorization code is given from the previous step). + + // you can now make requests using the + // `Authorization` header and the value: + String httpAuthorizationHeader = user.principal() + .getString("access_token"); + + }) + .onFailure(err -> { + System.err.println("Access Token Error: " + err.getMessage()); + }); } public void example4(Vertx vertx) { // Set the client credentials and the OAuth2 server OAuth2Options credentials = new OAuth2Options() - .setClientId("") - .setClientSecret("") - .setSite("https://api.oauth.com"); + .setClientId("") + .setClientSecret("") + .setSite("https://api.oauth.com"); // Initialize the OAuth2 Library OAuth2Auth oauth2 = OAuth2Auth.create(vertx, credentials); @@ -155,12 +155,12 @@ public void example4(Vertx vertx) { Credentials tokenConfig = new TokenCredentials(""); oauth2.authenticate(tokenConfig) - .onSuccess(user -> { - // Success - }) - .onFailure(err -> { - System.err.println("Access Token Error: " + err.getMessage()); - }); + .onSuccess(user -> { + // Success + }) + .onFailure(err -> { + System.err.println("Access Token Error: " + err.getMessage()); + }); } public void example5(OAuth2Auth oauth2, User user) { @@ -168,60 +168,60 @@ public void example5(OAuth2Auth oauth2, User user) { if (user.expired()) { // Callbacks oauth2.refresh(user) - .onSuccess(refreshedUser -> { - // the refreshed user is now available - }) - .onFailure(err -> { - // error handling... - }); + .onSuccess(refreshedUser -> { + // the refreshed user is now available + }) + .onFailure(err -> { + // error handling... + }); } } public void example6(OAuth2Auth oauth2, User user) { // Revoke only the access token oauth2.revoke(user, "access_token") - .onSuccess(v -> { - // Session ended. But the refresh_token is still valid. - - // Revoke the refresh_token - oauth2.revoke(user, "refresh_token") - .onSuccess(v2 -> { - System.out.println("token revoked."); - }); - }); + .onSuccess(v -> { + // Session ended. But the refresh_token is still valid. + + // Revoke the refresh_token + oauth2.revoke(user, "refresh_token") + .onSuccess(v2 -> { + System.out.println("token revoked."); + }); + }); } public void example13(Vertx vertx) { // you would get this config from the keycloak admin console JsonObject keycloakJson = new JsonObject() - .put("realm", "master") - .put("realm-public-key", "MIIBIjANBgkqhk...wIDAQAB") - .put("auth-server-url", "http://localhost:9000/auth") - .put("ssl-required", "external") - .put("resource", "frontend") - .put("credentials", new JsonObject() - .put("secret", "2fbf5e18-b923-4a83-9657-b4ebd5317f60")); + .put("realm", "master") + .put("realm-public-key", "MIIBIjANBgkqhk...wIDAQAB") + .put("auth-server-url", "http://localhost:9000/auth") + .put("ssl-required", "external") + .put("resource", "frontend") + .put("credentials", new JsonObject() + .put("secret", "2fbf5e18-b923-4a83-9657-b4ebd5317f60")); // Initialize the OAuth2 Library OAuth2Auth oauth2 = KeycloakAuth - .create(vertx, OAuth2FlowType.PASSWORD, keycloakJson); + .create(vertx, OAuth2FlowType.PASSWORD, keycloakJson); // first get a token (authenticate) oauth2.authenticate( new UsernamePasswordCredentials("user", "secret")) - .onSuccess(user -> { - // now check for permissions - AuthorizationProvider authz = KeycloakAuthorization.create(); - - authz.getAuthorizations(user) - .onSuccess(v -> { - if (RoleBasedAuthorization.create("manage-account") - .setResource("account") - .match(user)) { - // this user is authorized to manage its account - } - }); - }); + .onSuccess(user -> { + // now check for permissions + AuthorizationProvider authz = KeycloakAuthorization.create(); + + authz.getAuthorizations(user) + .onSuccess(v -> { + if (RoleBasedAuthorization.create("manage-account") + .setResource("account") + .match(user)) { + // this user is authorized to manage its account + } + }); + }); } public void example14(User user) { @@ -236,23 +236,23 @@ public void example14(User user) { public void example15(OAuth2Auth oauth2, User user) { // OAuth2Auth level oauth2.authenticate(new TokenCredentials("opaque string")) - .onSuccess(theUser -> { - // token is valid! - }); + .onSuccess(theUser -> { + // token is valid! + }); // User level oauth2.authenticate(new TokenCredentials(user.get("access_token"))) - .onSuccess(authenticatedUser -> { - // Token is valid! - }); + .onSuccess(authenticatedUser -> { + // Token is valid! + }); } public void example16(OAuth2Auth oauth2) { // OAuth2Auth level oauth2.authenticate(new TokenCredentials("jwt-token")) - .onSuccess(theUser -> { - // token is valid! - }); + .onSuccess(theUser -> { + // token is valid! + }); } public void example17(User user) { @@ -266,8 +266,8 @@ public void example18(User user) { // the resource is "realm" // the authority is "add-user" if (PermissionBasedAuthorization.create("add-user") - .setResource("realm") - .match(user)) { + .setResource("realm") + .match(user)) { // Yes the user can add users to the application } } @@ -276,8 +276,8 @@ public void example19(User user) { // the role is "finance" // the authority is "year-report" if (PermissionBasedAuthorization.create("year-report") - .setResource("finance") - .match(user)) { + .setResource("finance") + .match(user)) { // Yes the user can access the year report from the finance department } } @@ -295,37 +295,37 @@ public void example21(User user) { public void example22(OAuth2Auth oauth2, User user) { oauth2.refresh(user) - .onSuccess(refreshedUser -> { - // the refresh call succeeded - }) - .onFailure(err -> { - // the token was not refreshed, a best practise would be - // to forcefully logout the user since this could be a - // symptom that you're logged out by the server and this - // token is not valid anymore. - }); + .onSuccess(refreshedUser -> { + // the refresh call succeeded + }) + .onFailure(err -> { + // the token was not refreshed, a best practise would be + // to forcefully logout the user since this could be a + // symptom that you're logged out by the server and this + // token is not valid anymore. + }); } public void example23(OAuth2Auth oauth2, User user) { oauth2.revoke(user, "access_token") - .onSuccess(v -> { - // the revoke call succeeded - }) - .onFailure(err -> { - // the token was not revoked. - }); + .onSuccess(v -> { + // the revoke call succeeded + }) + .onFailure(err -> { + // the token was not revoked. + }); } public void example24(OAuth2Auth oauth2, User user) { oauth2.authenticate(new TokenCredentials(user.get("access_token"))) - .onSuccess(validUser -> { - // the introspection call succeeded - }) - .onFailure(err -> { - // the token failed the introspection. You should proceed - // to logout the user since this means that this token is - // not valid anymore. - }); + .onSuccess(validUser -> { + // the introspection call succeeded + }) + .onFailure(err -> { + // the token failed the introspection. You should proceed + // to logout the user since this means that this token is + // not valid anymore. + }); } public void example25(Vertx vertx) { @@ -333,17 +333,17 @@ public void example25(Vertx vertx) { OpenIDConnectAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setSite("https://accounts.google.com")) - .onSuccess(oauth2 -> { - // the setup call succeeded. - // at this moment your auth is ready to use and - // google signature keys are loaded so tokens can be decoded and verified. - }) - .onFailure(err -> { - // the setup failed. - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setSite("https://accounts.google.com")) + .onSuccess(oauth2 -> { + // the setup call succeeded. + // at this moment your auth is ready to use and + // google signature keys are loaded so tokens can be decoded and verified. + }) + .onFailure(err -> { + // the setup failed. + }); } public void example25b(Vertx vertx) { @@ -351,56 +351,56 @@ public void example25b(Vertx vertx) { KeycloakAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setSite("https://keycloakhost:keycloakport/auth/realms/{realm}") - .setTenant("your-realm")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setSite("https://keycloakhost:keycloakport/auth/realms/{realm}") + .setTenant("your-realm")) + .onSuccess(oauth2 -> { + // ... + }); // Google example GoogleAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret")) + .onSuccess(oauth2 -> { + // ... + }); // Salesforce example SalesforceAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret")) + .onSuccess(oauth2 -> { + // ... + }); // Azure AD example AzureADAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setTenant("your-app-guid")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setTenant("your-app-guid")) + .onSuccess(oauth2 -> { + // ... + }); // IBM Cloud example IBMCloudAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setClientSecret("clientSecret") - .setSite("https://.appid.cloud.ibm.com/oauth/v4/{tenant}") - .setTenant("your-tenant-id")) - .onSuccess(oauth2 -> { - // ... - }); + .setClientId("clientId") + .setClientSecret("clientSecret") + .setSite("https://.appid.cloud.ibm.com/oauth/v4/{tenant}") + .setTenant("your-tenant-id")) + .onSuccess(oauth2 -> { + // ... + }); } public void example26(Vertx vertx) { @@ -408,23 +408,23 @@ public void example26(Vertx vertx) { OpenIDConnectAuth.discover( vertx, new OAuth2Options() - .setClientId("clientId") - .setTenant("your_realm") - .setSite("https://server:port/auth/realms/{tenant}")) - .onSuccess(oauth2 -> { - // the setup call succeeded. - // at this moment your auth is ready to use - }); + .setClientId("clientId") + .setTenant("your_realm") + .setSite("https://server:port/auth/realms/{tenant}")) + .onSuccess(oauth2 -> { + // the setup call succeeded. + // at this moment your auth is ready to use + }); } public void example21(OAuth2Auth oauth2) { // OAuth2Auth level oauth2.jWKSet() - .onSuccess(v -> { - // load was successful, if the server returned the header - // `Cache-Control` with a `max-age` then a periodic task - // will run at that time to refresh the keys - }); + .onSuccess(v -> { + // load was successful, if the server returned the header + // `Cache-Control` with a `max-age` then a periodic task + // will run at that time to refresh the keys + }); } public void example22(OAuth2Auth oauth2) { @@ -435,9 +435,9 @@ public void example22(OAuth2Auth oauth2) { if (keyId.equals("the-new-id")) { // 2. refresh the keys oauth2.jWKSet() - .onSuccess(v -> { - // ... - }); + .onSuccess(v -> { + // ... + }); } }); } @@ -445,10 +445,10 @@ public void example22(OAuth2Auth oauth2) { // create a dynamic client in keycloak 25.0.0 public void example23(Vertx vertx) { JsonObject options = new JsonObject().put("site", "https://server:port") - .put("tenant", "master") - .put("initialAccessToken", "initial-access-token"); + .put("tenant", "master") + .put("initialAccessToken", "initial-access-token"); KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(vertx, - new DCROptions(options)); + new DCROptions(options)); keycloakClientRegistration.create("junit-test-client").onSuccess(v -> { // ... }); @@ -457,13 +457,14 @@ public void example23(Vertx vertx) { // get a dynamic client from keycloak 25.0.0 public void example24(Vertx vertx) { JsonObject options = new JsonObject().put("site", "https://server:port") - .put("tenant", "master") - .put("initialAccessToken", "initial-access-token"); + .put("tenant", "master") + .put("initialAccessToken", "initial-access-token"); KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(vertx, - new DCROptions(options)); + new DCROptions(options)); // registrationAccessToken is unique for Keycloak implementation - JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", "registration-access-token") - .put("clientId", "junit-test-client"); + JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", + "registration-access-token") + .put("clientId", "junit-test-client"); keycloakClientRegistration.get(new DCRRequest(requJsonObject)).onSuccess(v -> { // ... }); @@ -472,13 +473,14 @@ public void example24(Vertx vertx) { // delete a dynamic client from keycloak 25.0.0 public void example27(Vertx vertx) { JsonObject options = new JsonObject().put("site", "https://server:port") - .put("tenant", "master") - .put("initialAccessToken", "initial-access-token"); + .put("tenant", "master") + .put("initialAccessToken", "initial-access-token"); KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(vertx, - new DCROptions(options)); + new DCROptions(options)); // registrationAccessToken is unique for Keycloak implementation - JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", "registration-access-token") - .put("clientId", "junit-test-client"); + JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", + "registration-access-token") + .put("clientId", "junit-test-client"); keycloakClientRegistration.delete(new DCRRequest(requJsonObject)).onSuccess(v -> { // ... }); @@ -486,32 +488,34 @@ public void example27(Vertx vertx) { // create initial access token in keycloak 25.0.0 public void example28(Vertx vertx) { - JsonObject header = new JsonObject().put("Authorization", String.format("Bearer %s", "admin-access-token")) - .put("Content-Type", "application/json"); + JsonObject header = new JsonObject().put("Authorization", + String.format("Bearer %s", "admin-access-token")) + .put("Content-Type", "application/json"); JsonObject payload = new JsonObject() - .put("expiration", 180) - .put("count", 1); + .put("expiration", 180) + .put("count", 1); new SimpleHttpClient(vertx, "https://server:port", new HttpClientOptions()) - .fetch(HttpMethod.POST, - "https://server:port/admin/realms/master/clients-initial-access", - header, payload.toBuffer()) - .onSuccess(v -> { - // get the initial access token from v.jsonObject().getString("token") - // ... - }); + .fetch(HttpMethod.POST, + "https://server:port/admin/realms/master/clients-initial-access", + header, payload.toBuffer()) + .onSuccess(v -> { + // get the initial access token from v.jsonObject().getString("token") + // ... + }); } // create a admin token to create initial access token in keycloak 25.0.0 public void example29(Vertx vertx) { JsonObject header = new JsonObject().put("Content-Type", "application/x-www-form-urlencoded"); - Buffer body = Buffer.buffer("grant_type=password&client_id=admin-cli&username=admin&password=secret"); + Buffer body = Buffer.buffer( + "grant_type=password&client_id=admin-cli&username=admin&password=secret"); new SimpleHttpClient(vertx, "https://server:port", new HttpClientOptions()) - .fetch(HttpMethod.POST, - "https://server:port/realms/master/protocol/openid-connect/token", - header, body) - .onSuccess(v -> { - // get the admin access token from v.jsonObject().getString("access_token") - // ... - }); + .fetch(HttpMethod.POST, + "https://server:port/realms/master/protocol/openid-connect/token", + header, body) + .onSuccess(v -> { + // get the admin access token from v.jsonObject().getString("access_token") + // ... + }); } } diff --git a/vertx-auth-oauth2/src/main/java/examples/package-info.java b/vertx-auth-oauth2/src/main/java/examples/package-info.java index 1ffec9a73..bd1756207 100644 --- a/vertx-auth-oauth2/src/main/java/examples/package-info.java +++ b/vertx-auth-oauth2/src/main/java/examples/package-info.java @@ -1,4 +1,4 @@ @Source package examples; -import io.vertx.docgen.Source; \ No newline at end of file +import io.vertx.docgen.Source; diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java index ffb04d297..2c255f45f 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/ClientRegistrationProvider.java @@ -24,4 +24,4 @@ public interface ClientRegistrationProvider { Future delete(DCRRequest dcrRequest); -} \ No newline at end of file +} diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java index 0b2db9126..46debc1ab 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCROptions.java @@ -81,4 +81,4 @@ public void setTenant(String tenant) { public String resourceUri() { return String.format("%s/realms/%s/clients-registrations/default", site, tenant); } -} \ No newline at end of file +} diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java index af8549c64..718ff4441 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRRequest.java @@ -53,4 +53,4 @@ public String getRegistrationAccessToken() { public void setRegistrationAccessToken(String registrationAccessToken) { this.registrationAccessToken = registrationAccessToken; } -} \ No newline at end of file +} diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java index a631fe5ca..01bfaea59 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/DCRResponse.java @@ -105,4 +105,4 @@ public String getRegistrationAccessToken() { public void setRegistrationAccessToken(String registrationAccessToken) { this.registrationAccessToken = registrationAccessToken; } -} \ No newline at end of file +} diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java index b79e8af4d..3b4fa0e8e 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/KeycloakClientRegistration.java @@ -20,4 +20,4 @@ public interface KeycloakClientRegistration extends ClientRegistrationProvider { static KeycloakClientRegistration create(Vertx vertx, DCROptions dcrOptions) { return new KeycloakClientRegistrationImpl(vertx, dcrOptions); } -} \ No newline at end of file +} diff --git a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java index 9353f3a18..f32d5b4c5 100644 --- a/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java +++ b/vertx-auth-oauth2/src/main/java/io/vertx/ext/auth/oauth2/dcr/impl/KeycloakClientRegistrationImpl.java @@ -85,4 +85,4 @@ private Future constructResponse(SimpleHttpResponse response, int e } return Future.succeededFuture(new DCRResponse(json)); } -} \ No newline at end of file +} diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java index 48e5dfcaf..9304de908 100644 --- a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRKeycloak25_0_0_IT.java @@ -56,126 +56,150 @@ public class DCRKeycloak25_0_0_IT { - private static final String REALM = "master"; - @Rule - public final RunTestOnContext rule = new RunTestOnContext(); + private static final String REALM = "master"; + @Rule + public final RunTestOnContext rule = new RunTestOnContext(); - private static GenericContainer keycloak; + private static GenericContainer keycloak; - @BeforeClass - public static void setupDocker() { - keycloak = new GenericContainer<>(DockerImageName.parse("quay.io/keycloak/keycloak:25.0.0")) - .withExposedPorts(8080) - .withEnv("KEYCLOAK_ADMIN", "admin") - .withEnv("KEYCLOAK_ADMIN_PASSWORD", "secret") - .withCommand("start-dev") - .waitingFor( - Wait.forHttp(String.format("/realms/%s/.well-known/openid-configuration", REALM)) - .forStatusCode(200) - .withStartupTimeout(Duration.ofSeconds(90))); - } + @BeforeClass + public static void setupDocker() { + keycloak = new GenericContainer<>(DockerImageName.parse("quay.io/keycloak/keycloak:25.0.0")) + .withExposedPorts(8080) + .withEnv("KEYCLOAK_ADMIN", "admin") + .withEnv("KEYCLOAK_ADMIN_PASSWORD", "secret") + .withCommand("start-dev") + .waitingFor( + Wait.forHttp(String.format( + "/realms/%s/.well-known/openid-configuration", REALM)) + .forStatusCode(200) + .withStartupTimeout(Duration.ofSeconds(90))); + } - @Before - public void setup() { - keycloak.start(); - } + @Before + public void setup() { + keycloak.start(); + } - @After - public void tearDown() { - keycloak.stop(); - } + @After + public void tearDown() { + keycloak.stop(); + } - @Test - public void testCreateDynamicClient() throws Exception { - String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); - String initialAccessToken = createInitialAccessToken(baseUrl, - getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) - .await(10, TimeUnit.SECONDS); - JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put("initialAccessToken", - initialAccessToken); - KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(rule.vertx(), - new DCROptions(options)); - Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); - DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); - assertNotNull(client.getId()); - assertEquals("junit-test-client", client.getClientId()); - assertEquals("client-secret", client.getClientAuthenticatorType()); - assertNotNull(client.getRegistrationAccessToken()); - assertNotNull(client.getSecret()); - } + @Test + public void testCreateDynamicClient() throws Exception { + String baseUrl = String.format("http://%s:%s", keycloak.getHost(), + keycloak.getMappedPort(8080)); + String initialAccessToken = createInitialAccessToken(baseUrl, + getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) + .await(10, TimeUnit.SECONDS); + JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put( + "initialAccessToken", + initialAccessToken); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create( + rule.vertx(), + new DCROptions(options)); + Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); + DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); + assertNotNull(client.getId()); + assertEquals("junit-test-client", client.getClientId()); + assertEquals("client-secret", client.getClientAuthenticatorType()); + assertNotNull(client.getRegistrationAccessToken()); + assertNotNull(client.getSecret()); + } - @Test - public void testGetDynamicClient() throws Exception { - String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); - String initialAccessToken = createInitialAccessToken(baseUrl, - getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) - .await(10, TimeUnit.SECONDS); - JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put("initialAccessToken", - initialAccessToken); - KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(rule.vertx(), - new DCROptions(options)); - Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); - DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); - assertNotNull(client.getId()); - assertEquals("junit-test-client", client.getClientId()); - JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", client.getRegistrationAccessToken()) - .put("clientId", "junit-test-client"); - DCRRequest dcrRequest = new DCRRequest(requJsonObject); - DCRResponse getResopnse = keycloakClientRegistration.get(dcrRequest).await(10, TimeUnit.SECONDS); - assertEquals("junit-test-client", getResopnse.getClientId()); - assertEquals(client.getRegistrationAccessToken(), getResopnse.getRegistrationAccessToken()); - } + @Test + public void testGetDynamicClient() throws Exception { + String baseUrl = String.format("http://%s:%s", keycloak.getHost(), + keycloak.getMappedPort(8080)); + String initialAccessToken = createInitialAccessToken(baseUrl, + getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) + .await(10, TimeUnit.SECONDS); + JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put( + "initialAccessToken", + initialAccessToken); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create( + rule.vertx(), + new DCROptions(options)); + Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); + DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); + assertNotNull(client.getId()); + assertEquals("junit-test-client", client.getClientId()); + JsonObject requJsonObject = new JsonObject() + .put("registrationAccessToken", client.getRegistrationAccessToken()) + .put("clientId", "junit-test-client"); + DCRRequest dcrRequest = new DCRRequest(requJsonObject); + DCRResponse getResopnse = keycloakClientRegistration.get(dcrRequest) + .await(10, TimeUnit.SECONDS); + assertEquals("junit-test-client", getResopnse.getClientId()); + assertEquals(client.getRegistrationAccessToken(), getResopnse.getRegistrationAccessToken()); + } - @Test - public void testDleteDynamicClient() throws Exception { - String baseUrl = String.format("http://%s:%s", keycloak.getHost(), keycloak.getMappedPort(8080)); - String initialAccessToken = createInitialAccessToken(baseUrl, - getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) - .await(10, TimeUnit.SECONDS); - JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put("initialAccessToken", - initialAccessToken); - KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create(rule.vertx(), - new DCROptions(options)); - Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); - DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); - assertNotNull(client.getId()); - assertEquals("junit-test-client", client.getClientId()); - JsonObject requJsonObject = new JsonObject().put("registrationAccessToken", client.getRegistrationAccessToken()) - .put("clientId", "junit-test-client"); - DCRRequest dcrRequest = new DCRRequest(requJsonObject); - DCRResponse getResopnse = keycloakClientRegistration.get(dcrRequest).await(10, TimeUnit.SECONDS); - assertEquals("junit-test-client", getResopnse.getClientId()); - assertEquals(client.getRegistrationAccessToken(), getResopnse.getRegistrationAccessToken()); - keycloakClientRegistration.delete(dcrRequest).await(10, TimeUnit.SECONDS); - keycloakClientRegistration.get(dcrRequest).onFailure(load -> { - assertEquals( - "Unauthorized: {\"error\":\"invalid_token\",\"error_description\":\"Not authorized to view client. Not valid token or client credentials provided.\"}", - load.getMessage()); - }); - } + @Test + public void testDleteDynamicClient() throws Exception { + String baseUrl = String.format("http://%s:%s", keycloak.getHost(), + keycloak.getMappedPort(8080)); + String initialAccessToken = createInitialAccessToken(baseUrl, + getAdminAccessToken(baseUrl).await(10, TimeUnit.SECONDS)) + .await(10, TimeUnit.SECONDS); + JsonObject options = new JsonObject().put("site", baseUrl).put("tenant", REALM).put( + "initialAccessToken", + initialAccessToken); + KeycloakClientRegistration keycloakClientRegistration = KeycloakClientRegistration.create( + rule.vertx(), + new DCROptions(options)); + Future dcrResponse = keycloakClientRegistration.create("junit-test-client"); + DCRResponse client = dcrResponse.await(10, TimeUnit.SECONDS); + assertNotNull(client.getId()); + assertEquals("junit-test-client", client.getClientId()); + JsonObject requJsonObject = new JsonObject() + .put("registrationAccessToken", client.getRegistrationAccessToken()) + .put("clientId", "junit-test-client"); + DCRRequest dcrRequest = new DCRRequest(requJsonObject); + DCRResponse getResopnse = keycloakClientRegistration.get(dcrRequest) + .await(10, TimeUnit.SECONDS); + assertEquals("junit-test-client", getResopnse.getClientId()); + assertEquals(client.getRegistrationAccessToken(), getResopnse.getRegistrationAccessToken()); + keycloakClientRegistration.delete(dcrRequest).await(10, TimeUnit.SECONDS); + keycloakClientRegistration.get(dcrRequest).onFailure(load -> { + assertEquals( + "Unauthorized: {\"error\":\"invalid_token\",\"error_description\":\"Not authorized to view client. Not valid token or client credentials provided.\"}", + load.getMessage()); + }); + } - private Future getAdminAccessToken(String baseUrl) throws Exception { - SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, new HttpClientOptions()); - JsonObject header = new JsonObject().put("Content-Type", "application/x-www-form-urlencoded"); - Buffer body = Buffer.buffer("grant_type=password&client_id=admin-cli&username=admin&password=secret"); - return simpleHttpClient - .fetch(HttpMethod.POST, String.format("%s/realms/%s/protocol/openid-connect/token", baseUrl, REALM), - header, - body) - .compose(response -> Future.succeededFuture(response.jsonObject().getString("access_token"))); - } + private Future getAdminAccessToken(String baseUrl) throws Exception { + SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, + new HttpClientOptions()); + JsonObject header = new JsonObject().put("Content-Type", "application/x-www-form-urlencoded"); + Buffer body = Buffer.buffer( + "grant_type=password&client_id=admin-cli&username=admin&password=secret"); + return simpleHttpClient + .fetch(HttpMethod.POST, + String.format("%s/realms/%s/protocol/openid-connect/token", baseUrl, + REALM), + header, + body) + .compose(response -> Future + .succeededFuture(response.jsonObject().getString("access_token"))); + } - private Future createInitialAccessToken(String baseUrl, String adminBearer) throws Exception { - CompletableFuture future = new CompletableFuture<>(); - JsonObject header = new JsonObject().put("Authorization", String.format("Bearer %s", adminBearer)) - .put("Content-Type", "application/json"); - JsonObject payload = new JsonObject() - .put("expiration", 180) - .put("count", 1); - SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, new HttpClientOptions()); - return simpleHttpClient - .fetch(HttpMethod.POST, String.format("%s/admin/realms/%s/clients-initial-access", baseUrl, REALM), - header, payload.toBuffer()) - .compose(response -> Future.succeededFuture(response.jsonObject().getString("token"))); - } + private Future createInitialAccessToken(String baseUrl, String adminBearer) + throws Exception { + CompletableFuture future = new CompletableFuture<>(); + JsonObject header = new JsonObject().put("Authorization", + String.format("Bearer %s", adminBearer)) + .put("Content-Type", "application/json"); + JsonObject payload = new JsonObject() + .put("expiration", 180) + .put("count", 1); + SimpleHttpClient simpleHttpClient = new SimpleHttpClient(rule.vertx(), baseUrl, + new HttpClientOptions()); + return simpleHttpClient + .fetch(HttpMethod.POST, + String.format("%s/admin/realms/%s/clients-initial-access", baseUrl, + REALM), + header, payload.toBuffer()) + .compose(response -> Future.succeededFuture(response.jsonObject().getString("token"))); + } } diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java index 11e2f4472..3c4f3c752 100644 --- a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCROptionsTest.java @@ -10,37 +10,38 @@ public class DCROptionsTest { - private static final String DCR_OPTION_JSO_STRING = "{\n" + - " \"site\": \"https://auth.example.com\",\n" + - " \"tenant\": \"master\",\n" + - " \"initialAccessToken\": \"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0\",\n" + - " \"httpClientOptions\": {\n" + - " \"defaultHost\": \"auth.example.com\"\n" + - " }\n" + - "}\n" + - ""; - - private DCROptions dcrOptions; - - @Before - public void setup() { - dcrOptions = new DCROptions(new JsonObject(DCR_OPTION_JSO_STRING)); - } - - @Test - public void testCreateFromJson() { - assertEquals("https://auth.example.com", dcrOptions.getSite()); - assertEquals("master", dcrOptions.getTenant()); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0", dcrOptions.getInitialAccessToken()); - assertEquals("auth.example.com", dcrOptions.getHttpClientOptions().getDefaultHost()); - } - - @Test - public void testSerializationToJson() { - io.vertx.core.json.JsonObject json = dcrOptions.toJson(); - assertEquals("https://auth.example.com", json.getString("site")); - assertEquals("master", json.getString("tenant")); - assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0", json.getString("initialAccessToken")); - assertEquals("auth.example.com", json.getJsonObject("httpClientOptions").getString("defaultHost")); - } + private static final String DCR_OPTION_JSO_STRING = "{\n" + + " \"site\": \"https://auth.example.com\",\n" + + " \"tenant\": \"master\",\n" + + " \"initialAccessToken\": \"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0\",\n" + + " \"httpClientOptions\": {\n" + + " \"defaultHost\": \"auth.example.com\"\n" + + " }\n" + + "}\n" + + ""; + + private DCROptions dcrOptions; + + @Before + public void setup() { + dcrOptions = new DCROptions(new JsonObject(DCR_OPTION_JSO_STRING)); + } + + @Test + public void testCreateFromJson() { + assertEquals("https://auth.example.com", dcrOptions.getSite()); + assertEquals("master", dcrOptions.getTenant()); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0", dcrOptions.getInitialAccessToken()); + assertEquals("auth.example.com", dcrOptions.getHttpClientOptions().getDefaultHost()); + } + + @Test + public void testSerializationToJson() { + io.vertx.core.json.JsonObject json = dcrOptions.toJson(); + assertEquals("https://auth.example.com", json.getString("site")); + assertEquals("master", json.getString("tenant")); + assertEquals("eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0", json.getString("initialAccessToken")); + assertEquals("auth.example.com", + json.getJsonObject("httpClientOptions").getString("defaultHost")); + } } diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java index dc62879f7..21689f193 100644 --- a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRRequestTest.java @@ -8,28 +8,28 @@ public class DCRRequestTest { - private static final String DCR_REQUEST_STRING = "{\n" + - " \"clientId\": \"my-client-id\",\n" + - " \"registrationAccessToken\": \"my-registration-access-token\"\n" + - "}"; - - private DCRRequest dcrRequest; - - @Before - public void setup() { - this.dcrRequest = new DCRRequest(new JsonObject(DCR_REQUEST_STRING)); - } - - @Test - public void testCreateFromJson() { - assert "my-client-id".equals(dcrRequest.getClientId()); - assert "my-registration-access-token".equals(dcrRequest.getRegistrationAccessToken()); - } - - @Test - public void testToJson() { - io.vertx.core.json.JsonObject json = dcrRequest.toJson(); - assert "my-client-id".equals(json.getString("clientId")); - assert "my-registration-access-token".equals(json.getString("registrationAccessToken")); - } + private static final String DCR_REQUEST_STRING = "{\n" + + " \"clientId\": \"my-client-id\",\n" + + " \"registrationAccessToken\": \"my-registration-access-token\"\n" + + "}"; + + private DCRRequest dcrRequest; + + @Before + public void setup() { + this.dcrRequest = new DCRRequest(new JsonObject(DCR_REQUEST_STRING)); + } + + @Test + public void testCreateFromJson() { + assert "my-client-id".equals(dcrRequest.getClientId()); + assert "my-registration-access-token".equals(dcrRequest.getRegistrationAccessToken()); + } + + @Test + public void testToJson() { + io.vertx.core.json.JsonObject json = dcrRequest.toJson(); + assert "my-client-id".equals(json.getString("clientId")); + assert "my-registration-access-token".equals(json.getString("registrationAccessToken")); + } } diff --git a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java index 4294f24d7..065998d1b 100644 --- a/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java +++ b/vertx-auth-oauth2/src/test/java/io/vertx/tests/DCRResponseTest.java @@ -8,39 +8,40 @@ import io.vertx.ext.auth.oauth2.DCRResponse; public class DCRResponseTest { - private static final String DCR_RESPPONSE_STRING = "{\n" + - " \"id\": \"12345\",\n" + - " \"clientId\": \"my-client-id\",\n" + - " \"enabled\": true,\n" + - " \"clientAuthenticatorType\": \"client-secret\",\n" + - " \"secret\": \"my-secret\",\n" + - " \"registrationAccessToken\": \"my-registration-access-token\"\n" + - "}"; - private DCRResponse dcrResponse; - - @Before - public void setup() { - this.dcrResponse = new DCRResponse(new JsonObject(DCR_RESPPONSE_STRING)); - } - - @Test - public void testCreateFromJson() { - assert "12345".equals(dcrResponse.getId()); - assert "my-client-id".equals(dcrResponse.getClientId()); - assert dcrResponse.isEnabled(); - assert "client-secret".equals(dcrResponse.getClientAuthenticatorType()); - assert "my-secret".equals(dcrResponse.getSecret()); - assert "my-registration-access-token".equals(dcrResponse.getRegistrationAccessToken()); - } - - @Test - public void testSerializationToJson() { - JsonObject json = dcrResponse.toJson(); - assert "12345".equals(json.getString("id")); - assert "my-client-id".equals(json.getString("clientId")); - assert json.getBoolean("enabled"); - assert "client-secret".equals(json.getString("clientAuthenticatorType")); - assert "my-secret".equals(json.getString("secret")); - assert "my-registration-access-token".equals(json.getString("registrationAccessToken")); - } + + private static final String DCR_RESPPONSE_STRING = "{\n" + + " \"id\": \"12345\",\n" + + " \"clientId\": \"my-client-id\",\n" + + " \"enabled\": true,\n" + + " \"clientAuthenticatorType\": \"client-secret\",\n" + + " \"secret\": \"my-secret\",\n" + + " \"registrationAccessToken\": \"my-registration-access-token\"\n" + + "}"; + private DCRResponse dcrResponse; + + @Before + public void setup() { + this.dcrResponse = new DCRResponse(new JsonObject(DCR_RESPPONSE_STRING)); + } + + @Test + public void testCreateFromJson() { + assert "12345".equals(dcrResponse.getId()); + assert "my-client-id".equals(dcrResponse.getClientId()); + assert dcrResponse.isEnabled(); + assert "client-secret".equals(dcrResponse.getClientAuthenticatorType()); + assert "my-secret".equals(dcrResponse.getSecret()); + assert "my-registration-access-token".equals(dcrResponse.getRegistrationAccessToken()); + } + + @Test + public void testSerializationToJson() { + JsonObject json = dcrResponse.toJson(); + assert "12345".equals(json.getString("id")); + assert "my-client-id".equals(json.getString("clientId")); + assert json.getBoolean("enabled"); + assert "client-secret".equals(json.getString("clientAuthenticatorType")); + assert "my-secret".equals(json.getString("secret")); + assert "my-registration-access-token".equals(json.getString("registrationAccessToken")); + } }