From 5dbdcbf2f8fe7372bcf067c470408b26e86ecf67 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 3 Nov 2022 17:43:23 +0100 Subject: [PATCH 01/49] Realm export: introduce command line options --- .../config/KeycloakConfigApplication.java | 3 +- .../keycloak/config/KeycloakConfigRunner.java | 47 +++++++++++----- .../properties/ExportConfigProperties.java | 50 +++++++++++++++++ .../config/repository/RealmRepository.java | 5 ++ .../service/export/RealmExportService.java | 56 +++++++++++++++++++ 5 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java create mode 100644 src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java index 87784b03c..e51519a86 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java @@ -20,6 +20,7 @@ package de.adorsys.keycloak.config; +import de.adorsys.keycloak.config.properties.ExportConfigProperties; import de.adorsys.keycloak.config.properties.ImportConfigProperties; import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; import org.springframework.boot.SpringApplication; @@ -27,7 +28,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication(proxyBeanMethods = false) -@EnableConfigurationProperties({KeycloakConfigProperties.class, ImportConfigProperties.class}) +@EnableConfigurationProperties({KeycloakConfigProperties.class, ImportConfigProperties.class, ExportConfigProperties.class}) public class KeycloakConfigApplication { public static void main(String[] args) { // https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java index ddda773c0..8d24a9193 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java @@ -22,9 +22,11 @@ import de.adorsys.keycloak.config.model.KeycloakImport; import de.adorsys.keycloak.config.model.RealmImport; +import de.adorsys.keycloak.config.properties.ExportConfigProperties; import de.adorsys.keycloak.config.properties.ImportConfigProperties; import de.adorsys.keycloak.config.provider.KeycloakImportProvider; import de.adorsys.keycloak.config.service.RealmImportService; +import de.adorsys.keycloak.config.service.export.RealmExportService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -46,6 +48,8 @@ public class KeycloakConfigRunner implements CommandLineRunner, ExitCodeGenerato private final KeycloakImportProvider keycloakImportProvider; private final RealmImportService realmImportService; private final ImportConfigProperties importConfigProperties; + private final ExportConfigProperties exportConfigProperties; + private final RealmExportService realmExportService; private int exitCode = 0; @@ -53,10 +57,14 @@ public class KeycloakConfigRunner implements CommandLineRunner, ExitCodeGenerato public KeycloakConfigRunner( KeycloakImportProvider keycloakImportProvider, RealmImportService realmImportService, - ImportConfigProperties importConfigProperties) { + ImportConfigProperties importConfigProperties, + ExportConfigProperties exportConfigProperties, + RealmExportService realmExportService) { this.keycloakImportProvider = keycloakImportProvider; this.realmImportService = realmImportService; this.importConfigProperties = importConfigProperties; + this.exportConfigProperties = exportConfigProperties; + this.realmExportService = realmExportService; } @Override @@ -67,18 +75,10 @@ public int getExitCode() { @Override public void run(String... args) { try { - Collection importLocations = importConfigProperties.getFiles().getLocations(); - KeycloakImport keycloakImport = keycloakImportProvider.readFromLocations(importLocations); - - Map>> realmImports = keycloakImport.getRealmImports(); - - for (Map> realmImportLocations : realmImports.values()) { - for (Map.Entry> realmImport : realmImportLocations.entrySet()) { - logger.info("Importing file '{}'", realmImport.getKey()); - for (RealmImport realmImportParts : realmImport.getValue()) { - realmImportService.doImport(realmImportParts); - } - } + if (exportConfigProperties.isEnabled()) { + runExport(); + } else { + runImport(); } } catch (NullPointerException e) { throw e; @@ -96,4 +96,25 @@ public void run(String... args) { logger.info("keycloak-config-cli running in {}.", formattedTime); } } + + private void runImport() { + Collection importLocations = importConfigProperties.getFiles().getLocations(); + KeycloakImport keycloakImport = keycloakImportProvider.readFromLocations(importLocations); + + Map>> realmImports = keycloakImport.getRealmImports(); + + for (Map> realmImportLocations : realmImports.values()) { + for (Map.Entry> realmImport : realmImportLocations.entrySet()) { + logger.info("Importing file '{}'", realmImport.getKey()); + for (RealmImport realmImportParts : realmImport.getValue()) { + realmImportService.doImport(realmImportParts); + } + } + } + } + + private void runExport() { + logger.info("Exporting all the realms!"); + realmExportService.doExports(); + } } diff --git a/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java new file mode 100644 index 000000000..23a29556f --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java @@ -0,0 +1,50 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +@ConfigurationProperties(prefix = "export", ignoreUnknownFields = false) +@ConstructorBinding +@Validated +public class ExportConfigProperties { + + private final boolean enabled; + + private final List excludes; + + public ExportConfigProperties(boolean enabled, List excludes) { + this.enabled = enabled; + this.excludes = excludes == null ? List.of() : excludes; + } + + public boolean isEnabled() { + return enabled; + } + + public List getExcludes() { + return excludes; + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java index c71a6483a..13c28b85b 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java @@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; import javax.ws.rs.WebApplicationException; @Service @@ -105,4 +106,8 @@ public void removeDefaultDefaultClientScope(String realmName, String scopeId) { public void removeDefaultOptionalClientScope(String realmName, String scopeId) { getResource(realmName).removeDefaultOptionalClientScope(scopeId); } + + public List getRealms() { + return keycloakProvider.getInstance().realms().findAll(); + } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java new file mode 100644 index 000000000..2f23ed005 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -0,0 +1,56 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.export; + +import de.adorsys.keycloak.config.KeycloakConfigRunner; +import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import de.adorsys.keycloak.config.repository.RealmRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class RealmExportService { + + private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); + + private final RealmRepository realmRepository; + private final ExportConfigProperties exportConfigProperties; + + @Autowired + public RealmExportService(RealmRepository realmRepository, ExportConfigProperties exportConfigProperties) { + this.realmRepository = realmRepository; + this.exportConfigProperties = exportConfigProperties; + } + + public void doExports() { + var excludes = exportConfigProperties.getExcludes(); + for (var realm : realmRepository.getRealms()) { + var realmName = realm.getRealm(); + if (excludes.contains(realmName)) { + logger.info("Skipping realm {}", realmName); + } else { + logger.info("Exporting realm {}", realmName); + } + } + } +} From c7e759d42cae2d536981e71abe6f362958dc84b8 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 7 Nov 2022 18:45:02 +0100 Subject: [PATCH 02/49] Compare and import non-collection properties --- .gitignore | 1 + pom.xml | 5 + .../keycloak/config/KeycloakConfigRunner.java | 7 +- .../properties/ExportConfigProperties.java | 19 +- .../service/export/RealmExportService.java | 263 ++- .../application-export-dev.properties | 11 + .../reference-realms/19.0.3/realm.json | 1770 +++++++++++++++++ 7 files changed, 2070 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/application-export-dev.properties create mode 100644 src/main/resources/reference-realms/19.0.3/realm.json diff --git a/.gitignore b/.gitignore index 336b753a9..835791c49 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ release.properties /*.json /test* +/exports diff --git a/pom.xml b/pom.xml index cb12d887b..58af13362 100644 --- a/pom.xml +++ b/pom.xml @@ -230,6 +230,11 @@ jackson-databind + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + org.yaml snakeyaml diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java index 8d24a9193..4e0ab6180 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java @@ -34,6 +34,9 @@ import org.springframework.boot.ExitCodeGenerator; import org.springframework.stereotype.Component; +import java.beans.IntrospectionException; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; @@ -73,7 +76,7 @@ public int getExitCode() { } @Override - public void run(String... args) { + public void run(String... args) throws Exception { try { if (exportConfigProperties.isEnabled()) { runExport(); @@ -113,7 +116,7 @@ private void runImport() { } } - private void runExport() { + private void runExport() throws IOException, IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchMethodException { logger.info("Exporting all the realms!"); realmExportService.doExports(); } diff --git a/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java index 23a29556f..cd9299222 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java @@ -25,6 +25,7 @@ import org.springframework.validation.annotation.Validated; import java.util.List; +import javax.validation.constraints.NotNull; @ConfigurationProperties(prefix = "export", ignoreUnknownFields = false) @ConstructorBinding @@ -32,12 +33,18 @@ public class ExportConfigProperties { private final boolean enabled; - private final List excludes; + @NotNull + private final String location; + + @NotNull + private final String keycloakVersion; - public ExportConfigProperties(boolean enabled, List excludes) { + public ExportConfigProperties(boolean enabled, List excludes, String keycloakVersion, String location) { this.enabled = enabled; this.excludes = excludes == null ? List.of() : excludes; + this.keycloakVersion = keycloakVersion; + this.location = location; } public boolean isEnabled() { @@ -47,4 +54,12 @@ public boolean isEnabled() { public List getExcludes() { return excludes; } + + public String getKeycloakVersion() { + return keycloakVersion; + } + + public String getLocation() { + return location; + } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java index 2f23ed005..76f9862db 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -20,29 +20,235 @@ package de.adorsys.keycloak.config.service.export; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import de.adorsys.keycloak.config.KeycloakConfigRunner; import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; import de.adorsys.keycloak.config.repository.RealmRepository; +import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; + @Service public class RealmExportService { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper YAML_MAPPER = new YAMLMapper(); + private static final Set REALM_DEFAULT_FIELD_NAMES = new HashSet<>(); + + private static final Map DESCRIPTORS = new HashMap<>(); + + static { + YAML_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + REALM_DEFAULT_FIELD_NAMES.add("displayName"); + REALM_DEFAULT_FIELD_NAMES.add("displayNameHtml"); + REALM_DEFAULT_FIELD_NAMES.add("notBefore"); + REALM_DEFAULT_FIELD_NAMES.add("defaultSignatureAlgorithm"); + REALM_DEFAULT_FIELD_NAMES.add("revokeRefreshToken"); + REALM_DEFAULT_FIELD_NAMES.add("refreshTokenMaxReuse"); + REALM_DEFAULT_FIELD_NAMES.add("accessTokenLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("accessTokenLifespanForImplicitFlow"); + REALM_DEFAULT_FIELD_NAMES.add("ssoSessionIdleTimeout"); + REALM_DEFAULT_FIELD_NAMES.add("ssoSessionMaxLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("ssoSessionIdleTimeoutRememberMe"); + REALM_DEFAULT_FIELD_NAMES.add("ssoSessionMaxLifespanRememberMe"); + REALM_DEFAULT_FIELD_NAMES.add("offlineSessionIdleTimeout"); + REALM_DEFAULT_FIELD_NAMES.add("offlineSessionMaxLifespanEnabled"); + REALM_DEFAULT_FIELD_NAMES.add("offlineSessionMaxLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("clientSessionIdleTimeout"); + REALM_DEFAULT_FIELD_NAMES.add("clientSessionMaxLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("clientOfflineSessionIdleTimeout"); + REALM_DEFAULT_FIELD_NAMES.add("clientOfflineSessionMaxLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("accessCodeLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("accessCodeLifespanUserAction"); + REALM_DEFAULT_FIELD_NAMES.add("accessCodeLifespanLogin"); + REALM_DEFAULT_FIELD_NAMES.add("actionTokenGeneratedByAdminLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("actionTokenGeneratedByUserLifespan"); + REALM_DEFAULT_FIELD_NAMES.add("OAuth2DeviceCodeLifespan"); // Not equal to field name, derived from getter/setter for bean introspection + REALM_DEFAULT_FIELD_NAMES.add("OAuth2DevicePollingInterval"); // Not equal to field name, derived from getter/setter for bean introspection + REALM_DEFAULT_FIELD_NAMES.add("sslRequired"); + REALM_DEFAULT_FIELD_NAMES.add("registrationAllowed"); + REALM_DEFAULT_FIELD_NAMES.add("registrationEmailAsUsername"); + REALM_DEFAULT_FIELD_NAMES.add("rememberMe"); + REALM_DEFAULT_FIELD_NAMES.add("verifyEmail"); + REALM_DEFAULT_FIELD_NAMES.add("loginWithEmailAllowed"); + REALM_DEFAULT_FIELD_NAMES.add("duplicateEmailsAllowed"); + REALM_DEFAULT_FIELD_NAMES.add("resetPasswordAllowed"); + REALM_DEFAULT_FIELD_NAMES.add("editUsernameAllowed"); + REALM_DEFAULT_FIELD_NAMES.add("bruteForceProtected"); + REALM_DEFAULT_FIELD_NAMES.add("permanentLockout"); + REALM_DEFAULT_FIELD_NAMES.add("maxFailureWaitSeconds"); + REALM_DEFAULT_FIELD_NAMES.add("minimumQuickLoginWaitSeconds"); + REALM_DEFAULT_FIELD_NAMES.add("waitIncrementSeconds"); + REALM_DEFAULT_FIELD_NAMES.add("quickLoginCheckMilliSeconds"); + REALM_DEFAULT_FIELD_NAMES.add("maxDeltaTimeSeconds"); + REALM_DEFAULT_FIELD_NAMES.add("failureFactor"); + REALM_DEFAULT_FIELD_NAMES.add("privateKey"); + REALM_DEFAULT_FIELD_NAMES.add("publicKey"); + REALM_DEFAULT_FIELD_NAMES.add("certificate"); + REALM_DEFAULT_FIELD_NAMES.add("codeSecret"); + REALM_DEFAULT_FIELD_NAMES.add("passwordPolicy"); + REALM_DEFAULT_FIELD_NAMES.add("otpPolicyType"); + REALM_DEFAULT_FIELD_NAMES.add("otpPolicyAlgorithm"); + REALM_DEFAULT_FIELD_NAMES.add("otpPolicyInitialCounter"); + REALM_DEFAULT_FIELD_NAMES.add("otpPolicyDigits"); + REALM_DEFAULT_FIELD_NAMES.add("otpPolicyLookAheadWindow"); + REALM_DEFAULT_FIELD_NAMES.add("otpPolicyPeriod"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyRpEntityName"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyRpId"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyAttestationConveyancePreference"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyAuthenticatorAttachment"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyRequireResidentKey"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyUserVerificationRequirement"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyCreateTimeout"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyAvoidSameAuthenticatorRegister"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessRpEntityName"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessRpId"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessAttestationConveyancePreference"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessAuthenticatorAttachment"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessRequireResidentKey"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessUserVerificationRequirement"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessCreateTimeout"); + REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister"); + REALM_DEFAULT_FIELD_NAMES.add("loginTheme"); + REALM_DEFAULT_FIELD_NAMES.add("accountTheme"); + REALM_DEFAULT_FIELD_NAMES.add("adminTheme"); + REALM_DEFAULT_FIELD_NAMES.add("emailTheme"); + REALM_DEFAULT_FIELD_NAMES.add("eventsEnabled"); + REALM_DEFAULT_FIELD_NAMES.add("eventsExpiration"); + REALM_DEFAULT_FIELD_NAMES.add("adminEventsEnabled"); + REALM_DEFAULT_FIELD_NAMES.add("adminEventsDetailsEnabled"); + REALM_DEFAULT_FIELD_NAMES.add("internationalizationEnabled"); + REALM_DEFAULT_FIELD_NAMES.add("defaultLocale"); + REALM_DEFAULT_FIELD_NAMES.add("browserFlow"); + REALM_DEFAULT_FIELD_NAMES.add("registrationFlow"); + REALM_DEFAULT_FIELD_NAMES.add("directGrantFlow"); + REALM_DEFAULT_FIELD_NAMES.add("resetCredentialsFlow"); + REALM_DEFAULT_FIELD_NAMES.add("clientAuthenticationFlow"); + REALM_DEFAULT_FIELD_NAMES.add("dockerAuthenticationFlow"); + REALM_DEFAULT_FIELD_NAMES.add("keycloakVersion"); + REALM_DEFAULT_FIELD_NAMES.add("userManagedAccessAllowed"); + + /* + * TODO fields: + * + * roles + * groups + * defaultRoles + * defaultRole + * defaultGroups + * requiredCredentials + * otpSupportedApplications + * webAuthnPolicySignatureAlgorithms + * webAuthnPolicyAcceptableAaguids + * webAuthnPolicyPasswordlessSignatureAlgorithms + * webAuthnPolicyPasswordlessAcceptableAaguids + * clientProfiles + * clientPolicies + * users + * federatedUsers + * scopeMappings + * clientScopeMappings + * clients + * clientScopes + * defaultDefaultClientScopes + * defaultOptionalClientScopes + * browserSecurityHeaders + * smtpServer + * userFederationProviders + * userFederationMappers + * eventsListeners + * enabledEventTypes + * identityProviders + * identityProviderMappers + * protocolMappers + * components + * supportedLocales + * authenticationFlows + * authenticatorConfig + * requiredActions + * attributes + */ + + try { + for (var descriptor : Introspector.getBeanInfo(RealmRepresentation.class).getPropertyDescriptors()) { + var fieldName = descriptor.getName(); + if (REALM_DEFAULT_FIELD_NAMES.contains(fieldName)) { + // Override the read method for boxed booleans if they don't use the get-Prefix + if ((descriptor.getPropertyType().equals(Boolean.class) && descriptor.getReadMethod() == null)) { + var getterName = "is" + capitalizeFieldName(fieldName); + descriptor.setReadMethod(RealmRepresentation.class.getMethod(getterName)); + } + + // For some reason, setDockerAuthenticationFlow is a "fluent-style" setter and returns itself, which disqualifies it as a setter + if (fieldName.equals("dockerAuthenticationFlow")) { + descriptor.setWriteMethod(RealmRepresentation.class.getMethod("setDockerAuthenticationFlow", String.class)); + } + + DESCRIPTORS.put(fieldName, descriptor); + } + } + } catch (IntrospectionException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private static String capitalizeFieldName(String fieldName) { + return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); + } private final RealmRepository realmRepository; private final ExportConfigProperties exportConfigProperties; + private final KeycloakConfigProperties keycloakConfigProperties; @Autowired - public RealmExportService(RealmRepository realmRepository, ExportConfigProperties exportConfigProperties) { + public RealmExportService(RealmRepository realmRepository, + ExportConfigProperties exportConfigProperties, + KeycloakConfigProperties keycloakConfigProperties) { this.realmRepository = realmRepository; this.exportConfigProperties = exportConfigProperties; + this.keycloakConfigProperties = keycloakConfigProperties; } - public void doExports() { + public void doExports() throws IOException, IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchMethodException { + var outputLocation = Paths.get(exportConfigProperties.getLocation()); + if (!Files.exists(outputLocation)) { + Files.createDirectories(outputLocation); + } + if (!Files.isDirectory(outputLocation)) { + logger.error("Output location '{}' is not a directory. Aborting.", exportConfigProperties.getLocation()); + } + var keycloakConfigVersion = keycloakConfigProperties.getVersion(); + var exportVersion = exportConfigProperties.getKeycloakVersion(); + if (!exportVersion.equals(keycloakConfigVersion)) { + logger.warn("Keycloak-Config-CLI keycloak version {} and export keycloak version {} are not equal." + + " This may cause problems if the API changed." + + " Please compile keycloak-config-cli with a matching keycloak version!", + keycloakConfigVersion, exportVersion); + } + RealmRepresentation defaultRealm; + try (var is = getClass().getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportConfigProperties.getKeycloakVersion()))) { + if (is == null) { + logger.error("Reference realm for version {} does not exist", exportConfigProperties.getKeycloakVersion()); + return; + } + defaultRealm = OBJECT_MAPPER.readValue(is, RealmRepresentation.class); + } var excludes = exportConfigProperties.getExcludes(); for (var realm : realmRepository.getRealms()) { var realmName = realm.getRealm(); @@ -50,6 +256,59 @@ public void doExports() { logger.info("Skipping realm {}", realmName); } else { logger.info("Exporting realm {}", realmName); + var strippedRealm = new RealmRepresentation(); + strippedRealm.setRealm(realm.getRealm()); + strippedRealm.setEnabled(realm.isEnabled()); + if (!realm.getId().equals(realm.getRealm())) { + // If the realm ID diverges from the name, include it in the dump, otherwise ignore it + strippedRealm.setId(realm.getId()); + } + + for (var fieldName : REALM_DEFAULT_FIELD_NAMES) { + setNonDefaultValue(strippedRealm, defaultRealm, realm, fieldName); + } + + var outputFile = Paths.get(exportConfigProperties.getLocation(), String.format("%s.yaml", realmName)); + try (var os = new FileOutputStream(outputFile.toFile())) { + YAML_MAPPER.writeValue(os, strippedRealm); + } + } + } + } + + private void setNonDefaultValue(RealmRepresentation strippedRealm, + RealmRepresentation defaultRealm, + RealmRepresentation exportedRealm, + String fieldName) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { + var propertyDescriptor = DESCRIPTORS.get(fieldName); + if (propertyDescriptor == null) { + logger.error("Can't set field '{}', no such property on RealmRepresentation", fieldName); + return; + } + + var getter = propertyDescriptor.getReadMethod(); + var setter = propertyDescriptor.getWriteMethod(); + + /* + * These methods need special treatment because while the getter returns boxed types, the setter accepts unboxed types. + * This causes a mismatch and the proper getter can't be found. + */ + if (fieldName.equals("eventsEnabled")) { + getter = RealmRepresentation.class.getMethod("isEventsEnabled"); + } + if (fieldName.equals("eventsExpiration")) { + setter = RealmRepresentation.class.getMethod("setEventsExpiration", long.class); + } + Object defaultValue = getter.invoke(defaultRealm); + Object exportedValue = getter.invoke(exportedRealm); + + if (!Objects.equals(defaultValue, exportedValue)) { + /* + * If the special setters are called with null values, we get an NPE because these only accept primitives + * Therefore, do nothing, the underlying value will still be null because it's not a primitive + */ + if (!((fieldName.equals("eventsEnabled") || fieldName.equals("eventsExpiration")) && exportedValue == null)) { + setter.invoke(strippedRealm, exportedValue); } } } diff --git a/src/main/resources/application-export-dev.properties b/src/main/resources/application-export-dev.properties new file mode 100644 index 000000000..d003f2a16 --- /dev/null +++ b/src/main/resources/application-export-dev.properties @@ -0,0 +1,11 @@ +import.files.locations=/tmp +keycloak.url=http://localhost:8080 +keycloak.client-id=keycloak-config-cli +keycloak.client-secret=mysecret +keycloak.grant-type=client_credentials +keycloak.availability-check.enabled=true +keycloak.availability-check.timeout=120s +export.enabled=true +export.excludes=fgvsad,master +export.keycloak-version=19.0.3 +export.location=./exports diff --git a/src/main/resources/reference-realms/19.0.3/realm.json b/src/main/resources/reference-realms/19.0.3/realm.json new file mode 100644 index 000000000..4c0835dbf --- /dev/null +++ b/src/main/resources/reference-realms/19.0.3/realm.json @@ -0,0 +1,1770 @@ +{ + "id" : "test1234", + "realm" : "test1234", + "notBefore" : 1212340, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "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, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "40ea56a2-b8f4-4ace-b858-53f62283ded7", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "test1234", + "attributes" : { } + }, { + "id" : "fc2832ce-72fc-4df6-b7a5-b312b50584a5", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "test1234", + "attributes" : { } + }, { + "id" : "d5107cde-a901-42af-a319-8bc3e61c92fc", + "name" : "default-roles-test1234", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "test1234", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "38371fd2-caa1-4b7a-9817-5873b5b9c0a1", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "54679367-c3b8-4247-8999-d4a71f582b8f", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "7ce64142-6c45-4a3b-a994-6eb0db13c6b3", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "c1e7097a-9017-462e-981f-fe5a06e1e25e", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "c8eec92c-fa40-41aa-9ce5-54dbe05c0a33", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "91d59567-8432-4852-8fe8-2c7983c8adf2", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "340e8cd3-2c4a-40fb-b46d-643fd2a287c1", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "637c825a-553d-4632-983a-05f67d92fe32", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "6be5c796-f77b-4a43-b16d-4fd3f473da7f", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "4c114feb-5b2a-4a45-8f6f-02b6ff7e5d4d", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "ce6051c3-46a7-43dd-b8fc-bade1425629d", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "87f079c5-bf49-4f25-95bd-d27be9a9d5de", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "a2f45bd6-92fa-4564-9738-93493fa8f81a", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "0ce58d28-5fdd-4113-be47-b8329a565c47", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "9bccce09-8600-47bc-b066-9be7e7ca3d7b", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "aa923952-2afd-4a5c-aaf6-c9d3c19027cb", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "794cc33e-fee5-49c3-8254-c7fe4588620e", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "view-authorization", "manage-clients", "query-clients", "manage-events", "view-events", "query-users", "impersonation", "view-clients", "view-identity-providers", "manage-realm", "manage-identity-providers", "manage-authorization", "query-groups", "query-realms", "create-client", "view-realm", "view-users", "manage-users" ] + } + }, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "131d4c8a-6d8e-476d-96fc-8a2626ca3b0b", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + }, { + "id" : "405cbdd0-713b-4a30-a38c-4f10effe5148", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "2482511e-dacf-4559-a4df-10b7b925b637", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "fffd9aca-0bb9-463b-b2cd-ebbdf6a5b6ec", + "attributes" : { } + } ], + "account" : [ { + "id" : "188012b7-c790-4802-a398-e35c19d3d42c", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "attributes" : { } + }, { + "id" : "c065ad9a-fa86-4867-a3a1-7572c78f2e42", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "attributes" : { } + }, { + "id" : "36849282-f262-441e-be52-e0aa654e5a45", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "attributes" : { } + }, { + "id" : "9afe553c-5249-4147-b36a-44929fb2749a", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "attributes" : { } + }, { + "id" : "432459e7-14ac-4c1f-a4c2-67282b1ba469", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "attributes" : { } + }, { + "id" : "a24e3e85-f8c8-40e0-ae3c-00c93ee7ad60", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "attributes" : { } + }, { + "id" : "3dcadf7f-b3bb-42f4-ad13-966f333a6274", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "d5107cde-a901-42af-a319-8bc3e61c92fc", + "name" : "default-roles-test1234", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "test1234" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account" ] + } ] + }, + "clients" : [ { + "id" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/test1234/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/test1234/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", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "db19ba85-ec7e-44ea-a3e5-bb306052317a", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/test1234/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/test1234/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" : "ac105987-e3f5-47d1-addd-d3068502f643", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "e0ad29c0-9e11-4520-a2bb-06db3d6d6648", + "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", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "fffd9aca-0bb9-463b-b2cd-ebbdf6a5b6ec", + "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", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "e4979954-cd40-4f56-9e57-353f96458beb", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "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", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "9ee26d74-eb01-4852-826d-e174a8bd0a20", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/test1234/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/test1234/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" : "97da4dab-74f1-4a22-92ce-8bf514723dca", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "eba69b1b-2534-4abe-b593-37cda28b3519", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "c40831e0-048c-4dda-9757-87910638066f", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "fc89d395-906d-4563-a78c-272e380476dd", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "7205654a-1627-4366-b2fa-e7aefe079bf4", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "cd06ba24-2aaf-4997-a618-05c4262d5ab6", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "032183d7-3f79-47d3-b8f6-48034e14be10", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "98e7ba24-a78a-458a-8687-45691d8d2349", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "d1aa999a-aaba-45ca-b305-b9d525af937f", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "de986b34-e6de-40c2-aaad-9def6570e7ee", + "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" : "4b82ac72-2b9b-46bf-8981-fd790c83a0a3", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "982f8f13-367c-423a-8f92-a4153081bafa", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "a69f010d-3635-4067-83b9-f814c4ed5a92", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "ff126c19-9c3d-488d-ae61-2e4c964da5aa", + "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" : "2d41746f-6c92-43be-b149-20774a923426", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "e386fc51-6c8e-4e97-b709-1d2c7b61ca09", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "7bc05bc5-1766-4227-af40-c9e147e15450", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "5560e6b8-4459-4825-88d6-8c9e666474a0", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "38ed06c5-f8a8-4f10-9699-4415fdae201b", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "41b0f4f7-fd2d-4cfc-bcbd-50bdf9d4d78a", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "b5de8af6-82a0-40b9-a6a9-bd3bd51086b4", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "49eed952-5ec9-4675-a88c-5cd8ef171cea", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "b69119a9-768b-42ab-9156-8033b2a44d7c", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "0833a1f2-5167-41d6-bf8e-f5946e560dd3", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "00ad740c-05a4-409e-846c-34cf34dd843c", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "518dcdfa-f7de-4489-8e83-274000441667", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "095813b6-6138-488b-9455-c0d4d159a08f", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "608d6a77-5aa9-4761-a401-bb4de6928cc1", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "2f4d4e5b-5bcb-4c2e-a480-497ec8ccfb97", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "b03f4885-8ddd-4de4-94d4-30a74705c23d", + "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" : "82120918-242d-483c-b750-bd0ea337d313", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "100e9779-211d-4779-adce-4a3fd4769d2a", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "52ed7356-9c27-43ed-8328-8679bd0c6632", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "1a7f7752-3ff8-40f6-932e-02c4481095e1", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "4c3747d6-0dc8-412b-bedd-137a20209df6", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "c4082666-e349-40d8-aff7-1ad3924fccb3", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "bf3d0d89-ace0-465e-a7d1-a8a1ddfb2c54", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "d2d7bb96-e6ff-49a3-ac40-db1ea327fb68", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "8347cc62-074b-4bcd-91e1-bba5d0e5862b", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "1b0903e5-4c38-4c53-96bc-b39e408abf4a", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "66d6188c-66e7-4852-809b-cfbd0efb4064", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "53f879b3-a9fb-4099-b440-da0942c5dbd4", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "885cce92-9496-4301-8fe6-f468f9035f61", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "3792a2e2-0732-4803-8ec7-46ba39602fb5", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "5699fae9-ea80-499f-831c-bb7759613b0e", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper" ] + } + }, { + "id" : "e2eae8a6-a3a2-4495-ae90-a10f5bfcc8d1", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "45ccab5e-7426-47b4-b87a-e3a4a117c9be", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAv0yxmuv/pThrFziTK55h/NW0uU340jS8hZSu1DtsPfm5Uh6aR6+d1AGWid3koP2PbR323VbWp0Q0e6n3IHzMXZE18p6YIQRo4WZa/0wV0q5VPrVMvP02hLheSFPwv7Xgt3dmioESiRUQREZqDECoyKorwo2Q6VUBOy6tbuaKY+4SIRswkyG+muOypQSWWvkk1enRTOIE/mt4/k9/nIKSir2NZM+E1Px/1jo47cskEj1U6XvG1JUDMIpbAYD5hygQWqKSZ6ferorfVesx21BjkT2NEPHRfKauBcp837uQujBIyG7bZ8WnQ/E7mgrfvWcTISt39ui41hbakkjGZqa+6QIDAQABAoIBAAL6iY1scaqBkEt/cwSmCMema9myzTNPQf8gqIuCWnM09QxlSt0lQECDG//1vVuEvQdvSqTWvQoszOHcD46R2ir2E/YlyvEpZZkJPlTwmBMfdW5XK3BEFRQ8glHcWo59RDrxRru9jAjK8RQsKrDrbBhI4DlgSxv7WdwxaceN1Q1EWRbhIyTuEUL/wbiMdk0inS7cYxMGokcz18bXNv66i1xl13Z+jPDH2KN/sBVRVhk+g7nsjqXP/O7452NjJPoD3+ci0j4wKX85V6kp/FwPmS/XobVDfAw5bN5noEOvVShc9kAydLEwe4K5/1ULdlQnja5CM0adCHYEGMQ+VtFXLx0CgYEA6hb+N3d0rJUFbyIKOc/Zd2Q37aqVrO/A7QpFnnmAw0+aZ4GBob4mjW0w24WWRLXpla0r+fO8xzRVPnNOtgYbiyuaK43fbLGaym9JhOSVZ4jZoAE99q/lxOcf7wqFgcuaFQN5gx+A4ox1f2UBwsOP82fHIbh1wQjcagS2tRhpzscCgYEA0TRoUaXZ/wgNkNELHbjZQ2PYZwIigm08cYJOVuBv6xeIE+aVwSOL8xOLmiOAB1TIukSQm+lrgyaNyHEB7+zBJLXut9VTTNqWGCYwoW9XH0twyZniATuiAfPq0FOFn4gK7h/139cg8lheL7XOOZpJ+Muk28aMMiCyLmu479aHFM8CgYBymKz81FLexdkyLXTaFmF8bChhBfhd/8Tvhe7NBOg/NPH+p8yhNKhE5eZcIQqOOovORUdZBrQsxPbSKrqmq1jCiabgmYR9/ngrj81q7Egjtj/rb5A4+qU9/nOInxr6joSwstHCmiKLRX2jzvvMr9YBr5Uxcc7boA3fLM2Q3BG4MwKBgQCTjFDe9QhRNM7MioM6jTq+qtbfn8PTFVQCApNZ9qU4u7n/vytzsqxr3ichqVQM16FSrjUHoZNwRmrNKdbh7qsyRxZ8lXpQxAIEOjsmQK7DpaOArIWXtQVIN04vbBjcMUj0NZpmKwZTpjuXjrRKMCVn0ckOMHSEjkbdMqCWl0liRQKBgD7JH+iHLt1NYBNbUeeYYRkyXWR6N3airZemWHFhfGRZLxzyvtQbggtnkHdYI+LscOZmqrgbJEwmDcmEzJuW5UpAi7TqllNWXVOaF7Rcju3Pej8atGKAITaRePuPrkNihWX/XwXoEppdl7yeF4kbcuevVrqgwC4PBk6vTzyherEy" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICnzCCAYcCBgGEQpPU9jANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAh0ZXN0MTIzNDAeFw0yMjExMDQxMjE2MzVaFw0zMjExMDQxMjE4MTVaMBMxETAPBgNVBAMMCHRlc3QxMjM0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv0yxmuv/pThrFziTK55h/NW0uU340jS8hZSu1DtsPfm5Uh6aR6+d1AGWid3koP2PbR323VbWp0Q0e6n3IHzMXZE18p6YIQRo4WZa/0wV0q5VPrVMvP02hLheSFPwv7Xgt3dmioESiRUQREZqDECoyKorwo2Q6VUBOy6tbuaKY+4SIRswkyG+muOypQSWWvkk1enRTOIE/mt4/k9/nIKSir2NZM+E1Px/1jo47cskEj1U6XvG1JUDMIpbAYD5hygQWqKSZ6ferorfVesx21BjkT2NEPHRfKauBcp837uQujBIyG7bZ8WnQ/E7mgrfvWcTISt39ui41hbakkjGZqa+6QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQC68A25tdvt1WIQBvzQkbcEnrDbOC08qsRgCc0cqwOACTaSYwo7wKzUYkTpJ+iZ4HxSiaw76DsZkca12erSyVrzSA8+jwf6by1iwrpFEqkuwXnUfZz7wiFoDCL3Lj+3K42TkAJH691NsxzawGntbJsrHg9zFF/x9mH5SSjA8ViCWwlujdZSxt7jNSVv6GvfQKD9x426g80wa1k4ZbK/fh9CaiT0glFoClq9nzfeqt1h/yx+Gqe8aSr7gOUzRYtWTV6KAcJKkmvEVxcQ3YLyCRrh0zC4uyTkJb2GiiEf8vT/yjdZ9PM+kpJ/j1Q4V4oJcoB/FIL2x4Hz/sNhPk87pKIf" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "2a2d7de1-1537-48c9-954f-40f6588d9592", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "033c6644-3b37-416c-a349-551f995183ac" ], + "secret" : [ "DAT7vn_6btDQo94-jnWgTQ" ], + "priority" : [ "100" ] + } + }, { + "id" : "bf3b7c3e-dd97-4b7b-bfd7-f6f992dfff19", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "f15715bf-4c67-46a5-9cf2-90e02213b52a" ], + "secret" : [ "v0p3iMbV2WS2NG44__Ry9hssnUeARus-wsLYFQAZmucrr_iiDYyquIPQp36nh5XSm4rSV4gEjjcAUCRodJr30Q" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "20a008d8-9891-4d55-9b43-d10d58b3554b", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAwDlNah5JjBYo6NFV/nQat1+ZJgWYwde6cdTQhkL9KWwWzxynXcbE9eB4/ILVXkiDx5T+i9anelC3qmvaGAFDkZYfNGFhGx8YLnohpnq9qs78gqX/r7WYhYTnssU05/96tWij4ZALKtE0TphO2D1MrJ3Ug7yv1eefI5MaZSQv4WXkKf+wSWHy2fiKwsHcXx7VX1XzeoILeaVHBdPvWcDxYYv28hXdKa11VfEqvaqzubNTZXcdvvGB2tdsFYtEOKW3ogM50rmvNZP3Id+hREOUJMWoG+0Y+uhWlVbV3YL7h6e2ewX5XAXnTmXVBFEHHPzHrVegPOV1jUH1RU7k/fYT+wIDAQABAoIBADeGNdTh08fJvN5nXWonOnJehFvlwPa5LEtmOCeYIQ1+geYodjXRzSHiyb/SwPQ/iQU1BgP5qJ3dw/fPuIs3jj92+COGv2h4pmECeUbf7Hpy2KCad41XLKvgtJbAfSRIeTb+d1wX4gnnrS7IsvmmV0gDPRgigD4O7jp16ultNwFpj4NEq5CP65KqsU52ZY5b9P/8muEJTn7i5JtgZ/MKwNPjyMQm1X7JbzYaZrW7cqJZmtWGEVBh3432VWfMLQh9LGpLLSQZ7wCKoBFNkZGycY6Filed1XoUVL3vsfxsiX/FD8hjrdvQ4wBMwyCsL7J45p9u+rkKh08bawATa0mCrRUCgYEA3lXMAMOxefidNELeVNku2EZ4z35rvP9kCPoOFGkJd46tzOoEUaTr3+N0p3cn/JvrJ9BRe5dlb6WJ5aUrMcKwr9tA6cUbyhWObgOh5t0P6JTUFKBEr2VVGgVKkfzJSKmxmYSlmQxshYJCM1vTiBLWtBCkM6E1YBH1F8oj+UUO9EcCgYEA3VRTEztmbl1oZtAqjX85GMJt37fK71G9si3dU7dcvnqS28Dtu9QSBrkvEzNVKFkxUSeqN3Gogtu4YU8zr0Xi5aopwI4nP44CrIsBiBR0GQhb9u4fR5hSrDaEzEw6NBcRnlbMC0+tTdkm0uyDoXllRpi8ud9VgEkgVPw705rNAK0CgYEAv4EFC2+Dsbxro7UpDcpX6B+jNY39vLf58SV49SmX5uv1VbgH6k70gE7Jjuk6fwHJpexwEtVPoL/kK/J9Zwn76C0hF3oYupKgsK/eRx0H89wlZHK7Vpwglh4SofYrZbQMbl/rxw0FeGW/Ib+pTt5zInvnAzWK5Onywv/wxmcuP0cCgYAfgku5ZzFz8NRWHriQIFaOkc92lAHOBEMPRLxHmMkZTPXvVw4BOXW/g0mSYDJ1Zg1BUnZHImUtC/65y7696yMcMxdylEo+IAd9bOSw7MiCb9A33TqSxLqwTaqmMwvuMoKNmim+Sn+Pn6SJTqaGrHOgk0n65DKV6tMxhh82Rsl8mQKBgQCCxe+90nBa851q5yppyf4UO7zoFYPe7vB9gIRDZaQXHK92qGrQyKmHo+GUWq5QKT9pPUV4bGzH05R2ceVMsbbMtlrrxA4XY8s8GdANa7JQn6v8ugT5t6V5oDaTx+bi/qCvsLVn15b1G3inwzGCBruHjuKx0sVB5p4BnczWZMjS6w==" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICnzCCAYcCBgGEQpPTYTANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAh0ZXN0MTIzNDAeFw0yMjExMDQxMjE2MzVaFw0zMjExMDQxMjE4MTVaMBMxETAPBgNVBAMMCHRlc3QxMjM0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwDlNah5JjBYo6NFV/nQat1+ZJgWYwde6cdTQhkL9KWwWzxynXcbE9eB4/ILVXkiDx5T+i9anelC3qmvaGAFDkZYfNGFhGx8YLnohpnq9qs78gqX/r7WYhYTnssU05/96tWij4ZALKtE0TphO2D1MrJ3Ug7yv1eefI5MaZSQv4WXkKf+wSWHy2fiKwsHcXx7VX1XzeoILeaVHBdPvWcDxYYv28hXdKa11VfEqvaqzubNTZXcdvvGB2tdsFYtEOKW3ogM50rmvNZP3Id+hREOUJMWoG+0Y+uhWlVbV3YL7h6e2ewX5XAXnTmXVBFEHHPzHrVegPOV1jUH1RU7k/fYT+wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAlKDZaXC//CJVauzzTQ0wzFMXTsZsMwVvRdx9iesWgF95erGup6H2aL96THDn9UzV5/AUuYxuaZOvZASyedHk43bLJVsBoUcNkTGoWvHeKC+xtSq3vwbfJQHOrTn5kFm67katjGwLkQIAB/suf+4kVrz7KIHUU+Dh7P5oY8XXa587MNF/FIapUSoxfFzTlJGjEqP4p5qy1qGcDSMxEr8amDPP8jNRsy9J166QTvDunyyCSjFw5rZnViDIFQoRe1MuE8gIaXbLWfBrveB0CVs1CQ3djVYjODacfWRLzq8XCQhW9i8TFFjZv8pExDd3MTYatw5+fs0HwUyCCSSn6XRP0" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "d4f96a7b-4b90-4dd7-b90e-4d075336ff9d", + "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" : "5fac7aeb-2249-45c2-8a11-6925287c2228", + "alias" : "Authentication Options", + "description" : "Authentication options.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "basic-auth", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "basic-auth-otp", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "a85e7e37-1370-4266-a3f1-c16d77fd7b1a", + "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" : "ff5d37dc-6c5e-4e06-abb0-640913ea53bf", + "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" : "fdd2bf2c-9856-4671-8181-866e74fb9484", + "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" : "3d326d31-096d-499a-bf5f-3d08d90220bd", + "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" : "c915c1bf-3bc1-42de-813a-cc9dda7cc755", + "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" : "6428c4e7-249c-453e-a7f4-d4de9a7b611c", + "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" : "251a6c61-d8de-4d4b-ae36-a92a4ed082eb", + "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" : "cde9f495-0cac-46cc-a031-0548bbdf9bd0", + "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" : "16477e59-6d39-4699-8a30-096158b1d696", + "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" : "a72ed7b8-7a28-431c-ab05-b28b1473484f", + "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" : "7810a4a7-876e-4ebb-add8-29ca3c609fbf", + "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" : "166d46c2-0026-4cb2-af2b-17aee2d8ce31", + "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" : "e9f6c960-53a1-4258-bf0b-a4d79e039fbf", + "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" : "5b4516ff-7e2a-46ee-9aaa-2b2bbbd3ea94", + "alias" : "http challenge", + "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "no-cookie-redirect", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Authentication Options", + "userSetupAllowed" : false + } ] + }, { + "id" : "3e55346b-262a-44fe-88d6-1825547f1469", + "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" : "3d0aa61c-ec3e-4dde-b681-20b3e19954ae", + "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-profile-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 40, + "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 + } ] + }, { + "id" : "0a7ab5b4-bf7a-47e2-83f3-9199ba0b600e", + "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" : "fb2dbb96-51d3-43f6-b819-11d63cf56463", + "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" : "d12c5e65-be6b-4264-9b33-b342387e90c5", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "41df620e-e476-457e-89b6-9870412d08dc", + "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" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DeviceCodeLifespan" : "600", + "oauth2DevicePollingInterval" : "5", + "parRequestUriLifespan" : "60", + "cibaInterval" : "5" + }, + "keycloakVersion" : "19.0.3", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} From 9a75ccbfca421a6d3bec1cbe9a8f887c93d71790 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 8 Nov 2022 18:06:45 +0100 Subject: [PATCH 03/49] Use Javers to build diffs --- pom.xml | 11 + .../keycloak/config/KeycloakConfigRunner.java | 5 +- .../service/export/RealmExportService.java | 371 ++++---- .../reference-realms/19.0.3/client.json | 34 + .../reference-realms/19.0.3/realm.json | 816 +++++++++--------- 5 files changed, 602 insertions(+), 635 deletions(-) create mode 100644 src/main/resources/reference-realms/19.0.3/client.json diff --git a/pom.xml b/pom.xml index 58af13362..e129d6246 100644 --- a/pom.xml +++ b/pom.xml @@ -153,6 +153,12 @@ failsafe ${failsafe.version} + + + org.javers + javers-core + 6.8.0 + @@ -235,6 +241,11 @@ jackson-dataformat-yaml + + org.javers + javers-core + + org.yaml snakeyaml diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java index 4e0ab6180..47b88cf3b 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java @@ -34,9 +34,6 @@ import org.springframework.boot.ExitCodeGenerator; import org.springframework.stereotype.Component; -import java.beans.IntrospectionException; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; @@ -116,7 +113,7 @@ private void runImport() { } } - private void runExport() throws IOException, IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchMethodException { + private void runExport() throws Exception { logger.info("Exporting all the realms!"); realmExportService.doExports(); } diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java index 76f9862db..62d2d95e1 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -26,22 +26,25 @@ import de.adorsys.keycloak.config.KeycloakConfigRunner; import de.adorsys.keycloak.config.properties.ExportConfigProperties; import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; -import de.adorsys.keycloak.config.repository.RealmRepository; +import org.javers.core.Javers; +import org.javers.core.JaversBuilder; +import org.javers.core.diff.ListCompareAlgorithm; +import org.javers.core.diff.changetype.PropertyChange; +import org.javers.core.metamodel.clazz.EntityDefinition; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; @Service public class RealmExportService { @@ -49,183 +52,57 @@ public class RealmExportService { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final ObjectMapper YAML_MAPPER = new YAMLMapper(); - private static final Set REALM_DEFAULT_FIELD_NAMES = new HashSet<>(); - private static final Map DESCRIPTORS = new HashMap<>(); + private static final String PLACEHOLDER = "REALM_NAME_PLACEHOLDER"; + private static final Javers JAVERS; static { YAML_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); - REALM_DEFAULT_FIELD_NAMES.add("displayName"); - REALM_DEFAULT_FIELD_NAMES.add("displayNameHtml"); - REALM_DEFAULT_FIELD_NAMES.add("notBefore"); - REALM_DEFAULT_FIELD_NAMES.add("defaultSignatureAlgorithm"); - REALM_DEFAULT_FIELD_NAMES.add("revokeRefreshToken"); - REALM_DEFAULT_FIELD_NAMES.add("refreshTokenMaxReuse"); - REALM_DEFAULT_FIELD_NAMES.add("accessTokenLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("accessTokenLifespanForImplicitFlow"); - REALM_DEFAULT_FIELD_NAMES.add("ssoSessionIdleTimeout"); - REALM_DEFAULT_FIELD_NAMES.add("ssoSessionMaxLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("ssoSessionIdleTimeoutRememberMe"); - REALM_DEFAULT_FIELD_NAMES.add("ssoSessionMaxLifespanRememberMe"); - REALM_DEFAULT_FIELD_NAMES.add("offlineSessionIdleTimeout"); - REALM_DEFAULT_FIELD_NAMES.add("offlineSessionMaxLifespanEnabled"); - REALM_DEFAULT_FIELD_NAMES.add("offlineSessionMaxLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("clientSessionIdleTimeout"); - REALM_DEFAULT_FIELD_NAMES.add("clientSessionMaxLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("clientOfflineSessionIdleTimeout"); - REALM_DEFAULT_FIELD_NAMES.add("clientOfflineSessionMaxLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("accessCodeLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("accessCodeLifespanUserAction"); - REALM_DEFAULT_FIELD_NAMES.add("accessCodeLifespanLogin"); - REALM_DEFAULT_FIELD_NAMES.add("actionTokenGeneratedByAdminLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("actionTokenGeneratedByUserLifespan"); - REALM_DEFAULT_FIELD_NAMES.add("OAuth2DeviceCodeLifespan"); // Not equal to field name, derived from getter/setter for bean introspection - REALM_DEFAULT_FIELD_NAMES.add("OAuth2DevicePollingInterval"); // Not equal to field name, derived from getter/setter for bean introspection - REALM_DEFAULT_FIELD_NAMES.add("sslRequired"); - REALM_DEFAULT_FIELD_NAMES.add("registrationAllowed"); - REALM_DEFAULT_FIELD_NAMES.add("registrationEmailAsUsername"); - REALM_DEFAULT_FIELD_NAMES.add("rememberMe"); - REALM_DEFAULT_FIELD_NAMES.add("verifyEmail"); - REALM_DEFAULT_FIELD_NAMES.add("loginWithEmailAllowed"); - REALM_DEFAULT_FIELD_NAMES.add("duplicateEmailsAllowed"); - REALM_DEFAULT_FIELD_NAMES.add("resetPasswordAllowed"); - REALM_DEFAULT_FIELD_NAMES.add("editUsernameAllowed"); - REALM_DEFAULT_FIELD_NAMES.add("bruteForceProtected"); - REALM_DEFAULT_FIELD_NAMES.add("permanentLockout"); - REALM_DEFAULT_FIELD_NAMES.add("maxFailureWaitSeconds"); - REALM_DEFAULT_FIELD_NAMES.add("minimumQuickLoginWaitSeconds"); - REALM_DEFAULT_FIELD_NAMES.add("waitIncrementSeconds"); - REALM_DEFAULT_FIELD_NAMES.add("quickLoginCheckMilliSeconds"); - REALM_DEFAULT_FIELD_NAMES.add("maxDeltaTimeSeconds"); - REALM_DEFAULT_FIELD_NAMES.add("failureFactor"); - REALM_DEFAULT_FIELD_NAMES.add("privateKey"); - REALM_DEFAULT_FIELD_NAMES.add("publicKey"); - REALM_DEFAULT_FIELD_NAMES.add("certificate"); - REALM_DEFAULT_FIELD_NAMES.add("codeSecret"); - REALM_DEFAULT_FIELD_NAMES.add("passwordPolicy"); - REALM_DEFAULT_FIELD_NAMES.add("otpPolicyType"); - REALM_DEFAULT_FIELD_NAMES.add("otpPolicyAlgorithm"); - REALM_DEFAULT_FIELD_NAMES.add("otpPolicyInitialCounter"); - REALM_DEFAULT_FIELD_NAMES.add("otpPolicyDigits"); - REALM_DEFAULT_FIELD_NAMES.add("otpPolicyLookAheadWindow"); - REALM_DEFAULT_FIELD_NAMES.add("otpPolicyPeriod"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyRpEntityName"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyRpId"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyAttestationConveyancePreference"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyAuthenticatorAttachment"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyRequireResidentKey"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyUserVerificationRequirement"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyCreateTimeout"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyAvoidSameAuthenticatorRegister"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessRpEntityName"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessRpId"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessAttestationConveyancePreference"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessAuthenticatorAttachment"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessRequireResidentKey"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessUserVerificationRequirement"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessCreateTimeout"); - REALM_DEFAULT_FIELD_NAMES.add("webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister"); - REALM_DEFAULT_FIELD_NAMES.add("loginTheme"); - REALM_DEFAULT_FIELD_NAMES.add("accountTheme"); - REALM_DEFAULT_FIELD_NAMES.add("adminTheme"); - REALM_DEFAULT_FIELD_NAMES.add("emailTheme"); - REALM_DEFAULT_FIELD_NAMES.add("eventsEnabled"); - REALM_DEFAULT_FIELD_NAMES.add("eventsExpiration"); - REALM_DEFAULT_FIELD_NAMES.add("adminEventsEnabled"); - REALM_DEFAULT_FIELD_NAMES.add("adminEventsDetailsEnabled"); - REALM_DEFAULT_FIELD_NAMES.add("internationalizationEnabled"); - REALM_DEFAULT_FIELD_NAMES.add("defaultLocale"); - REALM_DEFAULT_FIELD_NAMES.add("browserFlow"); - REALM_DEFAULT_FIELD_NAMES.add("registrationFlow"); - REALM_DEFAULT_FIELD_NAMES.add("directGrantFlow"); - REALM_DEFAULT_FIELD_NAMES.add("resetCredentialsFlow"); - REALM_DEFAULT_FIELD_NAMES.add("clientAuthenticationFlow"); - REALM_DEFAULT_FIELD_NAMES.add("dockerAuthenticationFlow"); - REALM_DEFAULT_FIELD_NAMES.add("keycloakVersion"); - REALM_DEFAULT_FIELD_NAMES.add("userManagedAccessAllowed"); - - /* - * TODO fields: - * - * roles - * groups - * defaultRoles - * defaultRole - * defaultGroups - * requiredCredentials - * otpSupportedApplications - * webAuthnPolicySignatureAlgorithms - * webAuthnPolicyAcceptableAaguids - * webAuthnPolicyPasswordlessSignatureAlgorithms - * webAuthnPolicyPasswordlessAcceptableAaguids - * clientProfiles - * clientPolicies - * users - * federatedUsers - * scopeMappings - * clientScopeMappings - * clients - * clientScopes - * defaultDefaultClientScopes - * defaultOptionalClientScopes - * browserSecurityHeaders - * smtpServer - * userFederationProviders - * userFederationMappers - * eventsListeners - * enabledEventTypes - * identityProviders - * identityProviderMappers - * protocolMappers - * components - * supportedLocales - * authenticationFlows - * authenticatorConfig - * requiredActions - * attributes - */ - - try { - for (var descriptor : Introspector.getBeanInfo(RealmRepresentation.class).getPropertyDescriptors()) { - var fieldName = descriptor.getName(); - if (REALM_DEFAULT_FIELD_NAMES.contains(fieldName)) { - // Override the read method for boxed booleans if they don't use the get-Prefix - if ((descriptor.getPropertyType().equals(Boolean.class) && descriptor.getReadMethod() == null)) { - var getterName = "is" + capitalizeFieldName(fieldName); - descriptor.setReadMethod(RealmRepresentation.class.getMethod(getterName)); - } - - // For some reason, setDockerAuthenticationFlow is a "fluent-style" setter and returns itself, which disqualifies it as a setter - if (fieldName.equals("dockerAuthenticationFlow")) { - descriptor.setWriteMethod(RealmRepresentation.class.getMethod("setDockerAuthenticationFlow", String.class)); - } - - DESCRIPTORS.put(fieldName, descriptor); - } - } - } catch (IntrospectionException | NoSuchMethodException e) { - throw new RuntimeException(e); - } + var realmIgnoredProperties = new ArrayList(); + realmIgnoredProperties.add("groups"); + realmIgnoredProperties.add("roles"); + realmIgnoredProperties.add("defaultRole"); + realmIgnoredProperties.add("clientProfiles"); + realmIgnoredProperties.add("clientPolicies"); + realmIgnoredProperties.add("users"); + realmIgnoredProperties.add("federatedUsers"); + realmIgnoredProperties.add("scopeMappings"); + realmIgnoredProperties.add("clientScopeMappings"); + realmIgnoredProperties.add("clients"); + realmIgnoredProperties.add("clientScopes"); + realmIgnoredProperties.add("userFederationProviders"); + realmIgnoredProperties.add("userFederationMappers"); + realmIgnoredProperties.add("identityProviders"); + realmIgnoredProperties.add("identityProviderMappers"); + realmIgnoredProperties.add("protocolMappers"); + realmIgnoredProperties.add("components"); + realmIgnoredProperties.add("authenticationFlows"); + realmIgnoredProperties.add("authenticatorConfig"); + realmIgnoredProperties.add("requiredActions"); + realmIgnoredProperties.add("applicationScopeMappings"); + realmIgnoredProperties.add("applications"); + realmIgnoredProperties.add("oauthClients"); + realmIgnoredProperties.add("clientTemplates"); + + JAVERS = JaversBuilder.javers() + .registerEntity(new EntityDefinition(RealmRepresentation.class, "id", realmIgnoredProperties)) + .registerEntity(new EntityDefinition(ClientRepresentation.class, "clientId", List.of("id", "protocolMappers"))) + .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) + .withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE) + .build(); } - private static String capitalizeFieldName(String fieldName) { - return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); - } - - private final RealmRepository realmRepository; private final ExportConfigProperties exportConfigProperties; private final KeycloakConfigProperties keycloakConfigProperties; @Autowired - public RealmExportService(RealmRepository realmRepository, - ExportConfigProperties exportConfigProperties, + public RealmExportService(ExportConfigProperties exportConfigProperties, KeycloakConfigProperties keycloakConfigProperties) { - this.realmRepository = realmRepository; this.exportConfigProperties = exportConfigProperties; this.keycloakConfigProperties = keycloakConfigProperties; } - public void doExports() throws IOException, IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchMethodException { + public void doExports() throws Exception { var outputLocation = Paths.get(exportConfigProperties.getLocation()); if (!Files.exists(outputLocation)) { Files.createDirectories(outputLocation); @@ -241,75 +118,123 @@ public void doExports() throws IOException, IntrospectionException, InvocationTa + " Please compile keycloak-config-cli with a matching keycloak version!", keycloakConfigVersion, exportVersion); } - RealmRepresentation defaultRealm; - try (var is = getClass().getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportConfigProperties.getKeycloakVersion()))) { - if (is == null) { - logger.error("Reference realm for version {} does not exist", exportConfigProperties.getKeycloakVersion()); - return; - } - defaultRealm = OBJECT_MAPPER.readValue(is, RealmRepresentation.class); - } - var excludes = exportConfigProperties.getExcludes(); - for (var realm : realmRepository.getRealms()) { + var inputFile = Paths.get(exportConfigProperties.getLocation(), "in", "realm.json"); + try (var is = Files.newInputStream(inputFile)) { + var realm = OBJECT_MAPPER.readValue(is, RealmRepresentation.class); var realmName = realm.getRealm(); - if (excludes.contains(realmName)) { - logger.info("Skipping realm {}", realmName); - } else { - logger.info("Exporting realm {}", realmName); - var strippedRealm = new RealmRepresentation(); - strippedRealm.setRealm(realm.getRealm()); - strippedRealm.setEnabled(realm.isEnabled()); - if (!realm.getId().equals(realm.getRealm())) { - // If the realm ID diverges from the name, include it in the dump, otherwise ignore it - strippedRealm.setId(realm.getId()); + RealmRepresentation defaultRealm; + try (var defaultRealmIs = getClass().getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportConfigProperties.getKeycloakVersion()))) { + if (defaultRealmIs == null) { + logger.error("Reference realm for version {} does not exist", exportConfigProperties.getKeycloakVersion()); + return; } + /* + * Replace the placeholder with the realm name to import. This sets some internal values like role names, + * baseUrls and redirectUrls so that they don't get picked up as "changes" + */ + var realmString = new String(defaultRealmIs.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, realmName); + defaultRealm = OBJECT_MAPPER.readValue(realmString, RealmRepresentation.class); + } - for (var fieldName : REALM_DEFAULT_FIELD_NAMES) { - setNonDefaultValue(strippedRealm, defaultRealm, realm, fieldName); + /* + * Trick javers into thinking this is the "same" object, by setting the ID on the reference realm + * to the ID of the current realm. That way we only get actual changes, not a full list of changes + * including the "object removed" and "object added" changes + */ + logger.info("Exporting realm {}", realmName); + defaultRealm.setId(realm.getId()); + var strippedRealm = new RealmRepresentation(); + handleBaseRealm(realm, defaultRealm, strippedRealm); + + // Realm is complete, now do clients + var allClientsDiff = JAVERS.compareCollections(defaultRealm.getClients(),realm.getClients(), ClientRepresentation.class); + + if (allClientsDiff.hasChanges()) { + ClientRepresentation defaultEmptyClient; + try (var clientIs = getClass().getResourceAsStream(String.format("/reference-realms/%s/client.json", exportConfigProperties.getKeycloakVersion()))) { + defaultEmptyClient = OBJECT_MAPPER.readValue(clientIs, ClientRepresentation.class); } - - var outputFile = Paths.get(exportConfigProperties.getLocation(), String.format("%s.yaml", realmName)); - try (var os = new FileOutputStream(outputFile.toFile())) { - YAML_MAPPER.writeValue(os, strippedRealm); + // Clients aren't all default. Enumerate the "default" clients first and check if they changed at all + var defaultReferenceClients = defaultRealm.getClients(); + + for (var defaultReferenceClient : defaultReferenceClients) { + var defaultRealmClient = realm.getClients().stream().filter(c -> c.getClientId().equals(defaultReferenceClient.getClientId())).findAny(); + if (defaultRealmClient.isPresent()) { + var client = defaultRealmClient.get(); + // First, compare it to the actual default client: + var clientDiff = JAVERS.compare(defaultReferenceClient, client); + if (clientDiff.hasChanges()) { + var strippedClient = createMinimizedClient(strippedRealm, defaultEmptyClient, client); + // Add protocol mappers here + // Maybe add authorizationSettings here + } + } + } + // Now that we're done with the default realm clients, handle clients that are *not* default + var defaultClientIds = defaultReferenceClients.stream().map(ClientRepresentation::getClientId).collect(Collectors.toList()); + var nonDefaultClients = realm.getClients().stream() + .filter(c -> !defaultClientIds.contains(c.getClientId())).collect(Collectors.toList()); + for (var client : nonDefaultClients) { + var strippedClient = createMinimizedClient(strippedRealm, defaultEmptyClient, client); + // Add protocol mappers here + // Maybe add authorizationSettings here } } + var outputFile = Paths.get(exportConfigProperties.getLocation(), "out", String.format("%s.yaml", realmName)); + try (var os = new FileOutputStream(outputFile.toFile())) { + YAML_MAPPER.writeValue(os, strippedRealm); + } } } - private void setNonDefaultValue(RealmRepresentation strippedRealm, - RealmRepresentation defaultRealm, - RealmRepresentation exportedRealm, - String fieldName) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { - var propertyDescriptor = DESCRIPTORS.get(fieldName); - if (propertyDescriptor == null) { - logger.error("Can't set field '{}', no such property on RealmRepresentation", fieldName); - return; - } - - var getter = propertyDescriptor.getReadMethod(); - var setter = propertyDescriptor.getWriteMethod(); - + private ClientRepresentation createMinimizedClient(RealmRepresentation strippedRealm, ClientRepresentation defaultEmptyClient, ClientRepresentation client) throws NoSuchFieldException, IllegalAccessException { + // Trick javers again + defaultEmptyClient.setClientId(client.getClientId()); /* - * These methods need special treatment because while the getter returns boxed types, the setter accepts unboxed types. - * This causes a mismatch and the proper getter can't be found. + * We have changes compared to the keycloak default client. + * Now compare to a 'naked' client to generate a minimal client representation to put into yaml */ - if (fieldName.equals("eventsEnabled")) { - getter = RealmRepresentation.class.getMethod("isEventsEnabled"); + var strippedClient = new ClientRepresentation(); + var minimalDiff = JAVERS.compare(defaultEmptyClient, client); + for (var change : minimalDiff.getChangesByType(PropertyChange.class)) { + applyClientChange(strippedClient, client, (PropertyChange) change); } - if (fieldName.equals("eventsExpiration")) { - setter = RealmRepresentation.class.getMethod("setEventsExpiration", long.class); + strippedClient.setClientId(client.getClientId()); + strippedClient.setEnabled(client.isEnabled()); + if (strippedRealm.getClients() == null) { + strippedRealm.setClients(new ArrayList<>()); } - Object defaultValue = getter.invoke(defaultRealm); - Object exportedValue = getter.invoke(exportedRealm); + strippedRealm.getClients().add(strippedClient); + return strippedClient; + } - if (!Objects.equals(defaultValue, exportedValue)) { - /* - * If the special setters are called with null values, we get an NPE because these only accept primitives - * Therefore, do nothing, the underlying value will still be null because it's not a primitive - */ - if (!((fieldName.equals("eventsEnabled") || fieldName.equals("eventsExpiration")) && exportedValue == null)) { - setter.invoke(strippedRealm, exportedValue); - } + private void handleBaseRealm(RealmRepresentation realm, RealmRepresentation defaultRealm, RealmRepresentation strippedRealm) throws NoSuchFieldException, IllegalAccessException { + var diff = JAVERS.compare(defaultRealm, realm); + for (var change : diff.getChangesByType(PropertyChange.class)) { + applyRealmChange(strippedRealm, change); + } + + // Now that Javers is done, clean up a bit afterwards. We always need to set the realm and enabled fields + strippedRealm.setRealm(realm.getRealm()); + strippedRealm.setEnabled(realm.isEnabled()); + + // If the realm ID diverges from the name, include it in the dump, otherwise remove it + if (Objects.equals(realm.getRealm(), realm.getId())) { + strippedRealm.setId(null); + } else { + strippedRealm.setId(realm.getId()); } } + + private void applyRealmChange(RealmRepresentation strippedRealm, PropertyChange change) throws NoSuchFieldException, IllegalAccessException { + var field = RealmRepresentation.class.getDeclaredField(change.getPropertyName()); + field.setAccessible(true); + field.set(strippedRealm, change.getRight()); + } + + private void applyClientChange(ClientRepresentation strippedClient, ClientRepresentation client, PropertyChange change) throws NoSuchFieldException, IllegalAccessException { + var field = ClientRepresentation.class.getDeclaredField(change.getPropertyName()); + field.setAccessible(true); + field.set(strippedClient, field.get(client)); + } } diff --git a/src/main/resources/reference-realms/19.0.3/client.json b/src/main/resources/reference-realms/19.0.3/client.json new file mode 100644 index 000000000..995372ec9 --- /dev/null +++ b/src/main/resources/reference-realms/19.0.3/client.json @@ -0,0 +1,34 @@ +{ + "id" : "caa6606a-6056-475c-af05-8b0365bb8164", + "clientId" : "reference-client", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "q0t682YLCCk2qd5dntjtcniGozLXIZ7h", + "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" : "1667920370" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ], + "access" : { + "view" : true, + "configure" : true, + "manage" : true + } +} diff --git a/src/main/resources/reference-realms/19.0.3/realm.json b/src/main/resources/reference-realms/19.0.3/realm.json index 4c0835dbf..f121b05f9 100644 --- a/src/main/resources/reference-realms/19.0.3/realm.json +++ b/src/main/resources/reference-realms/19.0.3/realm.json @@ -1,7 +1,7 @@ { - "id" : "test1234", - "realm" : "test1234", - "notBefore" : 1212340, + "id" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "realm" : "REALM_NAME_PLACEHOLDER", + "notBefore" : 0, "defaultSignatureAlgorithm" : "RS256", "revokeRefreshToken" : false, "refreshTokenMaxReuse" : 0, @@ -45,232 +45,219 @@ "failureFactor" : 30, "roles" : { "realm" : [ { - "id" : "40ea56a2-b8f4-4ace-b858-53f62283ded7", + "id" : "0825e37b-b5bb-413d-9c1c-23457f17cb66", + "name" : "default-roles-REALM_NAME_PLACEHOLDER", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "attributes" : { } + }, { + "id" : "08b30ae4-52ba-45a9-b723-07e6ac57ae2f", "name" : "offline_access", "description" : "${role_offline-access}", "composite" : false, "clientRole" : false, - "containerId" : "test1234", + "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", "attributes" : { } }, { - "id" : "fc2832ce-72fc-4df6-b7a5-b312b50584a5", + "id" : "372ddfc2-cd6e-4ca3-a30f-d39cb92ac12d", "name" : "uma_authorization", "description" : "${role_uma_authorization}", "composite" : false, "clientRole" : false, - "containerId" : "test1234", - "attributes" : { } - }, { - "id" : "d5107cde-a901-42af-a319-8bc3e61c92fc", - "name" : "default-roles-test1234", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "manage-account", "view-profile" ] - } - }, - "clientRole" : false, - "containerId" : "test1234", + "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", "attributes" : { } } ], "client" : { "realm-management" : [ { - "id" : "38371fd2-caa1-4b7a-9817-5873b5b9c0a1", - "name" : "manage-clients", - "description" : "${role_manage-clients}", + "id" : "caf6ae68-6a16-4dfa-9a71-1149ceb8f79c", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "54679367-c3b8-4247-8999-d4a71f582b8f", - "name" : "view-authorization", - "description" : "${role_view-authorization}", + "id" : "913aeddb-8be9-4060-86c7-30cf0fa892ee", + "name" : "manage-users", + "description" : "${role_manage-users}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "7ce64142-6c45-4a3b-a994-6eb0db13c6b3", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, + "id" : "15ee649c-ef5f-4fe8-a6c1-67a9db14b0c5", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "c1e7097a-9017-462e-981f-fe5a06e1e25e", - "name" : "manage-events", - "description" : "${role_manage-events}", + "id" : "5d56f881-d3ff-49ad-929c-214afd641f8f", + "name" : "query-clients", + "description" : "${role_query-clients}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "c8eec92c-fa40-41aa-9ce5-54dbe05c0a33", - "name" : "impersonation", - "description" : "${role_impersonation}", + "id" : "0ba58546-8487-4508-b81f-0735432accf2", + "name" : "view-events", + "description" : "${role_view-events}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "91d59567-8432-4852-8fe8-2c7983c8adf2", + "id" : "f6f1b3c0-5d29-41b4-b2ec-cfab60e28a3b", "name" : "query-users", "description" : "${role_query-users}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "340e8cd3-2c4a-40fb-b46d-643fd2a287c1", - "name" : "view-events", - "description" : "${role_view-events}", + "id" : "2c6ddc11-206e-4e82-b894-9fca7cc85866", + "name" : "view-realm", + "description" : "${role_view-realm}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "637c825a-553d-4632-983a-05f67d92fe32", - "name" : "manage-realm", - "description" : "${role_manage-realm}", + "id" : "f9917916-7aca-413b-b7bc-452d3fca7a48", + "name" : "manage-events", + "description" : "${role_manage-events}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "6be5c796-f77b-4a43-b16d-4fd3f473da7f", - "name" : "view-clients", - "description" : "${role_view-clients}", + "id" : "ad1d551b-3f03-445d-8ac0-51b1196413f0", + "name" : "view-users", + "description" : "${role_view-users}", "composite" : true, "composites" : { "client" : { - "realm-management" : [ "query-clients" ] + "realm-management" : [ "query-groups", "query-users" ] } }, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "4c114feb-5b2a-4a45-8f6f-02b6ff7e5d4d", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", + "id" : "f645cca6-9d25-40f0-8ce9-d988a04d9bec", + "name" : "create-client", + "description" : "${role_create-client}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "ce6051c3-46a7-43dd-b8fc-bade1425629d", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", + "id" : "ce56402a-8925-4750-9269-6b035cfa334f", + "name" : "impersonation", + "description" : "${role_impersonation}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "87f079c5-bf49-4f25-95bd-d27be9a9d5de", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", + "id" : "1e70f63f-0883-4452-bb1b-f179bf3c8c30", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "a2f45bd6-92fa-4564-9738-93493fa8f81a", - "name" : "query-groups", - "description" : "${role_query-groups}", + "id" : "5cef1f90-8af8-4445-b6a3-05d6eb60c46c", + "name" : "manage-realm", + "description" : "${role_manage-realm}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "0ce58d28-5fdd-4113-be47-b8329a565c47", - "name" : "query-realms", - "description" : "${role_query-realms}", + "id" : "e87e4aa8-8c15-44ba-9622-eec4eeae1997", + "name" : "query-groups", + "description" : "${role_query-groups}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "9bccce09-8600-47bc-b066-9be7e7ca3d7b", - "name" : "create-client", - "description" : "${role_create-client}", + "id" : "baa31234-e4ed-4821-a696-6ccd686b5e1f", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "aa923952-2afd-4a5c-aaf6-c9d3c19027cb", - "name" : "view-realm", - "description" : "${role_view-realm}", + "id" : "0d7127a0-cd7b-478c-9156-629fc321bca4", + "name" : "query-realms", + "description" : "${role_query-realms}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "794cc33e-fee5-49c3-8254-c7fe4588620e", + "id" : "99d07b51-633c-4d25-9dc1-f779a219c246", "name" : "realm-admin", "description" : "${role_realm-admin}", "composite" : true, "composites" : { "client" : { - "realm-management" : [ "view-authorization", "manage-clients", "query-clients", "manage-events", "view-events", "query-users", "impersonation", "view-clients", "view-identity-providers", "manage-realm", "manage-identity-providers", "manage-authorization", "query-groups", "query-realms", "create-client", "view-realm", "view-users", "manage-users" ] + "realm-management" : [ "manage-users", "manage-identity-providers", "view-clients", "query-clients", "view-events", "query-users", "view-realm", "manage-events", "view-users", "create-client", "manage-authorization", "impersonation", "manage-realm", "view-identity-providers", "query-groups", "query-realms", "manage-clients", "view-authorization" ] } }, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "131d4c8a-6d8e-476d-96fc-8a2626ca3b0b", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-groups", "query-users" ] - } - }, + "id" : "118c289f-0b71-4edf-ac2a-7016cc0c674d", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } }, { - "id" : "405cbdd0-713b-4a30-a38c-4f10effe5148", - "name" : "manage-users", - "description" : "${role_manage-users}", + "id" : "30f43104-6914-4e4f-9f6d-219daf2f7991", + "name" : "view-authorization", + "description" : "${role_view-authorization}", "composite" : false, "clientRole" : true, - "containerId" : "e4979954-cd40-4f56-9e57-353f96458beb", + "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", "attributes" : { } } ], "security-admin-console" : [ ], "admin-cli" : [ ], "account-console" : [ ], "broker" : [ { - "id" : "2482511e-dacf-4559-a4df-10b7b925b637", + "id" : "6045a69a-aec3-4a11-b4c3-3cae53f7a914", "name" : "read-token", "description" : "${role_read-token}", "composite" : false, "clientRole" : true, - "containerId" : "fffd9aca-0bb9-463b-b2cd-ebbdf6a5b6ec", + "containerId" : "6eed48a3-64bd-48ed-ac7c-607f8e724258", "attributes" : { } } ], "account" : [ { - "id" : "188012b7-c790-4802-a398-e35c19d3d42c", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } - }, - "clientRole" : true, - "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", - "attributes" : { } - }, { - "id" : "c065ad9a-fa86-4867-a3a1-7572c78f2e42", + "id" : "4e27eb76-06ea-4547-ab5d-de8f9c745597", "name" : "manage-consent", "description" : "${role_manage-consent}", "composite" : true, @@ -280,59 +267,72 @@ } }, "clientRole" : true, - "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", "attributes" : { } }, { - "id" : "36849282-f262-441e-be52-e0aa654e5a45", + "id" : "60ae83a6-6ed1-4b4f-81cc-701b9a95ceb9", "name" : "delete-account", "description" : "${role_delete-account}", "composite" : false, "clientRole" : true, - "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", "attributes" : { } }, { - "id" : "9afe553c-5249-4147-b36a-44929fb2749a", - "name" : "view-applications", - "description" : "${role_view-applications}", + "id" : "f5f244ba-53de-45d7-9774-d4c915cd4ccb", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes" : { } + }, { + "id" : "2abb2ef2-3e4c-43d5-9ab7-b096e19f3a56", + "name" : "view-consent", + "description" : "${role_view-consent}", "composite" : false, "clientRole" : true, - "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", "attributes" : { } }, { - "id" : "432459e7-14ac-4c1f-a4c2-67282b1ba469", + "id" : "8eb01f07-3771-4b28-ab78-6f216079b508", "name" : "view-profile", "description" : "${role_view-profile}", "composite" : false, "clientRole" : true, - "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", "attributes" : { } }, { - "id" : "a24e3e85-f8c8-40e0-ae3c-00c93ee7ad60", - "name" : "view-consent", - "description" : "${role_view-consent}", + "id" : "23954dae-8150-4183-8733-bce1349fb0ec", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", "composite" : false, "clientRole" : true, - "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", "attributes" : { } }, { - "id" : "3dcadf7f-b3bb-42f4-ad13-966f333a6274", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", + "id" : "734d080d-1f67-48e9-942f-9233f6eca901", + "name" : "view-applications", + "description" : "${role_view-applications}", "composite" : false, "clientRole" : true, - "containerId" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", "attributes" : { } } ] } }, "groups" : [ ], "defaultRole" : { - "id" : "d5107cde-a901-42af-a319-8bc3e61c92fc", - "name" : "default-roles-test1234", + "id" : "0825e37b-b5bb-413d-9c1c-23457f17cb66", + "name" : "default-roles-REALM_NAME_PLACEHOLDER", "description" : "${role_default-roles}", "composite" : true, "clientRole" : false, - "containerId" : "test1234" + "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8" }, "requiredCredentials" : [ "password" ], "otpPolicyType" : "totp", @@ -373,16 +373,16 @@ } ] }, "clients" : [ { - "id" : "7ae80015-8248-48e9-969f-24feb8ce78ef", + "id" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", "clientId" : "account", "name" : "${client_account}", "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/test1234/account/", + "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", "surrogateAuthRequired" : false, "enabled" : true, "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/test1234/account/*" ], + "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/account/*" ], "webOrigins" : [ ], "notBefore" : 0, "bearerOnly" : false, @@ -403,16 +403,16 @@ "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { - "id" : "db19ba85-ec7e-44ea-a3e5-bb306052317a", + "id" : "fe6ddaaf-3a90-4942-ae6a-1f170c87fc3b", "clientId" : "account-console", "name" : "${client_account-console}", "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/test1234/account/", + "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", "surrogateAuthRequired" : false, "enabled" : true, "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/test1234/account/*" ], + "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/account/*" ], "webOrigins" : [ ], "notBefore" : 0, "bearerOnly" : false, @@ -432,7 +432,7 @@ "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, "protocolMappers" : [ { - "id" : "ac105987-e3f5-47d1-addd-d3068502f643", + "id" : "9f385a11-7b42-4f92-ac9b-7d286590a392", "name" : "audience resolve", "protocol" : "openid-connect", "protocolMapper" : "oidc-audience-resolve-mapper", @@ -442,7 +442,7 @@ "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { - "id" : "e0ad29c0-9e11-4520-a2bb-06db3d6d6648", + "id" : "61ecc83e-d3f3-4b4f-a821-845094b3d9d4", "clientId" : "admin-cli", "name" : "${client_admin-cli}", "surrogateAuthRequired" : false, @@ -468,7 +468,7 @@ "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { - "id" : "fffd9aca-0bb9-463b-b2cd-ebbdf6a5b6ec", + "id" : "6eed48a3-64bd-48ed-ac7c-607f8e724258", "clientId" : "broker", "name" : "${client_broker}", "surrogateAuthRequired" : false, @@ -494,7 +494,7 @@ "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { - "id" : "e4979954-cd40-4f56-9e57-353f96458beb", + "id" : "631f9fd2-7539-4190-8983-0e94614c5b73", "clientId" : "realm-management", "name" : "${client_realm-management}", "surrogateAuthRequired" : false, @@ -520,16 +520,16 @@ "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] }, { - "id" : "9ee26d74-eb01-4852-826d-e174a8bd0a20", + "id" : "05f3367b-492b-40e1-ba0c-f000aa3ad0ef", "clientId" : "security-admin-console", "name" : "${client_security-admin-console}", "rootUrl" : "${authAdminUrl}", - "baseUrl" : "/admin/test1234/console/", + "baseUrl" : "/admin/REALM_NAME_PLACEHOLDER/console/", "surrogateAuthRequired" : false, "enabled" : true, "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/admin/test1234/console/*" ], + "redirectUris" : [ "/admin/REALM_NAME_PLACEHOLDER/console/*" ], "webOrigins" : [ "+" ], "notBefore" : 0, "bearerOnly" : false, @@ -549,7 +549,7 @@ "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, "protocolMappers" : [ { - "id" : "97da4dab-74f1-4a22-92ce-8bf514723dca", + "id" : "9d1f5814-fa63-4c36-ae10-747d30f47c69", "name" : "locale", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", @@ -567,7 +567,7 @@ "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] } ], "clientScopes" : [ { - "id" : "eba69b1b-2534-4abe-b593-37cda28b3519", + "id" : "23fc5623-9366-478f-9924-801d71f32489", "name" : "role_list", "description" : "SAML role list", "protocol" : "saml", @@ -576,7 +576,7 @@ "display.on.consent.screen" : "true" }, "protocolMappers" : [ { - "id" : "c40831e0-048c-4dda-9757-87910638066f", + "id" : "31541a2f-1c88-43d9-96fe-9f9efbd096d4", "name" : "role list", "protocol" : "saml", "protocolMapper" : "saml-role-list-mapper", @@ -588,7 +588,45 @@ } } ] }, { - "id" : "fc89d395-906d-4563-a78c-272e380476dd", + "id" : "1859a4f3-d051-4800-963f-45b624cccd57", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "a7777bff-a046-4fe3-a5a9-a520d79865ec", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "7d011629-d7f1-45ad-a09b-b4decb3d47ed", + "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" : "22a74f8c-4493-4a1a-bf97-f51466e336b3", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "7fb8529d-cb6f-4f31-b4c3-f6be6f74b47d", "name" : "phone", "description" : "OpenID Connect built-in scope: phone", "protocol" : "openid-connect", @@ -598,7 +636,7 @@ "consent.screen.text" : "${phoneScopeConsentText}" }, "protocolMappers" : [ { - "id" : "7205654a-1627-4366-b2fa-e7aefe079bf4", + "id" : "5bd8bfba-d8a3-4792-aaec-5f0513397193", "name" : "phone number verified", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", @@ -612,7 +650,7 @@ "jsonType.label" : "boolean" } }, { - "id" : "cd06ba24-2aaf-4997-a618-05c4262d5ab6", + "id" : "89146d36-28d5-4750-933e-75014b203dcf", "name" : "phone number", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", @@ -627,84 +665,89 @@ } } ] }, { - "id" : "032183d7-3f79-47d3-b8f6-48034e14be10", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", + "id" : "01025c61-c655-463d-967d-a45e88368472", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", "protocol" : "openid-connect", "attributes" : { - "include.in.token.scope" : "true", + "include.in.token.scope" : "false", "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" + "consent.screen.text" : "${rolesScopeConsentText}" }, "protocolMappers" : [ { - "id" : "98e7ba24-a78a-458a-8687-45691d8d2349", - "name" : "email verified", + "id" : "ad80bcc7-66d4-44c0-b34e-65822a57359a", + "name" : "realm roles", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", + "user.attribute" : "foo", "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" } }, { - "id" : "d1aa999a-aaba-45ca-b305-b9d525af937f", - "name" : "email", + "id" : "73346462-e569-4679-a57d-fe623bdc5a95", + "name" : "audience resolve", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "3842db0a-837f-4564-9e48-07c87f5d3258", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", + "user.attribute" : "foo", "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" } } ] }, { - "id" : "de986b34-e6de-40c2-aaad-9def6570e7ee", - "name" : "acr", - "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "id" : "f79aab58-4d4a-40ee-b879-a586ff956f12", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", "protocol" : "openid-connect", "attributes" : { - "include.in.token.scope" : "false", + "include.in.token.scope" : "true", "display.on.consent.screen" : "false" }, "protocolMappers" : [ { - "id" : "4b82ac72-2b9b-46bf-8981-fd790c83a0a3", - "name" : "acr loa level", + "id" : "ccb6bedc-b921-4c83-abe6-13de8c0e9795", + "name" : "groups", "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", "consentRequired" : false, "config" : { + "multivalued" : "true", + "user.attribute" : "foo", "id.token.claim" : "true", - "access.token.claim" : "true" + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" } - } ] - }, { - "id" : "982f8f13-367c-423a-8f92-a4153081bafa", - "name" : "web-origins", - "description" : "OpenID Connect scope for add allowed web origins to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" - }, - "protocolMappers" : [ { - "id" : "a69f010d-3635-4067-83b9-f814c4ed5a92", - "name" : "allowed web origins", + }, { + "id" : "19ae8192-0189-4644-a362-d08a0bce5680", + "name" : "upn", "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", + "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, - "config" : { } + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } } ] }, { - "id" : "ff126c19-9c3d-488d-ae61-2e4c964da5aa", + "id" : "a0d6d0c2-afd3-4b29-a99f-ff3002866519", "name" : "offline_access", "description" : "OpenID Connect built-in scope: offline_access", "protocol" : "openid-connect", @@ -713,7 +756,7 @@ "display.on.consent.screen" : "true" } }, { - "id" : "2d41746f-6c92-43be-b149-20774a923426", + "id" : "1dcbfe97-e4ab-4df7-8517-4eaaa26ea410", "name" : "profile", "description" : "OpenID Connect built-in scope: profile", "protocol" : "openid-connect", @@ -723,49 +766,35 @@ "consent.screen.text" : "${profileScopeConsentText}" }, "protocolMappers" : [ { - "id" : "e386fc51-6c8e-4e97-b709-1d2c7b61ca09", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "7bc05bc5-1766-4227-af40-c9e147e15450", - "name" : "zoneinfo", + "id" : "8448e164-b72e-406e-b14b-ae5d1e72393a", + "name" : "gender", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", + "user.attribute" : "gender", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "zoneinfo", + "claim.name" : "gender", "jsonType.label" : "String" } }, { - "id" : "5560e6b8-4459-4825-88d6-8c9e666474a0", - "name" : "gender", + "id" : "c573ae92-f94a-494e-9403-f875d22d3e8d", + "name" : "profile", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "gender", + "user.attribute" : "profile", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "gender", + "claim.name" : "profile", "jsonType.label" : "String" } }, { - "id" : "38ed06c5-f8a8-4f10-9699-4415fdae201b", + "id" : "09f992cc-2d64-4d4d-88ca-59a1b63325e9", "name" : "website", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", @@ -779,116 +808,116 @@ "jsonType.label" : "String" } }, { - "id" : "41b0f4f7-fd2d-4cfc-bcbd-50bdf9d4d78a", - "name" : "birthdate", + "id" : "04658d90-dc3d-4865-ac52-6f16570fcd76", + "name" : "full name", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", + "protocolMapper" : "oidc-full-name-mapper", "consentRequired" : false, "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" + "userinfo.token.claim" : "true" } }, { - "id" : "b5de8af6-82a0-40b9-a6a9-bd3bd51086b4", - "name" : "username", + "id" : "de199ca1-e720-419f-aeee-e112568f5cff", + "name" : "family name", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "username", + "user.attribute" : "lastName", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "preferred_username", + "claim.name" : "family_name", "jsonType.label" : "String" } }, { - "id" : "49eed952-5ec9-4675-a88c-5cd8ef171cea", - "name" : "profile", + "id" : "08daf912-8959-4ba5-9c38-ffcd395d6487", + "name" : "given name", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", + "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "profile", + "user.attribute" : "firstName", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "profile", + "claim.name" : "given_name", "jsonType.label" : "String" } }, { - "id" : "b69119a9-768b-42ab-9156-8033b2a44d7c", - "name" : "updated at", + "id" : "d76e56a2-6237-46d5-a56b-0ecd1f979e85", + "name" : "username", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", + "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", + "user.attribute" : "username", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" + "claim.name" : "preferred_username", + "jsonType.label" : "String" } }, { - "id" : "0833a1f2-5167-41d6-bf8e-f5946e560dd3", - "name" : "full name", + "id" : "6bc40007-ab16-48f6-ae81-a66a9062ad5c", + "name" : "picture", "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", + "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", "id.token.claim" : "true", "access.token.claim" : "true", - "userinfo.token.claim" : "true" + "claim.name" : "picture", + "jsonType.label" : "String" } }, { - "id" : "00ad740c-05a4-409e-846c-34cf34dd843c", - "name" : "family name", + "id" : "0385dbe8-587c-49ab-a317-a15b5b52d456", + "name" : "zoneinfo", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", + "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "lastName", + "user.attribute" : "zoneinfo", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "family_name", + "claim.name" : "zoneinfo", "jsonType.label" : "String" } }, { - "id" : "518dcdfa-f7de-4489-8e83-274000441667", - "name" : "given name", + "id" : "28946b3e-0eab-44a5-8eee-d42088072306", + "name" : "locale", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", + "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "firstName", + "user.attribute" : "locale", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "given_name", + "claim.name" : "locale", "jsonType.label" : "String" } }, { - "id" : "095813b6-6138-488b-9455-c0d4d159a08f", - "name" : "picture", + "id" : "e61c48dc-d083-4029-92d1-3c7a6f3d8bb9", + "name" : "birthdate", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "picture", + "user.attribute" : "birthdate", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "picture", + "claim.name" : "birthdate", "jsonType.label" : "String" } }, { - "id" : "608d6a77-5aa9-4761-a401-bb4de6928cc1", + "id" : "62545d7c-ef2e-48a6-bff8-e2e9a2b16c3d", "name" : "middle name", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", @@ -902,60 +931,36 @@ "jsonType.label" : "String" } }, { - "id" : "2f4d4e5b-5bcb-4c2e-a480-497ec8ccfb97", - "name" : "locale", + "id" : "f1c75501-26b2-4d94-82aa-851e4fa3dd7c", + "name" : "nickname", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "b03f4885-8ddd-4de4-94d4-30a74705c23d", - "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" : "82120918-242d-483c-b750-bd0ea337d313", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "username", + "user.attribute" : "nickname", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "upn", + "claim.name" : "nickname", "jsonType.label" : "String" } }, { - "id" : "100e9779-211d-4779-adce-4a3fd4769d2a", - "name" : "groups", + "id" : "046a0b0d-5256-48fe-9b8f-b5193d87ebdd", + "name" : "updated at", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "protocolMapper" : "oidc-usermodel-attribute-mapper", "consentRequired" : false, "config" : { - "multivalued" : "true", - "user.attribute" : "foo", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" + "claim.name" : "updated_at", + "jsonType.label" : "long" } } ] }, { - "id" : "52ed7356-9c27-43ed-8328-8679bd0c6632", + "id" : "d807e61e-3661-44ff-a8cd-285458e6f763", "name" : "address", "description" : "OpenID Connect built-in scope: address", "protocol" : "openid-connect", @@ -965,7 +970,7 @@ "consent.screen.text" : "${addressScopeConsentText}" }, "protocolMappers" : [ { - "id" : "1a7f7752-3ff8-40f6-932e-02c4481095e1", + "id" : "fede614b-c46e-484c-8dba-9590cd0205fe", "name" : "address", "protocol" : "openid-connect", "protocolMapper" : "oidc-address-mapper", @@ -983,47 +988,42 @@ } } ] }, { - "id" : "4c3747d6-0dc8-412b-bedd-137a20209df6", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", + "id" : "c8a4b7a9-c8ba-412b-a17d-e90a3c6393fd", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", "protocol" : "openid-connect", "attributes" : { - "include.in.token.scope" : "false", + "include.in.token.scope" : "true", "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" + "consent.screen.text" : "${emailScopeConsentText}" }, "protocolMappers" : [ { - "id" : "c4082666-e349-40d8-aff7-1ad3924fccb3", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - }, { - "id" : "bf3d0d89-ace0-465e-a7d1-a8a1ddfb2c54", - "name" : "client roles", + "id" : "de4bafae-e88c-4c2f-9001-311f5c141633", + "name" : "email", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", + "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "user.attribute" : "foo", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" + "claim.name" : "email", + "jsonType.label" : "String" } }, { - "id" : "d2d7bb96-e6ff-49a3-ac40-db1ea327fb68", - "name" : "realm roles", + "id" : "9aa11aac-4178-435b-83b3-2d85508e34bd", + "name" : "email verified", "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "protocolMapper" : "oidc-usermodel-property-mapper", "consentRequired" : false, "config" : { - "user.attribute" : "foo", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" + "claim.name" : "email_verified", + "jsonType.label" : "boolean" } } ] } ], @@ -1048,24 +1048,30 @@ "identityProviderMappers" : [ ], "components" : { "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { - "id" : "8347cc62-074b-4bcd-91e1-bba5d0e5862b", - "name" : "Consent Required", - "providerId" : "consent-required", + "id" : "808ef7c4-b5d1-491c-ade0-559f38287e68", + "name" : "Full Scope Disabled", + "providerId" : "scope", "subType" : "anonymous", "subComponents" : { }, "config" : { } }, { - "id" : "1b0903e5-4c38-4c53-96bc-b39e408abf4a", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", + "id" : "2a0ad1a6-d6f9-43cb-8ec1-8913a23339d7", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", "subComponents" : { }, "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper" ] } }, { - "id" : "66d6188c-66e7-4852-809b-cfbd0efb4064", + "id" : "3695208c-32af-497b-9af9-5de878749899", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "b7317013-6157-4099-a1ee-194df4808b2d", "name" : "Allowed Client Scopes", "providerId" : "allowed-client-templates", "subType" : "authenticated", @@ -1074,7 +1080,7 @@ "allow-default-scopes" : [ "true" ] } }, { - "id" : "53f879b3-a9fb-4099-b440-da0942c5dbd4", + "id" : "2464d485-980d-447a-94e2-34cf96aad1f1", "name" : "Allowed Client Scopes", "providerId" : "allowed-client-templates", "subType" : "anonymous", @@ -1083,90 +1089,84 @@ "allow-default-scopes" : [ "true" ] } }, { - "id" : "885cce92-9496-4301-8fe6-f468f9035f61", - "name" : "Max Clients Limit", - "providerId" : "max-clients", + "id" : "6460fb5a-bbb9-400b-aba7-85cbe8666341", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", "subType" : "anonymous", "subComponents" : { }, "config" : { - "max-clients" : [ "200" ] + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] } }, { - "id" : "3792a2e2-0732-4803-8ec7-46ba39602fb5", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "5699fae9-ea80-499f-831c-bb7759613b0e", + "id" : "c2093410-4c63-4436-9294-535c492912dc", "name" : "Allowed Protocol Mapper Types", "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", + "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper" ] } }, { - "id" : "e2eae8a6-a3a2-4495-ae90-a10f5bfcc8d1", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", + "id" : "f9ef3de5-1ad1-4c9c-92bd-62a070e13ba2", + "name" : "Max Clients Limit", + "providerId" : "max-clients", "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper" ] + "max-clients" : [ "200" ] } } ], "org.keycloak.keys.KeyProvider" : [ { - "id" : "45ccab5e-7426-47b4-b87a-e3a4a117c9be", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", + "id" : "d75363d0-aed5-4fea-a97f-d0c1adb4fa63", + "name" : "hmac-generated", + "providerId" : "hmac-generated", "subComponents" : { }, "config" : { - "privateKey" : [ "MIIEowIBAAKCAQEAv0yxmuv/pThrFziTK55h/NW0uU340jS8hZSu1DtsPfm5Uh6aR6+d1AGWid3koP2PbR323VbWp0Q0e6n3IHzMXZE18p6YIQRo4WZa/0wV0q5VPrVMvP02hLheSFPwv7Xgt3dmioESiRUQREZqDECoyKorwo2Q6VUBOy6tbuaKY+4SIRswkyG+muOypQSWWvkk1enRTOIE/mt4/k9/nIKSir2NZM+E1Px/1jo47cskEj1U6XvG1JUDMIpbAYD5hygQWqKSZ6ferorfVesx21BjkT2NEPHRfKauBcp837uQujBIyG7bZ8WnQ/E7mgrfvWcTISt39ui41hbakkjGZqa+6QIDAQABAoIBAAL6iY1scaqBkEt/cwSmCMema9myzTNPQf8gqIuCWnM09QxlSt0lQECDG//1vVuEvQdvSqTWvQoszOHcD46R2ir2E/YlyvEpZZkJPlTwmBMfdW5XK3BEFRQ8glHcWo59RDrxRru9jAjK8RQsKrDrbBhI4DlgSxv7WdwxaceN1Q1EWRbhIyTuEUL/wbiMdk0inS7cYxMGokcz18bXNv66i1xl13Z+jPDH2KN/sBVRVhk+g7nsjqXP/O7452NjJPoD3+ci0j4wKX85V6kp/FwPmS/XobVDfAw5bN5noEOvVShc9kAydLEwe4K5/1ULdlQnja5CM0adCHYEGMQ+VtFXLx0CgYEA6hb+N3d0rJUFbyIKOc/Zd2Q37aqVrO/A7QpFnnmAw0+aZ4GBob4mjW0w24WWRLXpla0r+fO8xzRVPnNOtgYbiyuaK43fbLGaym9JhOSVZ4jZoAE99q/lxOcf7wqFgcuaFQN5gx+A4ox1f2UBwsOP82fHIbh1wQjcagS2tRhpzscCgYEA0TRoUaXZ/wgNkNELHbjZQ2PYZwIigm08cYJOVuBv6xeIE+aVwSOL8xOLmiOAB1TIukSQm+lrgyaNyHEB7+zBJLXut9VTTNqWGCYwoW9XH0twyZniATuiAfPq0FOFn4gK7h/139cg8lheL7XOOZpJ+Muk28aMMiCyLmu479aHFM8CgYBymKz81FLexdkyLXTaFmF8bChhBfhd/8Tvhe7NBOg/NPH+p8yhNKhE5eZcIQqOOovORUdZBrQsxPbSKrqmq1jCiabgmYR9/ngrj81q7Egjtj/rb5A4+qU9/nOInxr6joSwstHCmiKLRX2jzvvMr9YBr5Uxcc7boA3fLM2Q3BG4MwKBgQCTjFDe9QhRNM7MioM6jTq+qtbfn8PTFVQCApNZ9qU4u7n/vytzsqxr3ichqVQM16FSrjUHoZNwRmrNKdbh7qsyRxZ8lXpQxAIEOjsmQK7DpaOArIWXtQVIN04vbBjcMUj0NZpmKwZTpjuXjrRKMCVn0ckOMHSEjkbdMqCWl0liRQKBgD7JH+iHLt1NYBNbUeeYYRkyXWR6N3airZemWHFhfGRZLxzyvtQbggtnkHdYI+LscOZmqrgbJEwmDcmEzJuW5UpAi7TqllNWXVOaF7Rcju3Pej8atGKAITaRePuPrkNihWX/XwXoEppdl7yeF4kbcuevVrqgwC4PBk6vTzyherEy" ], - "keyUse" : [ "ENC" ], - "certificate" : [ "MIICnzCCAYcCBgGEQpPU9jANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAh0ZXN0MTIzNDAeFw0yMjExMDQxMjE2MzVaFw0zMjExMDQxMjE4MTVaMBMxETAPBgNVBAMMCHRlc3QxMjM0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv0yxmuv/pThrFziTK55h/NW0uU340jS8hZSu1DtsPfm5Uh6aR6+d1AGWid3koP2PbR323VbWp0Q0e6n3IHzMXZE18p6YIQRo4WZa/0wV0q5VPrVMvP02hLheSFPwv7Xgt3dmioESiRUQREZqDECoyKorwo2Q6VUBOy6tbuaKY+4SIRswkyG+muOypQSWWvkk1enRTOIE/mt4/k9/nIKSir2NZM+E1Px/1jo47cskEj1U6XvG1JUDMIpbAYD5hygQWqKSZ6ferorfVesx21BjkT2NEPHRfKauBcp837uQujBIyG7bZ8WnQ/E7mgrfvWcTISt39ui41hbakkjGZqa+6QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQC68A25tdvt1WIQBvzQkbcEnrDbOC08qsRgCc0cqwOACTaSYwo7wKzUYkTpJ+iZ4HxSiaw76DsZkca12erSyVrzSA8+jwf6by1iwrpFEqkuwXnUfZz7wiFoDCL3Lj+3K42TkAJH691NsxzawGntbJsrHg9zFF/x9mH5SSjA8ViCWwlujdZSxt7jNSVv6GvfQKD9x426g80wa1k4ZbK/fh9CaiT0glFoClq9nzfeqt1h/yx+Gqe8aSr7gOUzRYtWTV6KAcJKkmvEVxcQ3YLyCRrh0zC4uyTkJb2GiiEf8vT/yjdZ9PM+kpJ/j1Q4V4oJcoB/FIL2x4Hz/sNhPk87pKIf" ], + "kid" : [ "ea50f128-6340-4f3e-8050-1e79ba559121" ], + "secret" : [ "QYJipEnEXtsqk2OqtX1oyyQrANj03UWaGGf1p4yD28-x4MMOSxXVpEVTwZZvpgJeB-4L2lztTaJxVhANSO1lDQ" ], "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] + "algorithm" : [ "HS256" ] } }, { - "id" : "2a2d7de1-1537-48c9-954f-40f6588d9592", - "name" : "aes-generated", - "providerId" : "aes-generated", + "id" : "594b0771-b860-4aa8-8a81-4b29ee5ac384", + "name" : "rsa-generated", + "providerId" : "rsa-generated", "subComponents" : { }, "config" : { - "kid" : [ "033c6644-3b37-416c-a349-551f995183ac" ], - "secret" : [ "DAT7vn_6btDQo94-jnWgTQ" ], + "privateKey" : [ "MIIEowIBAAKCAQEAsZSMHIVGaorVvBfVYzTAHzXO7/ilxcjty/hayFAB3C1CkEnKeYIs2hUv8s3DFrG26GBFRWI+HVX8yCCuwksDkF4kB6ZxpNV/NCwt4+Hv1Fr/A58eVHkriXajlOE9LUkvrSd04bGGD+SplAgCweQzVcHF5Iy4bvsIJ8ow5WKJ7dGtJlnU2ddBEdja2KhlFWBdqn2YCdvpuLGEYNGr5/ANBM6evcv/bU3tVdq64TPSC13kw/QicKzAU6H+4k8reIAgaEhtudqb6sWI9G0JiSPtb98psTZMFjoGb9yphOFsoSmljA3Ozp2wDVltV/zyk8Aw7J2I8EOCdjcX8BdkkQc6oQIDAQABAoIBABEdhJ2RGNbW97+vumDb6jJ34LCPUgbslULF9pX85BkBAbvfaNTqP4FrblokC8wJp9vgv3xu+hagvYLaZ42RZlAJSsaz+5sL+r0gDvI6Sf+5H4ANW4J/xTr0BNMqHFfbiG1Tcrf4ALhSbSe31/AxGuOGkBi1mWcU6dXP7oOFSk7x79FVmirB0bKGGwd2TNbrmtBSiDU33vUPxwGnDSsmw8TVHyjISiM0BfvVkS9RrBGqEnNm0iKccukRgengrqCK8D4fq65YPrQQQ9o9I50eU0qHoCiVJyNC2+MBpiOniShLO8jyiLAuhDKDfoKis5C6Hqm1yyf2PzwB1rIjV5XVf2ECgYEA6JiWMHHKfyR3QNvGjRp3Jfz/WQvLGXZCwVDowhlNRtfvGC56tEC+QAWxM/4l12u1NMyYSDeF44tYpzq0dGtVkTWTzQKNczTkGhValNMgFEUa8pwumBVVfrfLllA0VGrMW6fEAr8ta2gFODOWoViLSD0s0Jmu6CpdKSUzafcnMjkCgYEAw3LQ2sLgGL8LajjPl++LgWcKRZF3M80IFF2fE78I80zVow4x6Ei+EyrvObmJ5necXKkRA7o7h/7Xb+ATy2h0ZCdr/OXpmR/yGASfUdNXDrtQ0nYF6TNz1Xqadi9cGv7YKy8SOgbB0SM5S6aRy+ouoaUwNCSsWDleAgEOQvpzq6kCgYBrPv/xMmaWHTBHXY69PPi3MWJjooZxJRA+ppnL9XKmOaZq1fOJ7VhLmNRODt9P5r/UqomEsuUvN+8WnIDcNSltHPEbVBP4jOioBjSP7pEaB4sXVmA9i4iyNvjORAj864lysXY1dgTxQzM06MSJfJQsKNjjDhmRvwbZk+eS8nzGMQKBgAFjAipbMZ3bVShmyMpKL9I2OfNuacsbTFBgra1FMLoRNH7Yre/4/ChEqLffIiRZeumJZY6CNsPrQfoQO/O4hQLk6LY9p1+nw176QWsiNb7sA1HK9pXGAK9mFEx8X4ntfvknd1ikDaH/PvvTbbtlqPkKpAHqtLJXjdwzx7cf8cwpAoGBALxwuNYA7NBV8qRzTsLfSGoZEfl6jQogD3GR+EmFswIEYzdp8Mp+VwcpBjP5D28v9kFKa8dCe7TU9aY1JZm5W+N3ZqfhLp6TNepKVpRsyKsF4Pjz9fDZu1Q9AU81ImSO4w6FkLqQc069w7M5O/tT4/WjYbfqEp16UkP77Gh1F4O6" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIIClzCCAX8CBgGEV1OLDTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARkZW1vMB4XDTIyMTEwODEyNTgyM1oXDTMyMTEwODEzMDAwM1owDzENMAsGA1UEAwwEZGVtbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGUjByFRmqK1bwX1WM0wB81zu/4pcXI7cv4WshQAdwtQpBJynmCLNoVL/LNwxaxtuhgRUViPh1V/MggrsJLA5BeJAemcaTVfzQsLePh79Ra/wOfHlR5K4l2o5ThPS1JL60ndOGxhg/kqZQIAsHkM1XBxeSMuG77CCfKMOViie3RrSZZ1NnXQRHY2tioZRVgXap9mAnb6bixhGDRq+fwDQTOnr3L/21N7VXauuEz0gtd5MP0InCswFOh/uJPK3iAIGhIbbnam+rFiPRtCYkj7W/fKbE2TBY6Bm/cqYThbKEppYwNzs6dsA1ZbVf88pPAMOydiPBDgnY3F/AXZJEHOqECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdo+c58iEquMpcBiMQ44lhSnroyrRwHazMxQ/8fHUUh2QxNwlGynGPfhMNyUtn06sPZqabV1ixTDfb6NbEpJK/HN3meWUNl4I4i5Zabew5DuUCh/BLRUbgOApsoRyHabDlR68inuXIPaP4M8lOfQsZO2/xNuGnr/eedKIR1WotXtmm2WJv79A9tJQkplizS78HoCa+HlyP1UAuAUDO0IZsJwY8CbKq1wgrhs9by8amdzZRBVILuDnuqEqeRxSY4o4BOvtM7TG5aA0iBVQc473NT1IvY10ojW6zs/ahqs0yG44+W2aBG35DvoDNVqP9Lw1vaTAD7OHdzQGhREz07cemA==" ], "priority" : [ "100" ] } }, { - "id" : "bf3b7c3e-dd97-4b7b-bfd7-f6f992dfff19", - "name" : "hmac-generated", - "providerId" : "hmac-generated", + "id" : "da244786-6bf6-40c4-b7c3-094f883d969e", + "name" : "aes-generated", + "providerId" : "aes-generated", "subComponents" : { }, "config" : { - "kid" : [ "f15715bf-4c67-46a5-9cf2-90e02213b52a" ], - "secret" : [ "v0p3iMbV2WS2NG44__Ry9hssnUeARus-wsLYFQAZmucrr_iiDYyquIPQp36nh5XSm4rSV4gEjjcAUCRodJr30Q" ], - "priority" : [ "100" ], - "algorithm" : [ "HS256" ] + "kid" : [ "6ee413ed-3b74-458f-acb7-a89d5e05f0b9" ], + "secret" : [ "sANwaoZOUoMcaPlAr5U1sQ" ], + "priority" : [ "100" ] } }, { - "id" : "20a008d8-9891-4d55-9b43-d10d58b3554b", - "name" : "rsa-generated", - "providerId" : "rsa-generated", + "id" : "1eeba879-03e8-4aad-b91a-0159cf513d73", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", "subComponents" : { }, "config" : { - "privateKey" : [ "MIIEpAIBAAKCAQEAwDlNah5JjBYo6NFV/nQat1+ZJgWYwde6cdTQhkL9KWwWzxynXcbE9eB4/ILVXkiDx5T+i9anelC3qmvaGAFDkZYfNGFhGx8YLnohpnq9qs78gqX/r7WYhYTnssU05/96tWij4ZALKtE0TphO2D1MrJ3Ug7yv1eefI5MaZSQv4WXkKf+wSWHy2fiKwsHcXx7VX1XzeoILeaVHBdPvWcDxYYv28hXdKa11VfEqvaqzubNTZXcdvvGB2tdsFYtEOKW3ogM50rmvNZP3Id+hREOUJMWoG+0Y+uhWlVbV3YL7h6e2ewX5XAXnTmXVBFEHHPzHrVegPOV1jUH1RU7k/fYT+wIDAQABAoIBADeGNdTh08fJvN5nXWonOnJehFvlwPa5LEtmOCeYIQ1+geYodjXRzSHiyb/SwPQ/iQU1BgP5qJ3dw/fPuIs3jj92+COGv2h4pmECeUbf7Hpy2KCad41XLKvgtJbAfSRIeTb+d1wX4gnnrS7IsvmmV0gDPRgigD4O7jp16ultNwFpj4NEq5CP65KqsU52ZY5b9P/8muEJTn7i5JtgZ/MKwNPjyMQm1X7JbzYaZrW7cqJZmtWGEVBh3432VWfMLQh9LGpLLSQZ7wCKoBFNkZGycY6Filed1XoUVL3vsfxsiX/FD8hjrdvQ4wBMwyCsL7J45p9u+rkKh08bawATa0mCrRUCgYEA3lXMAMOxefidNELeVNku2EZ4z35rvP9kCPoOFGkJd46tzOoEUaTr3+N0p3cn/JvrJ9BRe5dlb6WJ5aUrMcKwr9tA6cUbyhWObgOh5t0P6JTUFKBEr2VVGgVKkfzJSKmxmYSlmQxshYJCM1vTiBLWtBCkM6E1YBH1F8oj+UUO9EcCgYEA3VRTEztmbl1oZtAqjX85GMJt37fK71G9si3dU7dcvnqS28Dtu9QSBrkvEzNVKFkxUSeqN3Gogtu4YU8zr0Xi5aopwI4nP44CrIsBiBR0GQhb9u4fR5hSrDaEzEw6NBcRnlbMC0+tTdkm0uyDoXllRpi8ud9VgEkgVPw705rNAK0CgYEAv4EFC2+Dsbxro7UpDcpX6B+jNY39vLf58SV49SmX5uv1VbgH6k70gE7Jjuk6fwHJpexwEtVPoL/kK/J9Zwn76C0hF3oYupKgsK/eRx0H89wlZHK7Vpwglh4SofYrZbQMbl/rxw0FeGW/Ib+pTt5zInvnAzWK5Onywv/wxmcuP0cCgYAfgku5ZzFz8NRWHriQIFaOkc92lAHOBEMPRLxHmMkZTPXvVw4BOXW/g0mSYDJ1Zg1BUnZHImUtC/65y7696yMcMxdylEo+IAd9bOSw7MiCb9A33TqSxLqwTaqmMwvuMoKNmim+Sn+Pn6SJTqaGrHOgk0n65DKV6tMxhh82Rsl8mQKBgQCCxe+90nBa851q5yppyf4UO7zoFYPe7vB9gIRDZaQXHK92qGrQyKmHo+GUWq5QKT9pPUV4bGzH05R2ceVMsbbMtlrrxA4XY8s8GdANa7JQn6v8ugT5t6V5oDaTx+bi/qCvsLVn15b1G3inwzGCBruHjuKx0sVB5p4BnczWZMjS6w==" ], - "keyUse" : [ "SIG" ], - "certificate" : [ "MIICnzCCAYcCBgGEQpPTYTANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAh0ZXN0MTIzNDAeFw0yMjExMDQxMjE2MzVaFw0zMjExMDQxMjE4MTVaMBMxETAPBgNVBAMMCHRlc3QxMjM0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwDlNah5JjBYo6NFV/nQat1+ZJgWYwde6cdTQhkL9KWwWzxynXcbE9eB4/ILVXkiDx5T+i9anelC3qmvaGAFDkZYfNGFhGx8YLnohpnq9qs78gqX/r7WYhYTnssU05/96tWij4ZALKtE0TphO2D1MrJ3Ug7yv1eefI5MaZSQv4WXkKf+wSWHy2fiKwsHcXx7VX1XzeoILeaVHBdPvWcDxYYv28hXdKa11VfEqvaqzubNTZXcdvvGB2tdsFYtEOKW3ogM50rmvNZP3Id+hREOUJMWoG+0Y+uhWlVbV3YL7h6e2ewX5XAXnTmXVBFEHHPzHrVegPOV1jUH1RU7k/fYT+wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAlKDZaXC//CJVauzzTQ0wzFMXTsZsMwVvRdx9iesWgF95erGup6H2aL96THDn9UzV5/AUuYxuaZOvZASyedHk43bLJVsBoUcNkTGoWvHeKC+xtSq3vwbfJQHOrTn5kFm67katjGwLkQIAB/suf+4kVrz7KIHUU+Dh7P5oY8XXa587MNF/FIapUSoxfFzTlJGjEqP4p5qy1qGcDSMxEr8amDPP8jNRsy9J166QTvDunyyCSjFw5rZnViDIFQoRe1MuE8gIaXbLWfBrveB0CVs1CQ3djVYjODacfWRLzq8XCQhW9i8TFFjZv8pExDd3MTYatw5+fs0HwUyCCSSn6XRP0" ], - "priority" : [ "100" ] + "privateKey" : [ "MIIEowIBAAKCAQEA4FmIA8jfFAMVm8BuINWDWCn//Xzlk48yoUly1kP8RXriGOYfXrQQTqae0nhBBqlaJFBXAjI41UGJE9+4j23/aGp8vcTTUy9M4qBl9jlt9Pqz178/KaMDuBkeb3TV8PbR6PXrCSuWOuT3LJaXWEitpeMM/+dFSOxD4M1LWOwoQltSlmiZblcb5NsRal60sHvFUA5ecp35jW7Rj+xAQKR7QxgOr8rf83P9NPWYww4+lbKdbgnD2IMfdzZ9soSiHX4WtRrl1dPAqnihfEeaOoDXXTa1m49Q4xORnMVs6E15A+Zviu0xhR1385GT7sHLhZbSh+oLH4nx9jFinry+AgGlUwIDAQABAoIBABbvIBfe82r8y7sxxzBFE1myZXBY0bEtbMwPEZW0unex0aYg9Cj+uEIKB2dVkrQnIMdgjRx03Nl0CxrEfn3vDTJz3E+b7Mxuo+nw4qtygHqQHE1cSA0uFGW/75wOMgahfKDXbtDvqzpXCKt+s3b7awDvvnb0geEsAd5bri2napApy7qEg8iD04NhJhPj+nnoZ/jWTs3N++Dy/a0Y4/TsmIJXjtOY4JUcbYSJDBgFW0hikU/JKRMYXRZNCOzgDlUO+ejxKR2/HJjNK3ynf8Z5seROvVLI6sDTAA20b/PQLW4yEC7BtjWor8zag5tN6ZORdf4tZrsWLAiSAU2NPZepRx0CgYEA9duql4nPIs9Vf7GBZeVJd8Vl5z4WuAbaLgZ0TxaUwU83b9oaw1JmQgZ7gOCDgBkdECJk7iZAgdCCDKyFZyq+aeE8jn2uWXXrh/nMw2rXtAu3K464oyN2kWd4d7vinEsm7B5Vz1tiT20uuACS8AWi63pdBsx7yIsoq/yTantSMN0CgYEA6Zq7A++W2vwCWf0bzuwZ8ozObHafr3awgnEzlD2CXUW0mBC3ptxbo9+ug9SnZC7UMVNEqTVArd2mFZYEnDZGvGbsiN7Qnfw3foKdNpvxMP+CfuQw7vxmCa1JOd5nLz6zKBEPGjIuCy0+wB6ASpUQNxUQQkQoC7L8Qt5cysUSM+8CgYAiSM6iMSp8bTM8ClHEFtRG6nUKaSMb6IC2WFoRyVFXH6fYZi7DPBNcc7D3SNetnlLqNBGlEBqAv8XS5J/5wgEpnKooKKiOex4sKQ5/1b9csSGK5m0i+sgHAMnQ0JeKOgSkepp2vwSXlN8l85aJ+A8/DSI513wPfDBgw2j/OVE91QKBgCnanTNBVAf8Kvewj7DtQGDitYFdZ5LqcwmL+q/OrXLEsGymYiE1Tf34b64TBcK/WSlVP/IJJoOAOOeZL05FszrCPhLvyPTlYZP7FuvX2MjsnpbZj6Lh+e416+7AWEBwvWyqUchhwTojayDE1juGpZcY4Qbea0ZdVTEt4fY6hN5lAoGBAIjrfcjMELZqlLEECwjvTgPmYqTgyJKgVw/SrYBvPpNNqVeIwb62FK19BjXuoIjecXBX8jvefGNa6qTw/OjZLk7JNNbbOdJ1PFp8GkD+e0PPUoISSY0+rAq77geLLfZbcZUwoE0UqKwSYcz0w549aUVKc/5x3tHOxEb7UcWWS5DY" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIIClzCCAX8CBgGEV1OMBzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARkZW1vMB4XDTIyMTEwODEyNTgyM1oXDTMyMTEwODEzMDAwM1owDzENMAsGA1UEAwwEZGVtbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBZiAPI3xQDFZvAbiDVg1gp//185ZOPMqFJctZD/EV64hjmH160EE6mntJ4QQapWiRQVwIyONVBiRPfuI9t/2hqfL3E01MvTOKgZfY5bfT6s9e/PymjA7gZHm901fD20ej16wkrljrk9yyWl1hIraXjDP/nRUjsQ+DNS1jsKEJbUpZomW5XG+TbEWpetLB7xVAOXnKd+Y1u0Y/sQECke0MYDq/K3/Nz/TT1mMMOPpWynW4Jw9iDH3c2fbKEoh1+FrUa5dXTwKp4oXxHmjqA1102tZuPUOMTkZzFbOhNeQPmb4rtMYUdd/ORk+7By4WW0ofqCx+J8fYxYp68vgIBpVMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAwPwABXVkSlRYqA9sMKJrw9piGH8tkwf6wQeAFhQsInbDzXLeuLt0A3gSuh5nL2zOGcxXddIK4IgUSqI+DlFlFsSkVqHFrQBAdIVRXsYFvGbARKhVuInHlpaOy6Y/VC6opL1BnqsmUOPEv7pk4Nhf/z7y5yZfTUaGiD+K1KA/mEf56NytOFJYsxiCZaAGX6BYIavRJp3YKPAcsNlJS//1G4meOlYcx5HiTA+qY/spc7vPeKuowSh3v26x4tgypLqoD0BAS5KrK4PEVaQM0IcBC3jrIY+7dGbE1Z374nm+1FDU7md48TJdI0c75r60cpBnHUH2bLJo2Z0ezrEojP6mvQ==" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] } } ] }, "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "id" : "d4f96a7b-4b90-4dd7-b90e-4d075336ff9d", + "id" : "95570b1f-da9e-42a0-9532-aa9b690b04cb", "alias" : "Account verification options", "description" : "Method with which to verity the existing account", "providerId" : "basic-flow", @@ -1188,7 +1188,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "5fac7aeb-2249-45c2-8a11-6925287c2228", + "id" : "854f25b9-b37b-468a-b8ac-1e0da29133ba", "alias" : "Authentication Options", "description" : "Authentication options.", "providerId" : "basic-flow", @@ -1217,7 +1217,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a85e7e37-1370-4266-a3f1-c16d77fd7b1a", + "id" : "ebcd0954-f08f-4c69-970e-43569320bb07", "alias" : "Browser - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1239,7 +1239,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "ff5d37dc-6c5e-4e06-abb0-640913ea53bf", + "id" : "cd4b03ca-1a0f-4a9b-8bd9-388e0459d7ac", "alias" : "Direct Grant - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1261,7 +1261,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "fdd2bf2c-9856-4671-8181-866e74fb9484", + "id" : "692bd371-d43a-440b-886b-2a0884dd5b9f", "alias" : "First broker login - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1283,7 +1283,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "3d326d31-096d-499a-bf5f-3d08d90220bd", + "id" : "dce38371-4f6b-45d1-94b0-5e91182c31fd", "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", @@ -1305,7 +1305,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "c915c1bf-3bc1-42de-813a-cc9dda7cc755", + "id" : "357541a3-2499-42da-9ef0-db129f38f39b", "alias" : "Reset - Conditional OTP", "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId" : "basic-flow", @@ -1327,7 +1327,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6428c4e7-249c-453e-a7f4-d4de9a7b611c", + "id" : "9c37a124-af5e-44a2-b619-b3a88e1033c6", "alias" : "User creation or linking", "description" : "Flow for the existing/non-existing user alternatives", "providerId" : "basic-flow", @@ -1350,7 +1350,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "251a6c61-d8de-4d4b-ae36-a92a4ed082eb", + "id" : "94355d8f-b66f-405d-80e5-55ec030fb18c", "alias" : "Verify Existing Account by Re-authentication", "description" : "Reauthentication of existing account", "providerId" : "basic-flow", @@ -1372,7 +1372,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "cde9f495-0cac-46cc-a031-0548bbdf9bd0", + "id" : "8fcdac85-ec7e-4a3b-908a-4f00978d4724", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -1408,7 +1408,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "16477e59-6d39-4699-8a30-096158b1d696", + "id" : "cd9ce79e-ba0b-4d58-8249-40f8b2139a56", "alias" : "clients", "description" : "Base authentication for clients", "providerId" : "client-flow", @@ -1444,7 +1444,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a72ed7b8-7a28-431c-ab05-b28b1473484f", + "id" : "40f9ac10-edc0-46a6-b020-ba1785199911", "alias" : "direct grant", "description" : "OpenID Connect Resource Owner Grant", "providerId" : "basic-flow", @@ -1473,7 +1473,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "7810a4a7-876e-4ebb-add8-29ca3c609fbf", + "id" : "0ac19480-8675-4679-b222-64508c62bcdb", "alias" : "docker auth", "description" : "Used by Docker clients to authenticate against the IDP", "providerId" : "basic-flow", @@ -1488,7 +1488,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "166d46c2-0026-4cb2-af2b-17aee2d8ce31", + "id" : "9fd2aa99-79d7-435b-a852-5c18fad28546", "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", @@ -1511,7 +1511,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "e9f6c960-53a1-4258-bf0b-a4d79e039fbf", + "id" : "da0a8a3d-9643-4746-b42d-2e8c915f76e7", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -1533,7 +1533,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "5b4516ff-7e2a-46ee-9aaa-2b2bbbd3ea94", + "id" : "5eea7962-6fe5-4b69-8401-313afd0f6559", "alias" : "http challenge", "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId" : "basic-flow", @@ -1555,7 +1555,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "3e55346b-262a-44fe-88d6-1825547f1469", + "id" : "f80d7c69-9314-4f4b-a61a-28a73e2d0879", "alias" : "registration", "description" : "registration flow", "providerId" : "basic-flow", @@ -1571,7 +1571,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "3d0aa61c-ec3e-4dde-b681-20b3e19954ae", + "id" : "deffbcd6-aa05-40e2-9bca-2ed50be54bd6", "alias" : "registration form", "description" : "registration form", "providerId" : "form-flow", @@ -1607,7 +1607,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "0a7ab5b4-bf7a-47e2-83f3-9199ba0b600e", + "id" : "0b06e91d-77f8-4d8e-a55b-21caf478524a", "alias" : "reset credentials", "description" : "Reset credentials for a user if they forgot their password or something", "providerId" : "basic-flow", @@ -1643,7 +1643,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "fb2dbb96-51d3-43f6-b819-11d63cf56463", + "id" : "d51e394a-e9a8-495b-ac30-3adb1e66a369", "alias" : "saml ecp", "description" : "SAML ECP Profile Authentication Flow", "providerId" : "basic-flow", @@ -1659,13 +1659,13 @@ } ] } ], "authenticatorConfig" : [ { - "id" : "d12c5e65-be6b-4264-9b33-b342387e90c5", + "id" : "3c1657ce-3153-4718-bbec-9d0c508eb8a8", "alias" : "create unique user config", "config" : { "require.password.update.after.registration" : "false" } }, { - "id" : "41df620e-e476-457e-89b6-9870412d08dc", + "id" : "70a33e93-226e-492e-b119-f164e2cc457b", "alias" : "review profile config", "config" : { "update.profile.on.first.login" : "missing" From ffac4da5099eeb4275ed1d3c7ace2216948da41c Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 9 Nov 2022 16:22:27 +0100 Subject: [PATCH 04/49] Properly handle basic realm attributes and clients --- .../service/export/RealmExportService.java | 173 ++++++++++-------- 1 file changed, 101 insertions(+), 72 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java index 62d2d95e1..0097c2df9 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -34,17 +34,18 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.FileOutputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; -import java.util.stream.Collectors; @Service public class RealmExportService { @@ -59,6 +60,7 @@ public class RealmExportService { static { YAML_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); var realmIgnoredProperties = new ArrayList(); + realmIgnoredProperties.add("id"); realmIgnoredProperties.add("groups"); realmIgnoredProperties.add("roles"); realmIgnoredProperties.add("defaultRole"); @@ -85,8 +87,9 @@ public class RealmExportService { realmIgnoredProperties.add("clientTemplates"); JAVERS = JaversBuilder.javers() - .registerEntity(new EntityDefinition(RealmRepresentation.class, "id", realmIgnoredProperties)) - .registerEntity(new EntityDefinition(ClientRepresentation.class, "clientId", List.of("id", "protocolMappers"))) + .registerEntity(new EntityDefinition(RealmRepresentation.class, "realm", realmIgnoredProperties)) + .registerEntity(new EntityDefinition(ClientRepresentation.class, "clientId", + List.of("id", "authorizationSettings", "protocolMappers"))) .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) .withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE) .build(); @@ -120,10 +123,11 @@ public void doExports() throws Exception { } var inputFile = Paths.get(exportConfigProperties.getLocation(), "in", "realm.json"); try (var is = Files.newInputStream(inputFile)) { - var realm = OBJECT_MAPPER.readValue(is, RealmRepresentation.class); - var realmName = realm.getRealm(); + var exportedRealm = OBJECT_MAPPER.readValue(is, RealmRepresentation.class); + var exportedRealmRealm = exportedRealm.getRealm(); RealmRepresentation defaultRealm; - try (var defaultRealmIs = getClass().getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportConfigProperties.getKeycloakVersion()))) { + try (var defaultRealmIs = getClass() + .getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportConfigProperties.getKeycloakVersion()))) { if (defaultRealmIs == null) { logger.error("Reference realm for version {} does not exist", exportConfigProperties.getKeycloakVersion()); return; @@ -132,109 +136,134 @@ public void doExports() throws Exception { * Replace the placeholder with the realm name to import. This sets some internal values like role names, * baseUrls and redirectUrls so that they don't get picked up as "changes" */ - var realmString = new String(defaultRealmIs.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, realmName); + var realmString = new String(defaultRealmIs.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, exportedRealmRealm); defaultRealm = OBJECT_MAPPER.readValue(realmString, RealmRepresentation.class); } - /* * Trick javers into thinking this is the "same" object, by setting the ID on the reference realm * to the ID of the current realm. That way we only get actual changes, not a full list of changes * including the "object removed" and "object added" changes */ - logger.info("Exporting realm {}", realmName); - defaultRealm.setId(realm.getId()); + logger.info("Exporting realm {}", exportedRealmRealm); + defaultRealm.setRealm(exportedRealm.getRealm()); var strippedRealm = new RealmRepresentation(); - handleBaseRealm(realm, defaultRealm, strippedRealm); - // Realm is complete, now do clients - var allClientsDiff = JAVERS.compareCollections(defaultRealm.getClients(),realm.getClients(), ClientRepresentation.class); + handleBaseRealm(exportedRealm, defaultRealm, strippedRealm); - if (allClientsDiff.hasChanges()) { - ClientRepresentation defaultEmptyClient; - try (var clientIs = getClass().getResourceAsStream(String.format("/reference-realms/%s/client.json", exportConfigProperties.getKeycloakVersion()))) { - defaultEmptyClient = OBJECT_MAPPER.readValue(clientIs, ClientRepresentation.class); - } - // Clients aren't all default. Enumerate the "default" clients first and check if they changed at all - var defaultReferenceClients = defaultRealm.getClients(); - - for (var defaultReferenceClient : defaultReferenceClients) { - var defaultRealmClient = realm.getClients().stream().filter(c -> c.getClientId().equals(defaultReferenceClient.getClientId())).findAny(); - if (defaultRealmClient.isPresent()) { - var client = defaultRealmClient.get(); - // First, compare it to the actual default client: - var clientDiff = JAVERS.compare(defaultReferenceClient, client); - if (clientDiff.hasChanges()) { - var strippedClient = createMinimizedClient(strippedRealm, defaultEmptyClient, client); - // Add protocol mappers here - // Maybe add authorizationSettings here - } - } - } - // Now that we're done with the default realm clients, handle clients that are *not* default - var defaultClientIds = defaultReferenceClients.stream().map(ClientRepresentation::getClientId).collect(Collectors.toList()); - var nonDefaultClients = realm.getClients().stream() - .filter(c -> !defaultClientIds.contains(c.getClientId())).collect(Collectors.toList()); - for (var client : nonDefaultClients) { - var strippedClient = createMinimizedClient(strippedRealm, defaultEmptyClient, client); - // Add protocol mappers here - // Maybe add authorizationSettings here - } - } - var outputFile = Paths.get(exportConfigProperties.getLocation(), "out", String.format("%s.yaml", realmName)); + handleClients(exportedRealm, defaultRealm, strippedRealm); + + var outputFile = Paths.get(exportConfigProperties.getLocation(), "out", String.format("%s.yaml", exportedRealmRealm)); try (var os = new FileOutputStream(outputFile.toFile())) { YAML_MAPPER.writeValue(os, strippedRealm); } } } - private ClientRepresentation createMinimizedClient(RealmRepresentation strippedRealm, ClientRepresentation defaultEmptyClient, ClientRepresentation client) throws NoSuchFieldException, IllegalAccessException { - // Trick javers again - defaultEmptyClient.setClientId(client.getClientId()); - /* - * We have changes compared to the keycloak default client. - * Now compare to a 'naked' client to generate a minimal client representation to put into yaml - */ + private void handleClients(RealmRepresentation exportedRealm, RealmRepresentation defaultRealm, RealmRepresentation strippedRealm) + throws IOException, NoSuchFieldException, IllegalAccessException { + // Get a client map for better lookups + var exportedClientMap = new HashMap(); + for (var exportedClient : exportedRealm.getClients()) { + exportedClientMap.put(exportedClient.getClientId(), exportedClient); + } + + var defaultClientMap = new HashMap(); + + // Handle the default realm clients first + for (var defaultRealmClient : defaultRealm.getClients()) { + defaultClientMap.put(defaultRealmClient.getClientId(), defaultRealmClient); + var exportedClient = exportedClientMap.get(defaultRealmClient.getClientId()); + if (exportedClient == null) { + logger.info("Default realm client {} was deleted in exported realm", defaultRealmClient.getClientId()); + } else { + var clientId = defaultRealmClient.getClientId(); + if (clientChanged(defaultRealmClient, exportedClientMap.get(clientId))) { + // We know the client has changed in some way. Now, compare it to a default client to minimize it + handleClient(strippedRealm, exportedClient, clientId); + } + } + } + + // Now iterate over all the clients that are *not* default clients + for (Map.Entry e : exportedClientMap.entrySet()) { + var clientId = e.getKey(); + if (!defaultClientMap.containsKey(clientId)) { + handleClient(strippedRealm, e.getValue(), clientId); + } + } + } + + private void handleClient(RealmRepresentation strippedRealm, ClientRepresentation exportedClient, String clientId) + throws IOException, NoSuchFieldException, IllegalAccessException { + var minimalClient = getMinimalClient(clientId); + var clientDiff = JAVERS.compare(minimalClient, exportedClient); var strippedClient = new ClientRepresentation(); - var minimalDiff = JAVERS.compare(defaultEmptyClient, client); - for (var change : minimalDiff.getChangesByType(PropertyChange.class)) { - applyClientChange(strippedClient, client, (PropertyChange) change); + for (var change : clientDiff.getChangesByType(PropertyChange.class)) { + applyChange(strippedClient, change); } - strippedClient.setClientId(client.getClientId()); - strippedClient.setEnabled(client.isEnabled()); if (strippedRealm.getClients() == null) { strippedRealm.setClients(new ArrayList<>()); } + // For now, don't minimize authorizationSettings and protocolMappers. Add them as-is + strippedClient.setProtocolMappers(exportedClient.getProtocolMappers()); + strippedClient.setAuthorizationSettings(exportedClient.getAuthorizationSettings()); + strippedClient.setClientId(clientId); strippedRealm.getClients().add(strippedClient); - return strippedClient; } - private void handleBaseRealm(RealmRepresentation realm, RealmRepresentation defaultRealm, RealmRepresentation strippedRealm) throws NoSuchFieldException, IllegalAccessException { - var diff = JAVERS.compare(defaultRealm, realm); + + private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentation defaultRealm, RealmRepresentation strippedRealm) + throws NoSuchFieldException, IllegalAccessException { + var diff = JAVERS.compare(defaultRealm, exportedRealm); for (var change : diff.getChangesByType(PropertyChange.class)) { - applyRealmChange(strippedRealm, change); + applyChange(strippedRealm, change); } // Now that Javers is done, clean up a bit afterwards. We always need to set the realm and enabled fields - strippedRealm.setRealm(realm.getRealm()); - strippedRealm.setEnabled(realm.isEnabled()); + strippedRealm.setRealm(exportedRealm.getRealm()); + strippedRealm.setEnabled(exportedRealm.isEnabled()); // If the realm ID diverges from the name, include it in the dump, otherwise remove it - if (Objects.equals(realm.getRealm(), realm.getId())) { + if (Objects.equals(exportedRealm.getRealm(), exportedRealm.getId())) { strippedRealm.setId(null); } else { - strippedRealm.setId(realm.getId()); + strippedRealm.setId(exportedRealm.getId()); } } - private void applyRealmChange(RealmRepresentation strippedRealm, PropertyChange change) throws NoSuchFieldException, IllegalAccessException { - var field = RealmRepresentation.class.getDeclaredField(change.getPropertyName()); + private void applyChange(Object object, PropertyChange change) throws NoSuchFieldException, IllegalAccessException { + var field = object.getClass().getDeclaredField(change.getPropertyName()); field.setAccessible(true); - field.set(strippedRealm, change.getRight()); + field.set(object, change.getRight()); } - private void applyClientChange(ClientRepresentation strippedClient, ClientRepresentation client, PropertyChange change) throws NoSuchFieldException, IllegalAccessException { - var field = ClientRepresentation.class.getDeclaredField(change.getPropertyName()); - field.setAccessible(true); - field.set(strippedClient, field.get(client)); + private boolean clientChanged(ClientRepresentation defaultClient, ClientRepresentation exportedClient) { + var diff = JAVERS.compare(defaultClient, exportedClient); + if (diff.hasChanges()) { + return true; + } + if (protocolMappersChanged(defaultClient.getProtocolMappers(), exportedClient.getProtocolMappers())) { + return true; + } + return authorizationSettingsChanged(defaultClient.getAuthorizationSettings(), exportedClient.getAuthorizationSettings()); + } + + private boolean protocolMappersChanged(List defaultMappers, List exportedMappers) { + // CompareCollections doesn't handle nulls gracefully + return JAVERS.compareCollections(defaultMappers == null ? List.of() : defaultMappers, + exportedMappers == null ? List.of() : exportedMappers, ProtocolMapperRepresentation.class).hasChanges(); + } + + private boolean authorizationSettingsChanged(ResourceServerRepresentation defaultSettings, ResourceServerRepresentation exportedSettings) { + return JAVERS.compare(defaultSettings, exportedSettings).hasChanges(); + } + + private ClientRepresentation getMinimalClient(String clientId) throws IOException { + try (var is = getClass() + .getResourceAsStream(String.format("/reference-realms/%s/client.json", exportConfigProperties.getKeycloakVersion()))) { + var client = OBJECT_MAPPER.readValue(is, ClientRepresentation.class); + client.setClientId(clientId); + return client; + } } } From 1ca6993933b8d7a7b174bd878a55083e189cdfb2 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 10 Nov 2022 09:13:38 +0100 Subject: [PATCH 05/49] Note for deleted client handling --- .../keycloak/config/service/export/RealmExportService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java index 0097c2df9..6a2f2f4ce 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -175,6 +175,11 @@ private void handleClients(RealmRepresentation exportedRealm, RealmRepresentatio var exportedClient = exportedClientMap.get(defaultRealmClient.getClientId()); if (exportedClient == null) { logger.info("Default realm client {} was deleted in exported realm", defaultRealmClient.getClientId()); + /* + * Here we need to define a configuration parameter: If we want the import *not* to reintroduce default clients that were + * deleted, we need to add *all* clients, not just default clients to the dump. Then during import, set the mode that + * makes clients fully managed, so that *only* clients that are in the dump end up in the realm + */ } else { var clientId = defaultRealmClient.getClientId(); if (clientChanged(defaultRealmClient, exportedClientMap.get(clientId))) { From d1d2f3b7090626ef376a985c4ad3cf0cbcd8b9bc Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 24 Nov 2022 13:07:45 +0100 Subject: [PATCH 06/49] Export for (client)scopeMappings --- .../service/export/RealmExportService.java | 203 +++++++++++++----- 1 file changed, 150 insertions(+), 53 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java index 6a2f2f4ce..9ab52e036 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -34,6 +34,7 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.ScopeMappingRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,19 +65,19 @@ public class RealmExportService { realmIgnoredProperties.add("groups"); realmIgnoredProperties.add("roles"); realmIgnoredProperties.add("defaultRole"); - realmIgnoredProperties.add("clientProfiles"); - realmIgnoredProperties.add("clientPolicies"); + realmIgnoredProperties.add("clientProfiles"); // + realmIgnoredProperties.add("clientPolicies"); // realmIgnoredProperties.add("users"); realmIgnoredProperties.add("federatedUsers"); - realmIgnoredProperties.add("scopeMappings"); - realmIgnoredProperties.add("clientScopeMappings"); - realmIgnoredProperties.add("clients"); - realmIgnoredProperties.add("clientScopes"); + realmIgnoredProperties.add("scopeMappings"); // + realmIgnoredProperties.add("clientScopeMappings"); // + realmIgnoredProperties.add("clients"); // + realmIgnoredProperties.add("clientScopes"); // realmIgnoredProperties.add("userFederationProviders"); realmIgnoredProperties.add("userFederationMappers"); realmIgnoredProperties.add("identityProviders"); realmIgnoredProperties.add("identityProviderMappers"); - realmIgnoredProperties.add("protocolMappers"); + realmIgnoredProperties.add("protocolMappers"); // realmIgnoredProperties.add("components"); realmIgnoredProperties.add("authenticationFlows"); realmIgnoredProperties.add("authenticatorConfig"); @@ -98,11 +99,22 @@ public class RealmExportService { private final ExportConfigProperties exportConfigProperties; private final KeycloakConfigProperties keycloakConfigProperties; + private static class ExceptionObject { + String name; + Class type; + List defaultValues; + } + private Map exclusions; + @Autowired public RealmExportService(ExportConfigProperties exportConfigProperties, KeycloakConfigProperties keycloakConfigProperties) { this.exportConfigProperties = exportConfigProperties; this.keycloakConfigProperties = keycloakConfigProperties; + + // TODO allow extra "default" values to be ignored? + + // TODO Ignore clients by regex } public void doExports() throws Exception { @@ -125,7 +137,7 @@ public void doExports() throws Exception { try (var is = Files.newInputStream(inputFile)) { var exportedRealm = OBJECT_MAPPER.readValue(is, RealmRepresentation.class); var exportedRealmRealm = exportedRealm.getRealm(); - RealmRepresentation defaultRealm; + RealmRepresentation baselineRealm; try (var defaultRealmIs = getClass() .getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportConfigProperties.getKeycloakVersion()))) { if (defaultRealmIs == null) { @@ -137,7 +149,7 @@ public void doExports() throws Exception { * baseUrls and redirectUrls so that they don't get picked up as "changes" */ var realmString = new String(defaultRealmIs.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, exportedRealmRealm); - defaultRealm = OBJECT_MAPPER.readValue(realmString, RealmRepresentation.class); + baselineRealm = OBJECT_MAPPER.readValue(realmString, RealmRepresentation.class); } /* * Trick javers into thinking this is the "same" object, by setting the ID on the reference realm @@ -145,21 +157,41 @@ public void doExports() throws Exception { * including the "object removed" and "object added" changes */ logger.info("Exporting realm {}", exportedRealmRealm); - defaultRealm.setRealm(exportedRealm.getRealm()); - var strippedRealm = new RealmRepresentation(); + baselineRealm.setRealm(exportedRealm.getRealm()); + var minimizedRealm = new RealmRepresentation(); + + handleBaseRealm(exportedRealm, baselineRealm, minimizedRealm); - handleBaseRealm(exportedRealm, defaultRealm, strippedRealm); + var clients = getMinimizedClients(exportedRealm, baselineRealm); + if (!clients.isEmpty()) { + minimizedRealm.setClients(clients); + } + + // No setter for some reason... + var minimizedScopeMappings = getMinimizedScopeMappings(exportedRealm, baselineRealm); + if (!minimizedScopeMappings.isEmpty()) { + var scopeMappings = minimizedRealm.getScopeMappings(); + if (scopeMappings == null) { + minimizedRealm.clientScopeMapping("dummy"); + scopeMappings = minimizedRealm.getScopeMappings(); + scopeMappings.clear(); + } + scopeMappings.addAll(getMinimizedScopeMappings(exportedRealm, baselineRealm)); + } - handleClients(exportedRealm, defaultRealm, strippedRealm); + var clientScopeMappings = getMinimizedClientScopeMappings(exportedRealm, baselineRealm); + if (!clientScopeMappings.isEmpty()) { + minimizedRealm.setClientScopeMappings(clientScopeMappings); + } var outputFile = Paths.get(exportConfigProperties.getLocation(), "out", String.format("%s.yaml", exportedRealmRealm)); try (var os = new FileOutputStream(outputFile.toFile())) { - YAML_MAPPER.writeValue(os, strippedRealm); + YAML_MAPPER.writeValue(os, minimizedRealm); } } } - private void handleClients(RealmRepresentation exportedRealm, RealmRepresentation defaultRealm, RealmRepresentation strippedRealm) + private List getMinimizedClients(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) throws IOException, NoSuchFieldException, IllegalAccessException { // Get a client map for better lookups var exportedClientMap = new HashMap(); @@ -167,73 +199,138 @@ private void handleClients(RealmRepresentation exportedRealm, RealmRepresentatio exportedClientMap.put(exportedClient.getClientId(), exportedClient); } - var defaultClientMap = new HashMap(); + var baselineClientMap = new HashMap(); - // Handle the default realm clients first - for (var defaultRealmClient : defaultRealm.getClients()) { - defaultClientMap.put(defaultRealmClient.getClientId(), defaultRealmClient); - var exportedClient = exportedClientMap.get(defaultRealmClient.getClientId()); + var clients = new ArrayList(); + for (var baselineRealmClient : baselineRealm.getClients()) { + var clientId = baselineRealmClient.getClientId(); + baselineClientMap.put(clientId, baselineRealmClient); + var exportedClient = exportedClientMap.get(clientId); if (exportedClient == null) { - logger.info("Default realm client {} was deleted in exported realm", defaultRealmClient.getClientId()); + logger.warn("Default realm client '{}' was deleted in exported realm. It will be reintroduced during import!", clientId); /* * Here we need to define a configuration parameter: If we want the import *not* to reintroduce default clients that were * deleted, we need to add *all* clients, not just default clients to the dump. Then during import, set the mode that * makes clients fully managed, so that *only* clients that are in the dump end up in the realm */ - } else { - var clientId = defaultRealmClient.getClientId(); - if (clientChanged(defaultRealmClient, exportedClientMap.get(clientId))) { - // We know the client has changed in some way. Now, compare it to a default client to minimize it - handleClient(strippedRealm, exportedClient, clientId); - } + continue; + } + if (clientChanged(baselineRealmClient, exportedClient)) { + // We know the client has changed in some way. Now, compare it to a default client to minimize it + clients.add(getMinimizedClient(exportedClient, clientId)); } } // Now iterate over all the clients that are *not* default clients for (Map.Entry e : exportedClientMap.entrySet()) { var clientId = e.getKey(); - if (!defaultClientMap.containsKey(clientId)) { - handleClient(strippedRealm, e.getValue(), clientId); + if (!baselineClientMap.containsKey(clientId)) { + clients.add(getMinimizedClient(e.getValue(), clientId)); } } + return clients; } - private void handleClient(RealmRepresentation strippedRealm, ClientRepresentation exportedClient, String clientId) + private ClientRepresentation getMinimizedClient(ClientRepresentation exportedClient, String clientId) throws IOException, NoSuchFieldException, IllegalAccessException { - var minimalClient = getMinimalClient(clientId); - var clientDiff = JAVERS.compare(minimalClient, exportedClient); - var strippedClient = new ClientRepresentation(); + var baselineClient = getBaselineClient(clientId); + var clientDiff = JAVERS.compare(baselineClient, exportedClient); + var minimizedClient = new ClientRepresentation(); for (var change : clientDiff.getChangesByType(PropertyChange.class)) { - applyChange(strippedClient, change); - } - if (strippedRealm.getClients() == null) { - strippedRealm.setClients(new ArrayList<>()); + applyChange(minimizedClient, change); } // For now, don't minimize authorizationSettings and protocolMappers. Add them as-is - strippedClient.setProtocolMappers(exportedClient.getProtocolMappers()); - strippedClient.setAuthorizationSettings(exportedClient.getAuthorizationSettings()); - strippedClient.setClientId(clientId); - strippedRealm.getClients().add(strippedClient); + minimizedClient.setProtocolMappers(exportedClient.getProtocolMappers()); + minimizedClient.setAuthorizationSettings(exportedClient.getAuthorizationSettings()); + minimizedClient.setClientId(clientId); + return minimizedClient; } - private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentation defaultRealm, RealmRepresentation strippedRealm) + private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm, RealmRepresentation minimizedRealm) throws NoSuchFieldException, IllegalAccessException { - var diff = JAVERS.compare(defaultRealm, exportedRealm); + var diff = JAVERS.compare(baselineRealm, exportedRealm); for (var change : diff.getChangesByType(PropertyChange.class)) { - applyChange(strippedRealm, change); + applyChange(minimizedRealm, change); } // Now that Javers is done, clean up a bit afterwards. We always need to set the realm and enabled fields - strippedRealm.setRealm(exportedRealm.getRealm()); - strippedRealm.setEnabled(exportedRealm.isEnabled()); + minimizedRealm.setRealm(exportedRealm.getRealm()); + minimizedRealm.setEnabled(exportedRealm.isEnabled()); // If the realm ID diverges from the name, include it in the dump, otherwise remove it if (Objects.equals(exportedRealm.getRealm(), exportedRealm.getId())) { - strippedRealm.setId(null); + minimizedRealm.setId(null); } else { - strippedRealm.setId(exportedRealm.getId()); + minimizedRealm.setId(exportedRealm.getId()); + } + } + + private List getMinimizedScopeMappings(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { + /* + * TODO: are the mappings in scopeMappings always clientScope/role? If not, this breaks + */ + // First handle the "default" scopeMappings present in the + var exportedMappingsMap = new HashMap(); + for (var exportedMapping : exportedRealm.getScopeMappings()) { + exportedMappingsMap.put(exportedMapping.getClientScope(), exportedMapping); + } + + var baselineMappingsMap = new HashMap(); + + var mappings = new ArrayList(); + for (var baselineRealmMapping : baselineRealm.getScopeMappings()) { + var clientScope = baselineRealmMapping.getClientScope(); + baselineMappingsMap.put(clientScope, baselineRealmMapping); + var exportedMapping = exportedMappingsMap.get(clientScope); + if (exportedMapping == null) { + logger.warn("Default realm scopeMapping '{}' was deleted in exported realm. It will be reintroduced during import!", clientScope); + continue; + } + // If the exported scopeMapping is different from the one that is present in the baseline realm, export it in the yml + if (scopeMappingChanged(baselineRealmMapping, exportedMapping)) { + mappings.add(exportedMapping); + } + } + + for (Map.Entry e : exportedMappingsMap.entrySet()) { + var clientScope = e.getKey(); + if (!baselineMappingsMap.containsKey(clientScope)) { + mappings.add(e.getValue()); + } + } + return mappings; + } + + private Map> getMinimizedClientScopeMappings(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { + var baselineMappings = baselineRealm.getClientScopeMappings(); + var exportedMappings = exportedRealm.getClientScopeMappings(); + + var mappings = new HashMap>(); + for (var e : baselineMappings.entrySet()) { + var key = e.getKey(); + if (!exportedMappings.containsKey(key)) { + logger.warn("Default realm clientScopeMapping '{}' was deleted in exported realm. It will be reintroduced during import!", key); + continue; + } + var scopeMappings = exportedMappings.get(key); + if (JAVERS.compareCollections(e.getValue(), scopeMappings, ScopeMappingRepresentation.class).hasChanges()) { + mappings.put(key, scopeMappings); + } } + + for (var e : exportedMappings.entrySet()) { + var key = e.getKey(); + if (!baselineMappings.containsKey(key)) { + mappings.put(key, e.getValue()); + } + } + return mappings; + } + + + private boolean scopeMappingChanged(ScopeMappingRepresentation baselineRealmMapping, ScopeMappingRepresentation exportedMapping) { + return JAVERS.compare(baselineRealmMapping, exportedMapping).hasChanges(); } private void applyChange(Object object, PropertyChange change) throws NoSuchFieldException, IllegalAccessException { @@ -242,15 +339,15 @@ private void applyChange(Object object, PropertyChange change) throws NoSuchF field.set(object, change.getRight()); } - private boolean clientChanged(ClientRepresentation defaultClient, ClientRepresentation exportedClient) { - var diff = JAVERS.compare(defaultClient, exportedClient); + private boolean clientChanged(ClientRepresentation baselineClient, ClientRepresentation exportedClient) { + var diff = JAVERS.compare(baselineClient, exportedClient); if (diff.hasChanges()) { return true; } - if (protocolMappersChanged(defaultClient.getProtocolMappers(), exportedClient.getProtocolMappers())) { + if (protocolMappersChanged(baselineClient.getProtocolMappers(), exportedClient.getProtocolMappers())) { return true; } - return authorizationSettingsChanged(defaultClient.getAuthorizationSettings(), exportedClient.getAuthorizationSettings()); + return authorizationSettingsChanged(baselineClient.getAuthorizationSettings(), exportedClient.getAuthorizationSettings()); } private boolean protocolMappersChanged(List defaultMappers, List exportedMappers) { @@ -263,7 +360,7 @@ private boolean authorizationSettingsChanged(ResourceServerRepresentation defaul return JAVERS.compare(defaultSettings, exportedSettings).hasChanges(); } - private ClientRepresentation getMinimalClient(String clientId) throws IOException { + private ClientRepresentation getBaselineClient(String clientId) throws IOException { try (var is = getClass() .getResourceAsStream(String.format("/reference-realms/%s/client.json", exportConfigProperties.getKeycloakVersion()))) { var client = OBJECT_MAPPER.readValue(is, ClientRepresentation.class); From 972bea05d20f793166daaf5b2a8dd19400446015 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 24 Nov 2022 20:30:03 +0100 Subject: [PATCH 07/49] Properly differentiate import/export --- .../config/KeycloakConfigApplication.java | 5 +- .../config/KeycloakConfigExportRunner.java | 76 +++++++++++++++++++ .../keycloak/config/KeycloakConfigRunner.java | 58 ++++++-------- .../properties/ImportConfigProperties.java | 54 +++++++++---- .../properties/RunConfigProperties.java | 46 +++++++++++ .../service/export/RealmExportService.java | 12 +-- .../application-export-dev.properties | 8 +- src/main/resources/application.properties | 31 +------- 8 files changed, 190 insertions(+), 100 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java create mode 100644 src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java index e51519a86..2103d2901 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java @@ -20,15 +20,14 @@ package de.adorsys.keycloak.config; -import de.adorsys.keycloak.config.properties.ExportConfigProperties; -import de.adorsys.keycloak.config.properties.ImportConfigProperties; import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; +import de.adorsys.keycloak.config.properties.RunConfigProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication(proxyBeanMethods = false) -@EnableConfigurationProperties({KeycloakConfigProperties.class, ImportConfigProperties.class, ExportConfigProperties.class}) +@EnableConfigurationProperties({KeycloakConfigProperties.class, RunConfigProperties.class}) public class KeycloakConfigApplication { public static void main(String[] args) { // https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java new file mode 100644 index 000000000..da08f3936 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java @@ -0,0 +1,76 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config; + +import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import de.adorsys.keycloak.config.service.export.RealmExportService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.ExitCodeGenerator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.util.Date; + +@Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "EXPORT") +@EnableConfigurationProperties(ExportConfigProperties.class) +public class KeycloakConfigExportRunner implements CommandLineRunner, ExitCodeGenerator { + + private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigExportRunner.class); + private static final long START_TIME = System.currentTimeMillis(); + + private final RealmExportService exportService; + private int exitCode; + + @Autowired + public KeycloakConfigExportRunner(RealmExportService exportService) { + this.exportService = exportService; + } + + @Override + public void run(String... args) throws Exception { + try { + exportService.doExports(); + } catch (Exception e) { + logger.error(e.getMessage()); + + exitCode = 1; + + if (logger.isDebugEnabled()) { + throw e; + } + } finally { + long totalTime = System.currentTimeMillis() - START_TIME; + String formattedTime = new SimpleDateFormat("mm:ss.SSS").format(new Date(totalTime)); + logger.info("keycloak-config-cli running in {}.", formattedTime); + } + } + + @Override + public int getExitCode() { + return exitCode; + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java index 47b88cf3b..77c80b645 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java @@ -22,16 +22,16 @@ import de.adorsys.keycloak.config.model.KeycloakImport; import de.adorsys.keycloak.config.model.RealmImport; -import de.adorsys.keycloak.config.properties.ExportConfigProperties; import de.adorsys.keycloak.config.properties.ImportConfigProperties; import de.adorsys.keycloak.config.provider.KeycloakImportProvider; import de.adorsys.keycloak.config.service.RealmImportService; -import de.adorsys.keycloak.config.service.export.RealmExportService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.ExitCodeGenerator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; @@ -41,6 +41,13 @@ import java.util.Map; @Component +/* + * Spring only considers actual properties set, not default values of @ConfigurationProperties classes. + * Therefore, we enable matchIfMissing here, so if there is *no* property set, we consider it an import + * for backwards compatibility + */ +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) +@EnableConfigurationProperties(ImportConfigProperties.class) public class KeycloakConfigRunner implements CommandLineRunner, ExitCodeGenerator { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); private static final long START_TIME = System.currentTimeMillis(); @@ -48,8 +55,6 @@ public class KeycloakConfigRunner implements CommandLineRunner, ExitCodeGenerato private final KeycloakImportProvider keycloakImportProvider; private final RealmImportService realmImportService; private final ImportConfigProperties importConfigProperties; - private final ExportConfigProperties exportConfigProperties; - private final RealmExportService realmExportService; private int exitCode = 0; @@ -57,14 +62,10 @@ public class KeycloakConfigRunner implements CommandLineRunner, ExitCodeGenerato public KeycloakConfigRunner( KeycloakImportProvider keycloakImportProvider, RealmImportService realmImportService, - ImportConfigProperties importConfigProperties, - ExportConfigProperties exportConfigProperties, - RealmExportService realmExportService) { + ImportConfigProperties importConfigProperties) { this.keycloakImportProvider = keycloakImportProvider; this.realmImportService = realmImportService; this.importConfigProperties = importConfigProperties; - this.exportConfigProperties = exportConfigProperties; - this.realmExportService = realmExportService; } @Override @@ -73,12 +74,20 @@ public int getExitCode() { } @Override - public void run(String... args) throws Exception { + public void run(String... args) { try { - if (exportConfigProperties.isEnabled()) { - runExport(); - } else { - runImport(); + Collection importLocations = importConfigProperties.getFiles().getLocations(); + KeycloakImport keycloakImport = keycloakImportProvider.readFromLocations(importLocations); + + Map>> realmImports = keycloakImport.getRealmImports(); + + for (Map> realmImportLocations : realmImports.values()) { + for (Map.Entry> realmImport : realmImportLocations.entrySet()) { + logger.info("Importing file '{}'", realmImport.getKey()); + for (RealmImport realmImportParts : realmImport.getValue()) { + realmImportService.doImport(realmImportParts); + } + } } } catch (NullPointerException e) { throw e; @@ -96,25 +105,4 @@ public void run(String... args) throws Exception { logger.info("keycloak-config-cli running in {}.", formattedTime); } } - - private void runImport() { - Collection importLocations = importConfigProperties.getFiles().getLocations(); - KeycloakImport keycloakImport = keycloakImportProvider.readFromLocations(importLocations); - - Map>> realmImports = keycloakImport.getRealmImports(); - - for (Map> realmImportLocations : realmImports.values()) { - for (Map.Entry> realmImport : realmImportLocations.entrySet()) { - logger.info("Importing file '{}'", realmImport.getKey()); - for (RealmImport realmImportParts : realmImport.getValue()) { - realmImportService.doImport(realmImportParts); - } - } - } - } - - private void runExport() throws Exception { - logger.info("Exporting all the realms!"); - realmExportService.doExports(); - } } diff --git a/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java index 6370776b8..1f89f7ca6 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java @@ -22,6 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.validation.annotation.Validated; import java.util.Collection; @@ -62,10 +63,14 @@ public class ImportConfigProperties { @Valid private final ImportRemoteStateProperties remoteState; - public ImportConfigProperties(boolean validate, boolean parallel, - ImportFilesProperties files, ImportVarSubstitutionProperties varSubstitution, - ImportBehaviorsProperties behaviors, ImportCacheProperties cache, ImportManagedProperties managed, - ImportRemoteStateProperties remoteState + public ImportConfigProperties(@DefaultValue("true") boolean validate, + @DefaultValue("false") boolean parallel, + @DefaultValue ImportFilesProperties files, + @DefaultValue ImportVarSubstitutionProperties varSubstitution, + @DefaultValue ImportBehaviorsProperties behaviors, + @DefaultValue ImportCacheProperties cache, + @DefaultValue ImportManagedProperties managed, + @DefaultValue ImportRemoteStateProperties remoteState ) { this.validate = validate; this.parallel = parallel; @@ -150,13 +155,19 @@ public static class ImportManagedProperties { @NotNull private final ImportManagedPropertiesValues clientAuthorizationResources; - public ImportManagedProperties(ImportManagedPropertiesValues requiredAction, ImportManagedPropertiesValues group, - ImportManagedPropertiesValues clientScope, ImportManagedPropertiesValues scopeMapping, - ImportManagedPropertiesValues clientScopeMapping, ImportManagedPropertiesValues component, - ImportManagedPropertiesValues subComponent, ImportManagedPropertiesValues authenticationFlow, - ImportManagedPropertiesValues identityProvider, ImportManagedPropertiesValues identityProviderMapper, - ImportManagedPropertiesValues role, ImportManagedPropertiesValues client, - ImportManagedPropertiesValues clientAuthorizationResources) { + public ImportManagedProperties(@DefaultValue("FULL") ImportManagedPropertiesValues requiredAction, + @DefaultValue("FULL") ImportManagedPropertiesValues group, + @DefaultValue("FULL") ImportManagedPropertiesValues clientScope, + @DefaultValue("FULL") ImportManagedPropertiesValues scopeMapping, + @DefaultValue("FULL") ImportManagedPropertiesValues clientScopeMapping, + @DefaultValue("FULL") ImportManagedPropertiesValues component, + @DefaultValue("FULL") ImportManagedPropertiesValues subComponent, + @DefaultValue("FULL") ImportManagedPropertiesValues authenticationFlow, + @DefaultValue("FULL") ImportManagedPropertiesValues identityProvider, + @DefaultValue("FULL") ImportManagedPropertiesValues identityProviderMapper, + @DefaultValue("FULL") ImportManagedPropertiesValues role, + @DefaultValue("FULL") ImportManagedPropertiesValues client, + @DefaultValue("FULL") ImportManagedPropertiesValues clientAuthorizationResources) { this.requiredAction = requiredAction; this.group = group; this.clientScope = clientScope; @@ -240,7 +251,9 @@ public static class ImportFilesProperties { @NotNull private final boolean includeHiddenFiles; - public ImportFilesProperties(Collection locations, Collection excludes, boolean includeHiddenFiles) { + public ImportFilesProperties(Collection locations, + @DefaultValue("") Collection excludes, + @DefaultValue("false") boolean includeHiddenFiles) { this.locations = locations; this.excludes = excludes; this.includeHiddenFiles = includeHiddenFiles; @@ -276,7 +289,11 @@ public static class ImportVarSubstitutionProperties { @NotNull private final String suffix; - public ImportVarSubstitutionProperties(boolean enabled, boolean nested, boolean undefinedIsError, String prefix, String suffix) { + public ImportVarSubstitutionProperties(@DefaultValue("false") boolean enabled, + @DefaultValue("true") boolean nested, + @DefaultValue("true") boolean undefinedIsError, + @DefaultValue("$(") String prefix, + @DefaultValue(")") String suffix) { this.enabled = enabled; this.nested = nested; this.undefinedIsError = undefinedIsError; @@ -316,7 +333,9 @@ public static class ImportBehaviorsProperties { @NotNull private final boolean skipAttributesForFederatedUser; - public ImportBehaviorsProperties(boolean syncUserFederation, boolean removeDefaultRoleFromUser, boolean skipAttributesForFederatedUser) { + public ImportBehaviorsProperties(@DefaultValue("false") boolean syncUserFederation, + @DefaultValue("false") boolean removeDefaultRoleFromUser, + @DefaultValue("false") boolean skipAttributesForFederatedUser) { this.syncUserFederation = syncUserFederation; this.removeDefaultRoleFromUser = removeDefaultRoleFromUser; this.skipAttributesForFederatedUser = skipAttributesForFederatedUser; @@ -343,7 +362,8 @@ public static class ImportCacheProperties { @NotNull private final String key; - public ImportCacheProperties(boolean enabled, String key) { + public ImportCacheProperties(@DefaultValue("true") boolean enabled, + @DefaultValue("default") String key) { this.enabled = enabled; this.key = key; } @@ -367,7 +387,9 @@ public static class ImportRemoteStateProperties { @Pattern(regexp = "^[A-Fa-f0-9]+$") private final String encryptionSalt; - public ImportRemoteStateProperties(boolean enabled, String encryptionKey, String encryptionSalt) { + public ImportRemoteStateProperties(@DefaultValue("true") boolean enabled, + String encryptionKey, + @DefaultValue("2B521C795FBE2F2425DB150CD3700BA9") String encryptionSalt) { this.enabled = enabled; this.encryptionKey = encryptionKey; this.encryptionSalt = encryptionSalt; diff --git a/src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java new file mode 100644 index 000000000..9a23c1f0e --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java @@ -0,0 +1,46 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.boot.context.properties.bind.DefaultValue; +import org.springframework.validation.annotation.Validated; + +@ConfigurationProperties(prefix = "run", ignoreUnknownFields = false) +@ConstructorBinding +@Validated +public class RunConfigProperties { + + private final Operation operation; + + public RunConfigProperties(@DefaultValue("IMPORT") Operation operation) { + this.operation = operation; + } + + public Operation getOperation() { + return operation; + } + + public enum Operation { + IMPORT, EXPORT + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java index 9ab52e036..c6220f43d 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -2,7 +2,7 @@ * ---license-start * keycloak-config-cli * --- - * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com * --- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,13 +99,6 @@ public class RealmExportService { private final ExportConfigProperties exportConfigProperties; private final KeycloakConfigProperties keycloakConfigProperties; - private static class ExceptionObject { - String name; - Class type; - List defaultValues; - } - private Map exclusions; - @Autowired public RealmExportService(ExportConfigProperties exportConfigProperties, KeycloakConfigProperties keycloakConfigProperties) { @@ -302,7 +295,8 @@ private List getMinimizedScopeMappings(RealmRepresen return mappings; } - private Map> getMinimizedClientScopeMappings(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { + private Map> getMinimizedClientScopeMappings(RealmRepresentation exportedRealm, + RealmRepresentation baselineRealm) { var baselineMappings = baselineRealm.getClientScopeMappings(); var exportedMappings = exportedRealm.getClientScopeMappings(); diff --git a/src/main/resources/application-export-dev.properties b/src/main/resources/application-export-dev.properties index d003f2a16..0a256c449 100644 --- a/src/main/resources/application-export-dev.properties +++ b/src/main/resources/application-export-dev.properties @@ -1,10 +1,4 @@ -import.files.locations=/tmp -keycloak.url=http://localhost:8080 -keycloak.client-id=keycloak-config-cli -keycloak.client-secret=mysecret -keycloak.grant-type=client_credentials -keycloak.availability-check.enabled=true -keycloak.availability-check.timeout=120s +run.operation=EXPORT export.enabled=true export.excludes=fgvsad,master export.keycloak-version=19.0.3 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8378d75aa..b391d314e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -17,36 +17,7 @@ keycloak.read-timeout=10s keycloak.availability-check.enabled=false keycloak.availability-check.timeout=120s keycloak.availability-check.retry-delay=2s -import.validate=true -import.parallel=false -import.files.excludes="" -import.files.include-hidden-files=false -import.cache.enabled=true -import.cache.key=default -import.var-substitution.enabled=false -import.var-substitution.nested=true -import.var-substitution.undefined-is-error=true -import.var-substitution.prefix=$( -import.var-substitution.suffix=) -import.remote-state.enabled=true -# For security reasons, change this value if you want to encrypt the state -import.remote-state.encryption-salt=2B521C795FBE2F2425DB150CD3700BA9 -import.behaviors.remove-default-role-from-user=false -import.behaviors.skip-attributes-for-federated-user=false -import.behaviors.sync-user-federation=false -import.managed.authentication-flow=full -import.managed.group=full -import.managed.required-action=full -import.managed.client-scope=full -import.managed.scope-mapping=full -import.managed.client-scope-mapping=full -import.managed.component=full -import.managed.sub-component=full -import.managed.identity-provider=full -import.managed.identity-provider-mapper=full -import.managed.role=full -import.managed.client=full -import.managed.client-authorization-resources=full + logging.group.http=org.apache.http.wire logging.group.realm-config=de.adorsys.keycloak.config.provider.KeycloakImportProvider logging.group.keycloak-config-cli=de.adorsys.keycloak.config.service,de.adorsys.keycloak.config.KeycloakConfigRunner,de.adorsys.keycloak.config.provider.KeycloakProvider From 49a6e693897c7660b42e13df5c6c4995757720bc Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 29 Nov 2022 12:27:55 +0100 Subject: [PATCH 08/49] Use similar file params to import --- .../config/KeycloakConfigExportRunner.java | 19 +- .../properties/ExportConfigProperties.java | 69 ++++-- .../provider/KeycloakExportProvider.java | 229 ++++++++++++++++++ .../service/export/RealmExportService.java | 110 +++++---- .../application-export-dev.properties | 9 +- 5 files changed, 351 insertions(+), 85 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java index da08f3936..6c63bef51 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java @@ -21,7 +21,9 @@ package de.adorsys.keycloak.config; import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import de.adorsys.keycloak.config.provider.KeycloakExportProvider; import de.adorsys.keycloak.config.service.export.RealmExportService; +import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +35,8 @@ import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; +import java.util.Map; @Component @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "EXPORT") @@ -43,17 +47,28 @@ public class KeycloakConfigExportRunner implements CommandLineRunner, ExitCodeGe private static final long START_TIME = System.currentTimeMillis(); private final RealmExportService exportService; + private final KeycloakExportProvider exportProvider; + private int exitCode; @Autowired - public KeycloakConfigExportRunner(RealmExportService exportService) { + public KeycloakConfigExportRunner(RealmExportService exportService, KeycloakExportProvider exportProvider) { this.exportService = exportService; + this.exportProvider = exportProvider; } @Override public void run(String... args) throws Exception { try { - exportService.doExports(); + //exportService.doExports(); + for (Map> exportLocations : exportProvider.readFromLocations().values()) { + for (Map.Entry> export : exportLocations.entrySet()) { + logger.info("Normalizing file '{}'", export.getKey()); + for (RealmRepresentation realm : export.getValue()) { + exportService.doExport(realm); + } + } + } } catch (Exception e) { logger.error(e.getMessage()); diff --git a/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java index cd9299222..5edfe2860 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java @@ -22,9 +22,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.validation.annotation.Validated; -import java.util.List; +import java.util.Collection; +import javax.validation.Valid; import javax.validation.constraints.NotNull; @ConfigurationProperties(prefix = "export", ignoreUnknownFields = false) @@ -32,34 +34,55 @@ @Validated public class ExportConfigProperties { - private final boolean enabled; - private final List excludes; - @NotNull - private final String location; + @Valid + private final ExportFilesProperties files; - @NotNull - private final String keycloakVersion; - - public ExportConfigProperties(boolean enabled, List excludes, String keycloakVersion, String location) { - this.enabled = enabled; - this.excludes = excludes == null ? List.of() : excludes; - this.keycloakVersion = keycloakVersion; - this.location = location; + public ExportConfigProperties(@DefaultValue ExportFilesProperties files) { + this.files = files; } - public boolean isEnabled() { - return enabled; + public ExportFilesProperties getFiles() { + return files; } - public List getExcludes() { - return excludes; - } + public static class ExportFilesProperties { - public String getKeycloakVersion() { - return keycloakVersion; - } + @NotNull + private final Collection inputLocations; + + @NotNull + private final Collection excludes; + + @NotNull + private final boolean includeHiddenFiles; + + @NotNull + private final String outputDirectory; + + public ExportFilesProperties(Collection inputLocations, + @DefaultValue("") Collection excludes, + @DefaultValue("false") boolean includeHiddenFiles, + String outputDirectory) { + this.inputLocations = inputLocations; + this.excludes = excludes; + this.includeHiddenFiles = includeHiddenFiles; + this.outputDirectory = outputDirectory; + } + + public Collection getInputLocations() { + return inputLocations; + } + + public Collection getExcludes() { + return excludes; + } + + public boolean isIncludeHiddenFiles() { + return includeHiddenFiles; + } - public String getLocation() { - return location; + public String getOutputDirectory() { + return outputDirectory; + } } } diff --git a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java new file mode 100644 index 000000000..19ef236cd --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java @@ -0,0 +1,229 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.provider; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.adorsys.keycloak.config.exception.InvalidImportException; +import de.adorsys.keycloak.config.model.ImportResource; +import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.keycloak.representations.idm.RealmRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.stereotype.Component; +import org.springframework.util.PathMatcher; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/* + * This class heavily copy pastes code from KeycloakImportProvider. This can probably be reduced quite a bit by moving some code out to a shared class + */ +@Component +public class KeycloakExportProvider { + + private static final Logger logger = LoggerFactory.getLogger(KeycloakExportProvider.class); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + private final PathMatchingResourcePatternResolver patternResolver; + + private final ExportConfigProperties exportConfigProperties; + + @Autowired + public KeycloakExportProvider(PathMatchingResourcePatternResolver patternResolver, + ExportConfigProperties exportConfigProperties) { + this.patternResolver = patternResolver; + this.exportConfigProperties = exportConfigProperties; + } + + public Map>> readFromLocations() { + Map>> files = new LinkedHashMap<>(); + + for (String location : exportConfigProperties.getFiles().getInputLocations()) { + logger.debug("Loading file location '{}'", location); + String resourceLocation = prepareResourceLocation(location); + + Resource[] resources; + try { + resources = this.patternResolver.getResources(resourceLocation); + } catch (IOException e) { + throw new InvalidImportException("Unable to proceed location '" + location + "': " + e.getMessage(), e); + } + + resources = Arrays.stream(resources).filter(this::filterExcludedResources).toArray(Resource[]::new); + + if (resources.length == 0) { + throw new InvalidImportException("No files matching '" + location + "'!"); + } + + Map> exports = Arrays.stream(resources) + .map(this::readResource) + .filter(this::filterEmptyResources) + .sorted(Map.Entry.comparingByKey()) + .map(this::readRealms) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, + (oldValue, newValue) -> oldValue, LinkedHashMap::new)); + files.put(location, exports); + } + return files; + } + + private Pair> readRealms(ImportResource resource) { + String location = resource.getFilename(); + String content = resource.getValue(); + + if (logger.isTraceEnabled()) { + logger.trace(content); + } + + List realms; + try { + realms = readContent(content); + } catch (Exception e) { + throw new InvalidImportException("Unable to parse file '" + location + "': " + e.getMessage(), e); + } + return new ImmutablePair<>(location, realms); + } + + private List readContent(String content) { + List realms = new ArrayList<>(); + + Yaml yaml = new Yaml(); + Iterable yamlDocuments = yaml.loadAll(content); + + for (Object yamlDocument : yamlDocuments) { + realms.add(OBJECT_MAPPER.convertValue(yamlDocument, RealmRepresentation.class)); + } + return realms; + } + + private String prepareResourceLocation(String location) { + String importLocation = location; + + importLocation = importLocation.replaceFirst("^zip:", "jar:"); + + // backward compatibility to correct a possible missing prefix "file:" in path + if (!importLocation.contains(":")) { + importLocation = "file:" + importLocation; + } + return importLocation; + } + + private boolean filterExcludedResources(Resource resource) { + if (!resource.isFile()) { + return true; + } + + File file; + + try { + file = resource.getFile(); + } catch (IOException ignored) { + return true; + } + + if (file.isDirectory()) { + return false; + } + + if (!this.exportConfigProperties.getFiles().isIncludeHiddenFiles() && (file.isHidden() || FileUtils.hasHiddenAncestorDirectory(file))) { + return false; + } + + PathMatcher pathMatcher = patternResolver.getPathMatcher(); + return exportConfigProperties.getFiles().getExcludes() + .stream() + .map(pattern -> pattern.startsWith("**") ? "/" + pattern : pattern) + .map(pattern -> !pattern.startsWith("/**") ? "/**" + pattern : pattern) + .map(pattern -> !pattern.startsWith("/") ? "/" + pattern : pattern) + .noneMatch(pattern -> { + boolean match = pathMatcher.match(pattern, file.getPath()); + if (match) { + logger.debug("Excluding resource file '{}' (match {})", file.getPath(), pattern); + return true; + } + return false; + }); + } + + private ImportResource readResource(Resource resource) { + logger.debug("Loading file '{}'", resource.getFilename()); + + try { + resource = setupAuthentication(resource); + try (InputStream inputStream = resource.getInputStream()) { + return new ImportResource(resource.getURI().toString(), new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)); + } + } catch (IOException e) { + throw new InvalidImportException("Unable to proceed resource '" + resource + "': " + e.getMessage(), e); + } finally { + Authenticator.setDefault(null); + } + } + + private Resource setupAuthentication(Resource resource) throws IOException { + String userInfo; + + try { + userInfo = resource.getURL().getUserInfo(); + } catch (IOException e) { + return resource; + } + + if (userInfo == null) return resource; + + String[] userInfoSplit = userInfo.split(":"); + + if (userInfoSplit.length != 2) return resource; + + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(userInfoSplit[0], userInfoSplit[1].toCharArray()); + } + }); + + // Mask AuthInfo + String location = resource.getURI().toString().replace(userInfo + "@", "***@"); + return new UrlResource(location); + } + + private boolean filterEmptyResources(ImportResource resource) { + return !resource.getValue().isEmpty(); + } + + +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java index c6220f43d..067da4aa7 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java @@ -110,77 +110,75 @@ public RealmExportService(ExportConfigProperties exportConfigProperties, // TODO Ignore clients by regex } - public void doExports() throws Exception { - var outputLocation = Paths.get(exportConfigProperties.getLocation()); + public void doExport(RealmRepresentation exportedRealm) throws Exception { + var outputLocation = Paths.get(exportConfigProperties.getFiles().getOutputDirectory()); if (!Files.exists(outputLocation)) { + logger.info("Creating output directory '{}'", outputLocation); Files.createDirectories(outputLocation); } if (!Files.isDirectory(outputLocation)) { - logger.error("Output location '{}' is not a directory. Aborting.", exportConfigProperties.getLocation()); + logger.error("Output location '{}' is not a directory. Aborting.", outputLocation); } var keycloakConfigVersion = keycloakConfigProperties.getVersion(); - var exportVersion = exportConfigProperties.getKeycloakVersion(); + var exportVersion = exportedRealm.getKeycloakVersion(); if (!exportVersion.equals(keycloakConfigVersion)) { logger.warn("Keycloak-Config-CLI keycloak version {} and export keycloak version {} are not equal." + " This may cause problems if the API changed." + " Please compile keycloak-config-cli with a matching keycloak version!", keycloakConfigVersion, exportVersion); } - var inputFile = Paths.get(exportConfigProperties.getLocation(), "in", "realm.json"); - try (var is = Files.newInputStream(inputFile)) { - var exportedRealm = OBJECT_MAPPER.readValue(is, RealmRepresentation.class); - var exportedRealmRealm = exportedRealm.getRealm(); - RealmRepresentation baselineRealm; - try (var defaultRealmIs = getClass() - .getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportConfigProperties.getKeycloakVersion()))) { - if (defaultRealmIs == null) { - logger.error("Reference realm for version {} does not exist", exportConfigProperties.getKeycloakVersion()); - return; - } - /* - * Replace the placeholder with the realm name to import. This sets some internal values like role names, - * baseUrls and redirectUrls so that they don't get picked up as "changes" - */ - var realmString = new String(defaultRealmIs.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, exportedRealmRealm); - baselineRealm = OBJECT_MAPPER.readValue(realmString, RealmRepresentation.class); + var exportedRealmRealm = exportedRealm.getRealm(); + logger.info("Exporting realm {}", exportedRealmRealm); + RealmRepresentation baselineRealm; + try (var defaultRealmIs = getClass() + .getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportVersion))) { + if (defaultRealmIs == null) { + logger.error("Reference realm for version {} does not exist", exportVersion); + return; } /* - * Trick javers into thinking this is the "same" object, by setting the ID on the reference realm - * to the ID of the current realm. That way we only get actual changes, not a full list of changes - * including the "object removed" and "object added" changes + * Replace the placeholder with the realm name to import. This sets some internal values like role names, + * baseUrls and redirectUrls so that they don't get picked up as "changes" */ - logger.info("Exporting realm {}", exportedRealmRealm); - baselineRealm.setRealm(exportedRealm.getRealm()); - var minimizedRealm = new RealmRepresentation(); + var realmString = new String(defaultRealmIs.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, exportedRealmRealm); + baselineRealm = OBJECT_MAPPER.readValue(realmString, RealmRepresentation.class); + } + /* + * Trick javers into thinking this is the "same" object, by setting the ID on the reference realm + * to the ID of the current realm. That way we only get actual changes, not a full list of changes + * including the "object removed" and "object added" changes + */ + baselineRealm.setRealm(exportedRealm.getRealm()); + var minimizedRealm = new RealmRepresentation(); - handleBaseRealm(exportedRealm, baselineRealm, minimizedRealm); + handleBaseRealm(exportedRealm, baselineRealm, minimizedRealm); - var clients = getMinimizedClients(exportedRealm, baselineRealm); - if (!clients.isEmpty()) { - minimizedRealm.setClients(clients); - } + var clients = getMinimizedClients(exportedRealm, baselineRealm); + if (!clients.isEmpty()) { + minimizedRealm.setClients(clients); + } - // No setter for some reason... - var minimizedScopeMappings = getMinimizedScopeMappings(exportedRealm, baselineRealm); - if (!minimizedScopeMappings.isEmpty()) { - var scopeMappings = minimizedRealm.getScopeMappings(); - if (scopeMappings == null) { - minimizedRealm.clientScopeMapping("dummy"); - scopeMappings = minimizedRealm.getScopeMappings(); - scopeMappings.clear(); - } - scopeMappings.addAll(getMinimizedScopeMappings(exportedRealm, baselineRealm)); + // No setter for some reason... + var minimizedScopeMappings = getMinimizedScopeMappings(exportedRealm, baselineRealm); + if (!minimizedScopeMappings.isEmpty()) { + var scopeMappings = minimizedRealm.getScopeMappings(); + if (scopeMappings == null) { + minimizedRealm.clientScopeMapping("dummy"); + scopeMappings = minimizedRealm.getScopeMappings(); + scopeMappings.clear(); } + scopeMappings.addAll(getMinimizedScopeMappings(exportedRealm, baselineRealm)); + } - var clientScopeMappings = getMinimizedClientScopeMappings(exportedRealm, baselineRealm); - if (!clientScopeMappings.isEmpty()) { - minimizedRealm.setClientScopeMappings(clientScopeMappings); - } + var clientScopeMappings = getMinimizedClientScopeMappings(exportedRealm, baselineRealm); + if (!clientScopeMappings.isEmpty()) { + minimizedRealm.setClientScopeMappings(clientScopeMappings); + } - var outputFile = Paths.get(exportConfigProperties.getLocation(), "out", String.format("%s.yaml", exportedRealmRealm)); - try (var os = new FileOutputStream(outputFile.toFile())) { - YAML_MAPPER.writeValue(os, minimizedRealm); - } + var outputFile = outputLocation.resolve(String.format("%s.yaml", exportedRealmRealm)); + + try (var os = new FileOutputStream(outputFile.toFile())) { + YAML_MAPPER.writeValue(os, minimizedRealm); } } @@ -210,7 +208,7 @@ private List getMinimizedClients(RealmRepresentation expor } if (clientChanged(baselineRealmClient, exportedClient)) { // We know the client has changed in some way. Now, compare it to a default client to minimize it - clients.add(getMinimizedClient(exportedClient, clientId)); + clients.add(getMinimizedClient(exportedClient, clientId, exportedRealm.getKeycloakVersion())); } } @@ -218,15 +216,15 @@ private List getMinimizedClients(RealmRepresentation expor for (Map.Entry e : exportedClientMap.entrySet()) { var clientId = e.getKey(); if (!baselineClientMap.containsKey(clientId)) { - clients.add(getMinimizedClient(e.getValue(), clientId)); + clients.add(getMinimizedClient(e.getValue(), clientId, exportedRealm.getKeycloakVersion())); } } return clients; } - private ClientRepresentation getMinimizedClient(ClientRepresentation exportedClient, String clientId) + private ClientRepresentation getMinimizedClient(ClientRepresentation exportedClient, String clientId, String keycloakVersion) throws IOException, NoSuchFieldException, IllegalAccessException { - var baselineClient = getBaselineClient(clientId); + var baselineClient = getBaselineClient(clientId, keycloakVersion); var clientDiff = JAVERS.compare(baselineClient, exportedClient); var minimizedClient = new ClientRepresentation(); for (var change : clientDiff.getChangesByType(PropertyChange.class)) { @@ -354,9 +352,9 @@ private boolean authorizationSettingsChanged(ResourceServerRepresentation defaul return JAVERS.compare(defaultSettings, exportedSettings).hasChanges(); } - private ClientRepresentation getBaselineClient(String clientId) throws IOException { + private ClientRepresentation getBaselineClient(String clientId, String keycloakVersion) throws IOException { try (var is = getClass() - .getResourceAsStream(String.format("/reference-realms/%s/client.json", exportConfigProperties.getKeycloakVersion()))) { + .getResourceAsStream(String.format("/reference-realms/%s/client.json", keycloakVersion))) { var client = OBJECT_MAPPER.readValue(is, ClientRepresentation.class); client.setClientId(clientId); return client; diff --git a/src/main/resources/application-export-dev.properties b/src/main/resources/application-export-dev.properties index 0a256c449..662ca0d0a 100644 --- a/src/main/resources/application-export-dev.properties +++ b/src/main/resources/application-export-dev.properties @@ -1,5 +1,6 @@ +spring.output.ansi.enabled=ALWAYS +spring.config.import=classpath:application-debug.properties + run.operation=EXPORT -export.enabled=true -export.excludes=fgvsad,master -export.keycloak-version=19.0.3 -export.location=./exports +export.files.input-locations=./exports/in/realm.json +export.files.output-directory=./exports/out From 6e94f1452f64c204b00ad59de367bd41d9ebe1ee Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 30 Nov 2022 12:18:08 +0100 Subject: [PATCH 09/49] Export -> Normalization --- ...=> KeycloakConfigNormalizationRunner.java} | 21 +++++++++---------- ...ava => NormalizationConfigProperties.java} | 20 +++++++++--------- .../properties/RunConfigProperties.java | 2 +- .../provider/KeycloakExportProvider.java | 14 ++++++------- ...ce.java => RealmNormalizationService.java} | 16 +++++++------- .../application-export-dev.properties | 6 +++--- 6 files changed, 39 insertions(+), 40 deletions(-) rename src/main/java/de/adorsys/keycloak/config/{KeycloakConfigExportRunner.java => KeycloakConfigNormalizationRunner.java} (80%) rename src/main/java/de/adorsys/keycloak/config/properties/{ExportConfigProperties.java => NormalizationConfigProperties.java} (74%) rename src/main/java/de/adorsys/keycloak/config/service/export/{RealmExportService.java => RealmNormalizationService.java} (96%) diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java similarity index 80% rename from src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java rename to src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java index 6c63bef51..3986d4ecf 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigExportRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java @@ -20,9 +20,9 @@ package de.adorsys.keycloak.config; -import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import de.adorsys.keycloak.config.provider.KeycloakExportProvider; -import de.adorsys.keycloak.config.service.export.RealmExportService; +import de.adorsys.keycloak.config.service.export.RealmNormalizationService; import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,33 +39,32 @@ import java.util.Map; @Component -@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "EXPORT") -@EnableConfigurationProperties(ExportConfigProperties.class) -public class KeycloakConfigExportRunner implements CommandLineRunner, ExitCodeGenerator { +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +@EnableConfigurationProperties(NormalizationConfigProperties.class) +public class KeycloakConfigNormalizationRunner implements CommandLineRunner, ExitCodeGenerator { - private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigExportRunner.class); + private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigNormalizationRunner.class); private static final long START_TIME = System.currentTimeMillis(); - private final RealmExportService exportService; + private final RealmNormalizationService normalizationService; private final KeycloakExportProvider exportProvider; private int exitCode; @Autowired - public KeycloakConfigExportRunner(RealmExportService exportService, KeycloakExportProvider exportProvider) { - this.exportService = exportService; + public KeycloakConfigNormalizationRunner(RealmNormalizationService normalizationService, KeycloakExportProvider exportProvider) { + this.normalizationService = normalizationService; this.exportProvider = exportProvider; } @Override public void run(String... args) throws Exception { try { - //exportService.doExports(); for (Map> exportLocations : exportProvider.readFromLocations().values()) { for (Map.Entry> export : exportLocations.entrySet()) { logger.info("Normalizing file '{}'", export.getKey()); for (RealmRepresentation realm : export.getValue()) { - exportService.doExport(realm); + normalizationService.normalize(realm); } } } diff --git a/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java similarity index 74% rename from src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java rename to src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java index 5edfe2860..e3475b441 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/ExportConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java @@ -29,23 +29,23 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; -@ConfigurationProperties(prefix = "export", ignoreUnknownFields = false) +@ConfigurationProperties(prefix = "normalization", ignoreUnknownFields = false) @ConstructorBinding @Validated -public class ExportConfigProperties { +public class NormalizationConfigProperties { @Valid - private final ExportFilesProperties files; + private final NormalizationFilesProperties files; - public ExportConfigProperties(@DefaultValue ExportFilesProperties files) { + public NormalizationConfigProperties(@DefaultValue NormalizationFilesProperties files) { this.files = files; } - public ExportFilesProperties getFiles() { + public NormalizationFilesProperties getFiles() { return files; } - public static class ExportFilesProperties { + public static class NormalizationFilesProperties { @NotNull private final Collection inputLocations; @@ -59,10 +59,10 @@ public static class ExportFilesProperties { @NotNull private final String outputDirectory; - public ExportFilesProperties(Collection inputLocations, - @DefaultValue("") Collection excludes, - @DefaultValue("false") boolean includeHiddenFiles, - String outputDirectory) { + public NormalizationFilesProperties(Collection inputLocations, + @DefaultValue("") Collection excludes, + @DefaultValue("false") boolean includeHiddenFiles, + String outputDirectory) { this.inputLocations = inputLocations; this.excludes = excludes; this.includeHiddenFiles = includeHiddenFiles; diff --git a/src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java index 9a23c1f0e..c7e07056e 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/RunConfigProperties.java @@ -41,6 +41,6 @@ public Operation getOperation() { } public enum Operation { - IMPORT, EXPORT + IMPORT, NORMALIZE } } diff --git a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java index 19ef236cd..c5e7ea784 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.adorsys.keycloak.config.exception.InvalidImportException; import de.adorsys.keycloak.config.model.ImportResource; -import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.keycloak.representations.idm.RealmRepresentation; @@ -60,19 +60,19 @@ public class KeycloakExportProvider { private final PathMatchingResourcePatternResolver patternResolver; - private final ExportConfigProperties exportConfigProperties; + private final NormalizationConfigProperties normalizationConfigProperties; @Autowired public KeycloakExportProvider(PathMatchingResourcePatternResolver patternResolver, - ExportConfigProperties exportConfigProperties) { + NormalizationConfigProperties normalizationConfigProperties) { this.patternResolver = patternResolver; - this.exportConfigProperties = exportConfigProperties; + this.normalizationConfigProperties = normalizationConfigProperties; } public Map>> readFromLocations() { Map>> files = new LinkedHashMap<>(); - for (String location : exportConfigProperties.getFiles().getInputLocations()) { + for (String location : normalizationConfigProperties.getFiles().getInputLocations()) { logger.debug("Loading file location '{}'", location); String resourceLocation = prepareResourceLocation(location); @@ -159,12 +159,12 @@ private boolean filterExcludedResources(Resource resource) { return false; } - if (!this.exportConfigProperties.getFiles().isIncludeHiddenFiles() && (file.isHidden() || FileUtils.hasHiddenAncestorDirectory(file))) { + if (!this.normalizationConfigProperties.getFiles().isIncludeHiddenFiles() && (file.isHidden() || FileUtils.hasHiddenAncestorDirectory(file))) { return false; } PathMatcher pathMatcher = patternResolver.getPathMatcher(); - return exportConfigProperties.getFiles().getExcludes() + return normalizationConfigProperties.getFiles().getExcludes() .stream() .map(pattern -> pattern.startsWith("**") ? "/" + pattern : pattern) .map(pattern -> !pattern.startsWith("/**") ? "/**" + pattern : pattern) diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java similarity index 96% rename from src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java rename to src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java index 067da4aa7..18d11d188 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmExportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import de.adorsys.keycloak.config.KeycloakConfigRunner; -import de.adorsys.keycloak.config.properties.ExportConfigProperties; +import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; import org.javers.core.Javers; import org.javers.core.JaversBuilder; @@ -49,7 +49,7 @@ import java.util.*; @Service -public class RealmExportService { +public class RealmNormalizationService { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -96,13 +96,13 @@ public class RealmExportService { .build(); } - private final ExportConfigProperties exportConfigProperties; + private final NormalizationConfigProperties normalizationConfigProperties; private final KeycloakConfigProperties keycloakConfigProperties; @Autowired - public RealmExportService(ExportConfigProperties exportConfigProperties, - KeycloakConfigProperties keycloakConfigProperties) { - this.exportConfigProperties = exportConfigProperties; + public RealmNormalizationService(NormalizationConfigProperties normalizationConfigProperties, + KeycloakConfigProperties keycloakConfigProperties) { + this.normalizationConfigProperties = normalizationConfigProperties; this.keycloakConfigProperties = keycloakConfigProperties; // TODO allow extra "default" values to be ignored? @@ -110,8 +110,8 @@ public RealmExportService(ExportConfigProperties exportConfigProperties, // TODO Ignore clients by regex } - public void doExport(RealmRepresentation exportedRealm) throws Exception { - var outputLocation = Paths.get(exportConfigProperties.getFiles().getOutputDirectory()); + public void normalize(RealmRepresentation exportedRealm) throws Exception { + var outputLocation = Paths.get(normalizationConfigProperties.getFiles().getOutputDirectory()); if (!Files.exists(outputLocation)) { logger.info("Creating output directory '{}'", outputLocation); Files.createDirectories(outputLocation); diff --git a/src/main/resources/application-export-dev.properties b/src/main/resources/application-export-dev.properties index 662ca0d0a..477367e1e 100644 --- a/src/main/resources/application-export-dev.properties +++ b/src/main/resources/application-export-dev.properties @@ -1,6 +1,6 @@ spring.output.ansi.enabled=ALWAYS spring.config.import=classpath:application-debug.properties -run.operation=EXPORT -export.files.input-locations=./exports/in/realm.json -export.files.output-directory=./exports/out +run.operation=NORMALIZE +normalization.files.input-locations=./exports/in/realm.json +normalization.files.output-directory=./exports/out From f0e15a8a70d44d42e17bd004d0960f643c1493db Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Fri, 2 Dec 2022 14:38:59 +0100 Subject: [PATCH 10/49] Normalize: Filter attributes --- docs/FEATURES.md | 1 + docs/NORMALIZE.md | 6 +++ .../export/RealmNormalizationService.java | 39 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 docs/NORMALIZE.md diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 1c5a7bb4e..ede3aa835 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -55,6 +55,7 @@ | Remove clientScopeMappings | 2.5.0 | Remove existing clientScopeMappings while creating or updating realms | | Synchronize user federation | 3.5.0 | Synchronize the user federation defined on the realm configuration | | Synchronize user profile | 5.4.0 | Synchronize the user profile configuration defined on the realm configuration | +| Normalize realm exports | 5.5.0 | Normalize a full realm export to be more minimal | # Specificities diff --git a/docs/NORMALIZE.md b/docs/NORMALIZE.md new file mode 100644 index 000000000..b34aa925e --- /dev/null +++ b/docs/NORMALIZE.md @@ -0,0 +1,6 @@ +# Realm normalization + + + +To run the normalization, run keycloak-config-cli with the CLI option `--run.operation=NORMALIZE`. +The default value for this option is `IMPORT`, which will run the regular keycloak-config-cli import. diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java index 18d11d188..b6086030a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java @@ -86,6 +86,7 @@ public class RealmNormalizationService { realmIgnoredProperties.add("applications"); realmIgnoredProperties.add("oauthClients"); realmIgnoredProperties.add("clientTemplates"); + realmIgnoredProperties.add("attributes"); JAVERS = JaversBuilder.javers() .registerEntity(new EntityDefinition(RealmRepresentation.class, "realm", realmIgnoredProperties)) @@ -175,6 +176,17 @@ public void normalize(RealmRepresentation exportedRealm) throws Exception { minimizedRealm.setClientScopeMappings(clientScopeMappings); } + var attributes = getMinimizedAttributes(exportedRealm, baselineRealm); + if (!attributes.isEmpty()) { + minimizedRealm.setAttributes(attributes); + } + + var protocolMappers = getMinimizedProtocolMappers(exportedRealm.getProtocolMappers(), + baselineRealm.getProtocolMappers()); + if (!protocolMappers.isEmpty()) { + minimizedRealm.setProtocolMappers(protocolMappers); + } + var outputFile = outputLocation.resolve(String.format("%s.yaml", exportedRealmRealm)); try (var os = new FileOutputStream(outputFile.toFile())) { @@ -182,6 +194,33 @@ public void normalize(RealmRepresentation exportedRealm) throws Exception { } } + private Map getMinimizedAttributes(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { + var exportedAttributes = exportedRealm.getAttributesOrEmpty(); + var baselineAttributes = baselineRealm.getAttributesOrEmpty(); + var minimizedAttributes = new HashMap(); + + for (var entry : baselineAttributes.entrySet()) { + var key = entry.getKey(); + var exportedValue = exportedAttributes.get(key); + if (!Objects.equals(exportedValue, entry.getValue())) { + minimizedAttributes.put(key, exportedValue); + } + } + + for (var entry : exportedAttributes.entrySet()) { + var key = entry.getKey(); + if (!baselineAttributes.containsKey(key)) { + minimizedAttributes.put(key, entry.getValue()); + } + } + return minimizedAttributes; + } + + private List getMinimizedProtocolMappers(List exportedMappers, + List baselineMappers) { + return List.of(); + } + private List getMinimizedClients(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) throws IOException, NoSuchFieldException, IllegalAccessException { // Get a client map for better lookups From 04ed90bfbd168f6632b47b4330fdac86cff7daa8 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Fri, 2 Dec 2022 14:50:39 +0100 Subject: [PATCH 11/49] Fix checkstyle warnings --- .../keycloak/config/provider/KeycloakExportProvider.java | 3 ++- .../config/service/export/RealmNormalizationService.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java index c5e7ea784..7898be692 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java @@ -159,7 +159,8 @@ private boolean filterExcludedResources(Resource resource) { return false; } - if (!this.normalizationConfigProperties.getFiles().isIncludeHiddenFiles() && (file.isHidden() || FileUtils.hasHiddenAncestorDirectory(file))) { + if (!this.normalizationConfigProperties.getFiles().isIncludeHiddenFiles() + && (file.isHidden() || FileUtils.hasHiddenAncestorDirectory(file))) { return false; } diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java index b6086030a..57d2e8c6e 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java @@ -24,8 +24,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import de.adorsys.keycloak.config.KeycloakConfigRunner; -import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; +import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import org.javers.core.Javers; import org.javers.core.JaversBuilder; import org.javers.core.diff.ListCompareAlgorithm; From 73b622ded74501dd839887d37285ac277733a5d3 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 5 Dec 2022 13:36:22 +0100 Subject: [PATCH 12/49] Properly ignore some more configuration parameters --- .../config/KeycloakConfigApplication.java | 3 +- .../KeycloakConfigNormalizationRunner.java | 5 +- .../keycloak/config/KeycloakConfigRunner.java | 3 +- .../properties/KeycloakConfigProperties.java | 26 +++++----- ...NormalizationKeycloakConfigProperties.java | 49 +++++++++++++++++++ .../provider/KeycloakExportProvider.java | 2 + .../RealmNormalizationService.java | 10 ++-- src/main/resources/application.properties | 11 ----- 8 files changed, 78 insertions(+), 31 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/properties/NormalizationKeycloakConfigProperties.java rename src/main/java/de/adorsys/keycloak/config/service/{export => normalize}/RealmNormalizationService.java (97%) diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java index 2103d2901..f17777978 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigApplication.java @@ -20,14 +20,13 @@ package de.adorsys.keycloak.config; -import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; import de.adorsys.keycloak.config.properties.RunConfigProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication(proxyBeanMethods = false) -@EnableConfigurationProperties({KeycloakConfigProperties.class, RunConfigProperties.class}) +@EnableConfigurationProperties(RunConfigProperties.class) public class KeycloakConfigApplication { public static void main(String[] args) { // https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java index 3986d4ecf..5a5f90344 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java @@ -21,8 +21,9 @@ package de.adorsys.keycloak.config; import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; +import de.adorsys.keycloak.config.properties.NormalizationKeycloakConfigProperties; import de.adorsys.keycloak.config.provider.KeycloakExportProvider; -import de.adorsys.keycloak.config.service.export.RealmNormalizationService; +import de.adorsys.keycloak.config.service.normalize.RealmNormalizationService; import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +41,7 @@ @Component @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") -@EnableConfigurationProperties(NormalizationConfigProperties.class) +@EnableConfigurationProperties({NormalizationConfigProperties.class, NormalizationKeycloakConfigProperties.class}) public class KeycloakConfigNormalizationRunner implements CommandLineRunner, ExitCodeGenerator { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigNormalizationRunner.class); diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java index 77c80b645..f482847ca 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigRunner.java @@ -23,6 +23,7 @@ import de.adorsys.keycloak.config.model.KeycloakImport; import de.adorsys.keycloak.config.model.RealmImport; import de.adorsys.keycloak.config.properties.ImportConfigProperties; +import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; import de.adorsys.keycloak.config.provider.KeycloakImportProvider; import de.adorsys.keycloak.config.service.RealmImportService; import org.slf4j.Logger; @@ -47,7 +48,7 @@ * for backwards compatibility */ @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) -@EnableConfigurationProperties(ImportConfigProperties.class) +@EnableConfigurationProperties({ImportConfigProperties.class, KeycloakConfigProperties.class}) public class KeycloakConfigRunner implements CommandLineRunner, ExitCodeGenerator { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); private static final long START_TIME = System.currentTimeMillis(); diff --git a/src/main/java/de/adorsys/keycloak/config/properties/KeycloakConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/KeycloakConfigProperties.java index bf9e907fa..60470b3f0 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/KeycloakConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/KeycloakConfigProperties.java @@ -22,6 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.validation.annotation.Validated; import java.net.URL; @@ -70,18 +71,19 @@ public class KeycloakConfigProperties { private final KeycloakAvailabilityCheck availabilityCheck; public KeycloakConfigProperties( - String loginRealm, - String clientId, - String version, URL url, - String user, + @DefaultValue("master") String loginRealm, + @DefaultValue("admin-cli") String clientId, + String version, + URL url, + @DefaultValue("admin") String user, String password, - String clientSecret, - String grantType, - boolean sslVerify, + @DefaultValue("") String clientSecret, + @DefaultValue("password") String grantType, + @DefaultValue("true") boolean sslVerify, URL httpProxy, - KeycloakAvailabilityCheck availabilityCheck, - Duration connectTimeout, - Duration readTimeout + @DefaultValue KeycloakAvailabilityCheck availabilityCheck, + @DefaultValue("10s") Duration connectTimeout, + @DefaultValue("10s") Duration readTimeout ) { this.loginRealm = loginRealm; this.clientId = clientId; @@ -161,7 +163,9 @@ public static class KeycloakAvailabilityCheck { private final Duration retryDelay; @SuppressWarnings("unused") - public KeycloakAvailabilityCheck(boolean enabled, Duration timeout, Duration retryDelay) { + public KeycloakAvailabilityCheck(@DefaultValue("false") boolean enabled, + @DefaultValue("120s") Duration timeout, + @DefaultValue("2s") Duration retryDelay) { this.enabled = enabled; this.timeout = timeout; this.retryDelay = retryDelay; diff --git a/src/main/java/de/adorsys/keycloak/config/properties/NormalizationKeycloakConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationKeycloakConfigProperties.java new file mode 100644 index 000000000..e618be0ec --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationKeycloakConfigProperties.java @@ -0,0 +1,49 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotNull; + +/* + * Duplicated prefix keycloak. Since only one of the two classes is loaded (depending on configuration) this is fine. + * This saves us from having to define a keycloak address for the normalization usage, since we don't actually need to + * talk to a keycloak instance, and we only need to know the version. + */ +@ConfigurationProperties(prefix = "keycloak", ignoreUnknownFields = false) +@ConstructorBinding +@Validated +public class NormalizationKeycloakConfigProperties { + + @NotNull + private final String version; + + public NormalizationKeycloakConfigProperties(String version) { + this.version = version; + } + + public String getVersion() { + return version; + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java index 7898be692..c64c00a2e 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakExportProvider.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -51,6 +52,7 @@ * This class heavily copy pastes code from KeycloakImportProvider. This can probably be reduced quite a bit by moving some code out to a shared class */ @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class KeycloakExportProvider { private static final Logger logger = LoggerFactory.getLogger(KeycloakExportProvider.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java similarity index 97% rename from src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java rename to src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 57d2e8c6e..14246371a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/export/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -18,14 +18,14 @@ * ---license-end */ -package de.adorsys.keycloak.config.service.export; +package de.adorsys.keycloak.config.service.normalize; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import de.adorsys.keycloak.config.KeycloakConfigRunner; -import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; +import de.adorsys.keycloak.config.properties.NormalizationKeycloakConfigProperties; import org.javers.core.Javers; import org.javers.core.JaversBuilder; import org.javers.core.diff.ListCompareAlgorithm; @@ -39,6 +39,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.io.FileOutputStream; @@ -49,6 +50,7 @@ import java.util.*; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class RealmNormalizationService { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); @@ -98,11 +100,11 @@ public class RealmNormalizationService { } private final NormalizationConfigProperties normalizationConfigProperties; - private final KeycloakConfigProperties keycloakConfigProperties; + private final NormalizationKeycloakConfigProperties keycloakConfigProperties; @Autowired public RealmNormalizationService(NormalizationConfigProperties normalizationConfigProperties, - KeycloakConfigProperties keycloakConfigProperties) { + NormalizationKeycloakConfigProperties keycloakConfigProperties) { this.normalizationConfigProperties = normalizationConfigProperties; this.keycloakConfigProperties = keycloakConfigProperties; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b391d314e..57a0c6fb3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,17 +6,6 @@ spring.main.lazy-initialization=true spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration keycloak.version=@keycloak.version@ -keycloak.login-realm=master -keycloak.user=admin -keycloak.client-secret= -keycloak.grant-type=password -keycloak.client-id=admin-cli -keycloak.ssl-verify=true -keycloak.connect-timeout=10s -keycloak.read-timeout=10s -keycloak.availability-check.enabled=false -keycloak.availability-check.timeout=120s -keycloak.availability-check.retry-delay=2s logging.group.http=org.apache.http.wire logging.group.realm-config=de.adorsys.keycloak.config.provider.KeycloakImportProvider From dc982396da78f79e24a6d1a5d9f2f8861609ab0c Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 5 Dec 2022 14:13:40 +0100 Subject: [PATCH 13/49] Bump to Spring Boot 2.7.5 --- .../keycloak/config/properties/ImportConfigProperties.java | 2 +- .../config/properties/NormalizationConfigProperties.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java index 1f89f7ca6..b6346c6d0 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java @@ -252,7 +252,7 @@ public static class ImportFilesProperties { private final boolean includeHiddenFiles; public ImportFilesProperties(Collection locations, - @DefaultValue("") Collection excludes, + @DefaultValue Collection excludes, @DefaultValue("false") boolean includeHiddenFiles) { this.locations = locations; this.excludes = excludes; diff --git a/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java index e3475b441..4d9925df4 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java @@ -60,7 +60,7 @@ public static class NormalizationFilesProperties { private final String outputDirectory; public NormalizationFilesProperties(Collection inputLocations, - @DefaultValue("") Collection excludes, + @DefaultValue Collection excludes, @DefaultValue("false") boolean includeHiddenFiles, String outputDirectory) { this.inputLocations = inputLocations; From 72e3c4fa1ffb2d04f65c58ec4d04ab8d3c7f6824 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 5 Dec 2022 15:09:09 +0100 Subject: [PATCH 14/49] Rename export profile to normalize --- ...export-dev.properties => application-normalize-dev.properties} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/{application-export-dev.properties => application-normalize-dev.properties} (100%) diff --git a/src/main/resources/application-export-dev.properties b/src/main/resources/application-normalize-dev.properties similarity index 100% rename from src/main/resources/application-export-dev.properties rename to src/main/resources/application-normalize-dev.properties From e041d08a5626245e3818c5764ccaccff4b6472af Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 5 Dec 2022 17:17:22 +0100 Subject: [PATCH 15/49] Prepare for testing --- pom.xml | 8 ++++ ...edAuthenticationFlowWorkaroundFactory.java | 2 + .../model/AuthenticationFlowImport.java | 2 + .../keycloak/config/model/RealmImport.java | 2 + .../provider/KeycloakImportProvider.java | 2 + .../config/provider/KeycloakProvider.java | 2 + .../AuthenticationFlowRepository.java | 2 + .../AuthenticatorConfigRepository.java | 2 + .../config/repository/ClientRepository.java | 2 + .../repository/ClientScopeRepository.java | 2 + .../repository/ComponentRepository.java | 2 + .../repository/ExecutionFlowRepository.java | 2 + .../config/repository/GroupRepository.java | 2 + .../IdentityProviderMapperRepository.java | 2 + .../IdentityProviderRepository.java | 2 + .../config/repository/RealmRepository.java | 2 + .../repository/RequiredActionRepository.java | 2 + .../repository/RoleCompositeRepository.java | 2 + .../config/repository/RoleRepository.java | 2 + .../repository/ScopeMappingRepository.java | 2 + .../config/repository/StateRepository.java | 2 + .../repository/UserProfileRepository.java | 2 + .../config/repository/UserRepository.java | 2 + .../AuthenticationFlowsImportService.java | 2 + .../AuthenticatorConfigImportService.java | 2 + .../ClientAuthorizationImportService.java | 2 + .../config/service/ClientImportService.java | 2 + .../service/ClientScopeImportService.java | 2 + .../ClientScopeMappingImportService.java | 2 + .../service/ComponentImportService.java | 2 + .../service/DefaultGroupsImportService.java | 2 + .../service/ExecutionFlowsImportService.java | 2 + .../config/service/GroupImportService.java | 2 + .../IdentityProviderImportService.java | 2 + .../config/service/RealmImportService.java | 2 + .../service/RequiredActionsImportService.java | 2 + .../config/service/RoleImportService.java | 2 + .../service/ScopeMappingImportService.java | 2 + .../config/service/UserImportService.java | 2 + .../service/UserProfileImportService.java | 2 + .../service/checksum/ChecksumService.java | 2 + .../normalize/RealmNormalizationService.java | 5 +++ .../client/ClientCompositeImport.java | 2 + .../ClientRoleCompositeImportService.java | 2 + .../client/RealmCompositeImport.java | 2 + .../realm/ClientCompositeImport.java | 2 + .../realm/RealmCompositeImport.java | 2 + .../RealmRoleCompositeImportService.java | 2 + .../config/service/state/StateService.java | 2 + .../NormalizeTestConfiguration.java | 35 +++++++++++++++ .../configuration/TestConfiguration.java | 2 + .../normalize/AbstractNormalizeTest.java | 45 +++++++++++++++++++ .../keycloak/config/normalize/DummyTest.java | 31 +++++++++++++ .../test/util/KeycloakAuthentication.java | 2 + .../config/test/util/KeycloakRepository.java | 2 + .../application-normalize-IT.properties | 3 ++ 56 files changed, 227 insertions(+) create mode 100644 src/test/java/de/adorsys/keycloak/config/configuration/NormalizeTestConfiguration.java create mode 100644 src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java create mode 100644 src/test/java/de/adorsys/keycloak/config/normalize/DummyTest.java create mode 100644 src/test/resources/application-normalize-IT.properties diff --git a/pom.xml b/pom.xml index e129d6246..8023ce41e 100644 --- a/pom.xml +++ b/pom.xml @@ -251,6 +251,14 @@ snakeyaml + + com.github.spotbugs + spotbugs-annotations + ${spotbugs.version} + provided + + + net.logstash.logback diff --git a/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java b/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java index 4b9252936..911fc53a1 100644 --- a/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java +++ b/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java @@ -31,11 +31,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.*; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class UsedAuthenticationFlowWorkaroundFactory { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/model/AuthenticationFlowImport.java b/src/main/java/de/adorsys/keycloak/config/model/AuthenticationFlowImport.java index 2dba38141..9f4de757f 100644 --- a/src/main/java/de/adorsys/keycloak/config/model/AuthenticationFlowImport.java +++ b/src/main/java/de/adorsys/keycloak/config/model/AuthenticationFlowImport.java @@ -22,6 +22,7 @@ import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.io.Serializable; @@ -33,6 +34,7 @@ */ @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class AuthenticationFlowImport extends AuthenticationFlowRepresentation { private static final Comparator COMPARATOR = new AuthenticationExecutionExportRepresentationComparator(); diff --git a/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java b/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java index 2d4666b35..1b718572e 100644 --- a/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java +++ b/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonSetter; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -31,6 +32,7 @@ import java.util.List; @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RealmImport extends RealmRepresentation { private List authenticationFlowImports; diff --git a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java index 24dd458e1..d362eb382 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java @@ -36,6 +36,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; @@ -54,6 +55,7 @@ import java.util.stream.Collectors; @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class KeycloakImportProvider { private final PathMatchingResourcePatternResolver patternResolver; private final ImportConfigProperties importConfigProperties; diff --git a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakProvider.java index 13782648f..e6a008422 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakProvider.java @@ -33,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.net.URI; @@ -50,6 +51,7 @@ * to avoid a deadlock. */ @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class KeycloakProvider implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(KeycloakProvider.class); diff --git a/src/main/java/de/adorsys/keycloak/config/repository/AuthenticationFlowRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/AuthenticationFlowRepository.java index 01eb48509..440acd8ac 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/AuthenticationFlowRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/AuthenticationFlowRepository.java @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -44,6 +45,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class AuthenticationFlowRepository { private static final Logger logger = LoggerFactory.getLogger(AuthenticationFlowRepository.class); diff --git a/src/main/java/de/adorsys/keycloak/config/repository/AuthenticatorConfigRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/AuthenticatorConfigRepository.java index 7aeda05d1..c3b0921be 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/AuthenticatorConfigRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/AuthenticatorConfigRepository.java @@ -24,6 +24,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -31,6 +32,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class AuthenticatorConfigRepository { private final AuthenticationFlowRepository authenticationFlowRepository; private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java index 2116a393f..db9857111 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/ClientRepository.java @@ -35,6 +35,7 @@ import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -46,6 +47,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ClientScopeRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ClientScopeRepository.java index e70a46100..6bf4b7d57 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/ClientScopeRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/ClientScopeRepository.java @@ -29,6 +29,7 @@ import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -40,6 +41,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientScopeRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java index 25957dbe6..9efd9146b 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java @@ -28,6 +28,7 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.representations.idm.ComponentRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.Collections; @@ -38,6 +39,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ComponentRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ExecutionFlowRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ExecutionFlowRepository.java index ad429c4e7..d28f0a679 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/ExecutionFlowRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/ExecutionFlowRepository.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -41,6 +42,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ExecutionFlowRepository { private static final Logger logger = LoggerFactory.getLogger(ExecutionFlowRepository.class); diff --git a/src/main/java/de/adorsys/keycloak/config/repository/GroupRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/GroupRepository.java index 88628176f..129ae35ae 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/GroupRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/GroupRepository.java @@ -32,6 +32,7 @@ import org.keycloak.representations.idm.ManagementPermissionRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -42,6 +43,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class GroupRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderMapperRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderMapperRepository.java index ac71c2b65..03bb04743 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderMapperRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderMapperRepository.java @@ -26,6 +26,7 @@ import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -35,6 +36,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class IdentityProviderMapperRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderRepository.java index aa7301c3e..7363bb3d6 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/IdentityProviderRepository.java @@ -28,6 +28,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.ManagementPermissionRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -36,6 +37,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class IdentityProviderRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java index 13c28b85b..82aa70681 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/RealmRepository.java @@ -28,12 +28,14 @@ import org.keycloak.admin.client.resource.RealmsResource; import org.keycloak.representations.idm.RealmRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; import javax.ws.rs.WebApplicationException; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RealmRepository { private final KeycloakProvider keycloakProvider; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/RequiredActionRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/RequiredActionRepository.java index 4b55fb683..a3ade2570 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/RequiredActionRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/RequiredActionRepository.java @@ -25,6 +25,7 @@ import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -34,6 +35,7 @@ * Provides methods to retrieve and store required-actions in your realm */ @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RequiredActionRepository { private final AuthenticationFlowRepository authenticationFlowRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/RoleCompositeRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/RoleCompositeRepository.java index aacbf2901..ac8165089 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/RoleCompositeRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/RoleCompositeRepository.java @@ -26,6 +26,7 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.*; @@ -33,6 +34,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RoleCompositeRepository { private final RoleRepository roleRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/RoleRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/RoleRepository.java index 94833431c..253f9a58a 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/RoleRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/RoleRepository.java @@ -27,6 +27,7 @@ import org.keycloak.admin.client.resource.*; import org.keycloak.representations.idm.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; @@ -34,6 +35,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RoleRepository { private final RealmRepository realmRepository; private final ClientRepository clientRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ScopeMappingRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ScopeMappingRepository.java index a8e15154d..b420ae043 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/ScopeMappingRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/ScopeMappingRepository.java @@ -26,6 +26,7 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.ScopeMappingRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.Collection; @@ -34,6 +35,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ScopeMappingRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/StateRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/StateRepository.java index 212ab611f..38679f63f 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/StateRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/StateRepository.java @@ -24,6 +24,7 @@ import de.adorsys.keycloak.config.properties.ImportConfigProperties; import de.adorsys.keycloak.config.util.CryptoUtil; import org.keycloak.representations.idm.RealmRepresentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.text.MessageFormat; @@ -36,6 +37,7 @@ import static de.adorsys.keycloak.config.util.JsonUtil.toJson; @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class StateRepository { private static final int MAX_ATTRIBUTE_LENGTH = 250; diff --git a/src/main/java/de/adorsys/keycloak/config/repository/UserProfileRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/UserProfileRepository.java index ad2302e52..c546829b4 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/UserProfileRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/UserProfileRepository.java @@ -27,12 +27,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.util.Optional; import javax.ws.rs.core.Response; @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class UserProfileRepository { private static final Logger logger = LoggerFactory.getLogger(AuthenticationFlowRepository.class); diff --git a/src/main/java/de/adorsys/keycloak/config/repository/UserRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/UserRepository.java index e778751a2..35b21cdc1 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/UserRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/UserRepository.java @@ -28,6 +28,7 @@ import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -35,6 +36,7 @@ import javax.ws.rs.core.Response; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class UserRepository { private final RealmRepository realmRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java index 0649b0bd7..42e8a416c 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java @@ -35,6 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -51,6 +52,7 @@ * sub-flow: any flow which has the property 'topLevel' set to 'false' and which are related to execution-flows within topLevel-flows */ @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class AuthenticationFlowsImportService { private static final Logger logger = LoggerFactory.getLogger(AuthenticationFlowsImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/AuthenticatorConfigImportService.java b/src/main/java/de/adorsys/keycloak/config/service/AuthenticatorConfigImportService.java index e3fa9d846..e85812d18 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/AuthenticatorConfigImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/AuthenticatorConfigImportService.java @@ -29,6 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -40,6 +41,7 @@ import java.util.stream.Stream; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class AuthenticatorConfigImportService { private static final Logger logger = LoggerFactory.getLogger(AuthenticatorConfigImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/ClientAuthorizationImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ClientAuthorizationImportService.java index 1663b2b7d..194188888 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ClientAuthorizationImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ClientAuthorizationImportService.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -54,6 +55,7 @@ @Service @SuppressWarnings({"java:S1192"}) +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientAuthorizationImportService { private static final Logger logger = LoggerFactory.getLogger(ClientAuthorizationImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java index ea0c64b53..883f9eba6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ClientImportService.java @@ -35,6 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.*; @@ -47,6 +48,7 @@ @Service @SuppressWarnings({"java:S1192"}) +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientImportService { private static final Logger logger = LoggerFactory.getLogger(ClientImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/ClientScopeImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ClientScopeImportService.java index 98faafba4..145b781e6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ClientScopeImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ClientScopeImportService.java @@ -32,6 +32,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -41,6 +42,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientScopeImportService { private static final Logger logger = LoggerFactory.getLogger(ClientScopeImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/ClientScopeMappingImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ClientScopeMappingImportService.java index 5d223e9f7..dbe029f2a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ClientScopeMappingImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ClientScopeMappingImportService.java @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -42,6 +43,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientScopeMappingImportService { private static final Logger logger = LoggerFactory.getLogger(ClientScopeMappingImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java index 586283ab2..0d8bd3042 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java @@ -34,11 +34,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.*; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ComponentImportService { private static final Logger logger = LoggerFactory.getLogger(ComponentImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/DefaultGroupsImportService.java b/src/main/java/de/adorsys/keycloak/config/service/DefaultGroupsImportService.java index 8f7f0823b..43adbbbc5 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/DefaultGroupsImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/DefaultGroupsImportService.java @@ -26,11 +26,13 @@ import de.adorsys.keycloak.config.repository.RealmRepository; import org.keycloak.admin.client.resource.RealmResource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class DefaultGroupsImportService { private final RealmRepository realmRepository; private final GroupRepository groupRepository; diff --git a/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java index ae885891f..26ba23a3a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -44,6 +45,7 @@ * Imports executions and execution-flows of existing top-level flows */ @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ExecutionFlowsImportService { private static final Logger logger = LoggerFactory.getLogger(ExecutionFlowsImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/GroupImportService.java b/src/main/java/de/adorsys/keycloak/config/service/GroupImportService.java index c8334e8f8..df4ee4eaf 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/GroupImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/GroupImportService.java @@ -28,6 +28,7 @@ import org.keycloak.representations.idm.GroupRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.*; @@ -35,6 +36,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class GroupImportService { private static final Logger logger = LoggerFactory.getLogger(GroupImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/IdentityProviderImportService.java b/src/main/java/de/adorsys/keycloak/config/service/IdentityProviderImportService.java index 760340f6d..ebb93d5b7 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/IdentityProviderImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/IdentityProviderImportService.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -39,6 +40,7 @@ import static de.adorsys.keycloak.config.properties.ImportConfigProperties.ImportManagedProperties.ImportManagedPropertiesValues; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class IdentityProviderImportService { private static final Logger logger = LoggerFactory.getLogger(IdentityProviderImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java index ebb0543d2..2f52d5eb1 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java @@ -31,9 +31,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RealmImportService { static final String[] ignoredPropertiesForRealmImport = new String[]{ "clients", diff --git a/src/main/java/de/adorsys/keycloak/config/service/RequiredActionsImportService.java b/src/main/java/de/adorsys/keycloak/config/service/RequiredActionsImportService.java index 4ddd12893..021a67b72 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/RequiredActionsImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/RequiredActionsImportService.java @@ -30,6 +30,7 @@ import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -40,6 +41,7 @@ * Creates and updates required-actions in your realm */ @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RequiredActionsImportService { private static final Logger logger = LoggerFactory.getLogger(RequiredActionsImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/RoleImportService.java b/src/main/java/de/adorsys/keycloak/config/service/RoleImportService.java index d3ddf1051..f6771f3f9 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/RoleImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/RoleImportService.java @@ -34,6 +34,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -44,6 +45,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RoleImportService { private static final Logger logger = LoggerFactory.getLogger(RoleImportService.class); private static final String[] propertiesWithDependencies = new String[]{ diff --git a/src/main/java/de/adorsys/keycloak/config/service/ScopeMappingImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ScopeMappingImportService.java index efe6396fb..c6ebb221a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ScopeMappingImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ScopeMappingImportService.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -39,6 +40,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ScopeMappingImportService { private static final Logger logger = LoggerFactory.getLogger(ScopeMappingImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/UserImportService.java b/src/main/java/de/adorsys/keycloak/config/service/UserImportService.java index ecac23bd2..e80808a13 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/UserImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/UserImportService.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -38,6 +39,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class UserImportService { private static final Logger logger = LoggerFactory.getLogger(UserImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/UserProfileImportService.java b/src/main/java/de/adorsys/keycloak/config/service/UserProfileImportService.java index 8530dddbb..3f6197833 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/UserProfileImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/UserProfileImportService.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.LinkedHashMap; @@ -33,6 +34,7 @@ import java.util.Map; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class UserProfileImportService { private static final Logger logger = LoggerFactory.getLogger(UserProfileImportService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java b/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java index ab4525508..20dee4d7a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.text.MessageFormat; @@ -34,6 +35,7 @@ import java.util.Objects; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ChecksumService { private static final Logger logger = LoggerFactory.getLogger(ChecksumService.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 14246371a..1f0bd80ae 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -26,6 +26,7 @@ import de.adorsys.keycloak.config.KeycloakConfigRunner; import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import de.adorsys.keycloak.config.properties.NormalizationKeycloakConfigProperties; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.javers.core.Javers; import org.javers.core.JaversBuilder; import org.javers.core.diff.ListCompareAlgorithm; @@ -51,6 +52,8 @@ @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +@SuppressFBWarnings(value = {"NP_LOAD_OF_KNOWN_NULL_VALUE", "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"}, + justification = "Bug in Spotbugs, see https://github.com/spotbugs/spotbugs/issues/1338") public class RealmNormalizationService { private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); @@ -121,6 +124,7 @@ public void normalize(RealmRepresentation exportedRealm) throws Exception { } if (!Files.isDirectory(outputLocation)) { logger.error("Output location '{}' is not a directory. Aborting.", outputLocation); + return; } var keycloakConfigVersion = keycloakConfigProperties.getVersion(); var exportVersion = exportedRealm.getKeycloakVersion(); @@ -220,6 +224,7 @@ private Map getMinimizedAttributes(RealmRepresentation exportedR private List getMinimizedProtocolMappers(List exportedMappers, List baselineMappers) { + logger.info("Temporary measure so PMD shuts up {} {}", exportedMappers.size(), baselineMappers.size()); return List.of(); } diff --git a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientCompositeImport.java b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientCompositeImport.java index 977b902b0..28d7ac2dd 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientCompositeImport.java +++ b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientCompositeImport.java @@ -26,12 +26,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service("clientRoleClientCompositeImport") +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientCompositeImport { private static final Logger logger = LoggerFactory.getLogger(ClientCompositeImport.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientRoleCompositeImportService.java b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientRoleCompositeImportService.java index 2130abc0c..05b56fa43 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientRoleCompositeImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/ClientRoleCompositeImportService.java @@ -22,6 +22,7 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -32,6 +33,7 @@ * Implements the update mechanism for role composites of client-level roles */ @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientRoleCompositeImportService { private final RealmCompositeImport realmCompositeImport; diff --git a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/RealmCompositeImport.java b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/RealmCompositeImport.java index 1fe734179..126fd06eb 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/RealmCompositeImport.java +++ b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/client/RealmCompositeImport.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.Objects; @@ -32,6 +33,7 @@ import java.util.stream.Collectors; @Service("clientRoleRealmCompositeImport") +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RealmCompositeImport { private static final Logger logger = LoggerFactory.getLogger(RealmCompositeImport.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/ClientCompositeImport.java b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/ClientCompositeImport.java index 71dc650b0..a2564ed3a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/ClientCompositeImport.java +++ b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/ClientCompositeImport.java @@ -25,12 +25,14 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service("realmRoleClientCompositeImport") +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class ClientCompositeImport { private static final Logger logger = LoggerFactory.getLogger(ClientCompositeImport.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmCompositeImport.java b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmCompositeImport.java index e7eb4f7ac..1cb9aa31d 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmCompositeImport.java +++ b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmCompositeImport.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.Objects; @@ -32,6 +33,7 @@ import java.util.stream.Collectors; @Service("realmRoleRealmCompositeImport") +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RealmCompositeImport { private static final Logger logger = LoggerFactory.getLogger(RealmCompositeImport.class); diff --git a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmRoleCompositeImportService.java b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmRoleCompositeImportService.java index 5292af18c..b3d484e86 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmRoleCompositeImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/rolecomposites/realm/RealmRoleCompositeImportService.java @@ -22,6 +22,7 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.List; @@ -31,6 +32,7 @@ * Implements the update mechanism for role composites of realm-level roles */ @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class RealmRoleCompositeImportService { private final RealmCompositeImport realmCompositeImport; diff --git a/src/main/java/de/adorsys/keycloak/config/service/state/StateService.java b/src/main/java/de/adorsys/keycloak/config/service/state/StateService.java index 6582e4adc..771b614dc 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/state/StateService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/state/StateService.java @@ -29,6 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -37,6 +38,7 @@ import java.util.stream.Collectors; @Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class StateService { private static final Logger logger = LoggerFactory.getLogger(StateService.class); diff --git a/src/test/java/de/adorsys/keycloak/config/configuration/NormalizeTestConfiguration.java b/src/test/java/de/adorsys/keycloak/config/configuration/NormalizeTestConfiguration.java new file mode 100644 index 000000000..a27f1e142 --- /dev/null +++ b/src/test/java/de/adorsys/keycloak/config/configuration/NormalizeTestConfiguration.java @@ -0,0 +1,35 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.configuration; + +import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; +import de.adorsys.keycloak.config.properties.NormalizationKeycloakConfigProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = {"de.adorsys.keycloak.config"}) +@EnableConfigurationProperties({NormalizationKeycloakConfigProperties.class, NormalizationConfigProperties.class}) +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class NormalizeTestConfiguration { +} diff --git a/src/test/java/de/adorsys/keycloak/config/configuration/TestConfiguration.java b/src/test/java/de/adorsys/keycloak/config/configuration/TestConfiguration.java index 27685c07a..258f4b868 100644 --- a/src/test/java/de/adorsys/keycloak/config/configuration/TestConfiguration.java +++ b/src/test/java/de/adorsys/keycloak/config/configuration/TestConfiguration.java @@ -22,6 +22,7 @@ import de.adorsys.keycloak.config.properties.ImportConfigProperties; import de.adorsys.keycloak.config.properties.KeycloakConfigProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -29,6 +30,7 @@ @Configuration @ComponentScan(basePackages = {"de.adorsys.keycloak.config"}) @EnableConfigurationProperties({KeycloakConfigProperties.class, ImportConfigProperties.class}) +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class TestConfiguration { } diff --git a/src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java b/src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java new file mode 100644 index 000000000..5a92dbe8d --- /dev/null +++ b/src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java @@ -0,0 +1,45 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.normalize; + +import de.adorsys.keycloak.config.configuration.NormalizeTestConfiguration; +import de.adorsys.keycloak.config.extensions.GithubActionsExtension; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static java.util.concurrent.TimeUnit.SECONDS; + +@ExtendWith(SpringExtension.class) +@ExtendWith(GithubActionsExtension.class) +@ContextConfiguration( + classes = {NormalizeTestConfiguration.class}, + initializers = {ConfigDataApplicationContextInitializer.class} +) +@ActiveProfiles("normalize-IT") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +@Timeout(value = 30, unit = SECONDS) +public abstract class AbstractNormalizeTest { +} diff --git a/src/test/java/de/adorsys/keycloak/config/normalize/DummyTest.java b/src/test/java/de/adorsys/keycloak/config/normalize/DummyTest.java new file mode 100644 index 000000000..2c3a7a520 --- /dev/null +++ b/src/test/java/de/adorsys/keycloak/config/normalize/DummyTest.java @@ -0,0 +1,31 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.normalize; + +import org.junit.jupiter.api.Test; + +public class DummyTest extends AbstractNormalizeTest { + + @Test + void test() { + + } +} diff --git a/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakAuthentication.java b/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakAuthentication.java index b29f4851c..52e26edc7 100644 --- a/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakAuthentication.java +++ b/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakAuthentication.java @@ -24,9 +24,11 @@ import org.keycloak.admin.client.Keycloak; import org.keycloak.representations.AccessTokenResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class KeycloakAuthentication { private final KeycloakConfigProperties keycloakConfigProperties; diff --git a/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakRepository.java b/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakRepository.java index 123bbefc7..d3ea4269c 100644 --- a/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakRepository.java +++ b/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakRepository.java @@ -24,6 +24,7 @@ import org.keycloak.admin.client.resource.UserResource; import org.keycloak.representations.idm.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.util.List; @@ -35,6 +36,7 @@ import static org.hamcrest.Matchers.hasSize; @Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "IMPORT", matchIfMissing = true) public class KeycloakRepository { private final KeycloakProvider keycloakProvider; diff --git a/src/test/resources/application-normalize-IT.properties b/src/test/resources/application-normalize-IT.properties new file mode 100644 index 000000000..77a36297e --- /dev/null +++ b/src/test/resources/application-normalize-IT.properties @@ -0,0 +1,3 @@ +run.operation=normalize +normalization.files.input-locations=default +normalization.files.output-directory=default-output From 3bc830d65f011ddfc9caf91eb7844c6a3047dfa7 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Fri, 9 Dec 2022 14:57:26 +0100 Subject: [PATCH 16/49] Split up normalization into separate services --- .../KeycloakConfigNormalizationRunner.java | 40 +- .../NormalizationConfiguration.java | 100 + .../exception/NormalizationException.java | 31 + .../config/provider/BaselineProvider.java | 77 + .../normalize/ClientNormalizationService.java | 135 + .../ProtocolMapperNormalizationService.java | 59 + .../normalize/RealmNormalizationService.java | 317 +-- .../ScopeMappingNormalizationService.java | 115 + .../keycloak/config/util/JaversUtil.java | 42 + .../application-normalize-dev.properties | 2 +- .../baseline/19.0.3/client/client.json | 45 + .../baseline/19.0.3/realm/realm.json | 2176 +++++++++++++++++ .../reference-realms/19.0.3/client.json | 34 - .../reference-realms/19.0.3/realm.json | 1770 -------------- 14 files changed, 2850 insertions(+), 2093 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java create mode 100644 src/main/java/de/adorsys/keycloak/config/exception/NormalizationException.java create mode 100644 src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java create mode 100644 src/main/java/de/adorsys/keycloak/config/util/JaversUtil.java create mode 100644 src/main/resources/baseline/19.0.3/client/client.json create mode 100644 src/main/resources/baseline/19.0.3/realm/realm.json delete mode 100644 src/main/resources/reference-realms/19.0.3/client.json delete mode 100644 src/main/resources/reference-realms/19.0.3/realm.json diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java index 5a5f90344..e0e36c07d 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java @@ -20,11 +20,11 @@ package de.adorsys.keycloak.config; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import de.adorsys.keycloak.config.properties.NormalizationKeycloakConfigProperties; import de.adorsys.keycloak.config.provider.KeycloakExportProvider; import de.adorsys.keycloak.config.service.normalize.RealmNormalizationService; -import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -34,10 +34,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; -import java.util.Map; @Component @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") @@ -49,23 +50,44 @@ public class KeycloakConfigNormalizationRunner implements CommandLineRunner, Exi private final RealmNormalizationService normalizationService; private final KeycloakExportProvider exportProvider; - + private final NormalizationConfigProperties normalizationConfigProperties; + private final YAMLMapper yamlMapper; private int exitCode; @Autowired - public KeycloakConfigNormalizationRunner(RealmNormalizationService normalizationService, KeycloakExportProvider exportProvider) { + public KeycloakConfigNormalizationRunner(RealmNormalizationService normalizationService, + KeycloakExportProvider exportProvider, + NormalizationConfigProperties normalizationConfigProperties, + YAMLMapper yamlMapper) { this.normalizationService = normalizationService; this.exportProvider = exportProvider; + this.normalizationConfigProperties = normalizationConfigProperties; + this.yamlMapper = yamlMapper; } @Override public void run(String... args) throws Exception { try { - for (Map> exportLocations : exportProvider.readFromLocations().values()) { - for (Map.Entry> export : exportLocations.entrySet()) { + var outputLocation = Paths.get(normalizationConfigProperties.getFiles().getOutputDirectory()); + if (!Files.exists(outputLocation)) { + logger.info("Creating output directory '{}'", outputLocation); + Files.createDirectories(outputLocation); + } + if (!Files.isDirectory(outputLocation)) { + logger.error("Output location '{}' is not a directory. Aborting", outputLocation); + exitCode = 1; + return; + } + + for (var exportLocations : exportProvider.readFromLocations().values()) { + for (var export : exportLocations.entrySet()) { logger.info("Normalizing file '{}'", export.getKey()); - for (RealmRepresentation realm : export.getValue()) { - normalizationService.normalize(realm); + for (var realm : export.getValue()) { + var normalizedRealm = normalizationService.normalizeRealm(realm); + var outputFile = outputLocation.resolve(String.format("%s.yaml", normalizedRealm.getRealm())); + try (var os = new FileOutputStream(outputFile.toFile())) { + yamlMapper.writeValue(os, normalizedRealm); + } } } } diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java new file mode 100644 index 000000000..904a6bd13 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -0,0 +1,100 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.configuration; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import org.javers.core.Javers; +import org.javers.core.JaversBuilder; +import org.javers.core.diff.ListCompareAlgorithm; +import org.javers.core.metamodel.clazz.EntityDefinition; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class NormalizationConfiguration { + + @Bean + public Javers javers() { + return commonJavers() + .withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE) + .build(); + } + + @Bean + public Javers unOrderedJavers() { + return commonJavers() + .withListCompareAlgorithm(ListCompareAlgorithm.AS_SET) + .build(); + } + + @Bean + public YAMLMapper yamlMapper() { + YAMLMapper mapper = new YAMLMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return mapper; + } + + private JaversBuilder commonJavers() { + var realmIgnoredProperties = new ArrayList(); + realmIgnoredProperties.add("id"); + realmIgnoredProperties.add("groups"); + realmIgnoredProperties.add("roles"); + realmIgnoredProperties.add("defaultRole"); + realmIgnoredProperties.add("clientProfiles"); // + realmIgnoredProperties.add("clientPolicies"); // + realmIgnoredProperties.add("users"); + realmIgnoredProperties.add("federatedUsers"); + realmIgnoredProperties.add("scopeMappings"); // + realmIgnoredProperties.add("clientScopeMappings"); // + realmIgnoredProperties.add("clients"); // + realmIgnoredProperties.add("clientScopes"); // + realmIgnoredProperties.add("userFederationProviders"); + realmIgnoredProperties.add("userFederationMappers"); + realmIgnoredProperties.add("identityProviders"); + realmIgnoredProperties.add("identityProviderMappers"); + realmIgnoredProperties.add("protocolMappers"); // + realmIgnoredProperties.add("components"); + realmIgnoredProperties.add("authenticationFlows"); + realmIgnoredProperties.add("authenticatorConfig"); + realmIgnoredProperties.add("requiredActions"); + realmIgnoredProperties.add("applicationScopeMappings"); + realmIgnoredProperties.add("applications"); + realmIgnoredProperties.add("oauthClients"); + realmIgnoredProperties.add("clientTemplates"); + realmIgnoredProperties.add("attributes"); + + return JaversBuilder.javers() + .registerEntity(new EntityDefinition(RealmRepresentation.class, "realm", realmIgnoredProperties)) + .registerEntity(new EntityDefinition(ClientRepresentation.class, "clientId", + List.of("id", "authorizationSettings", "protocolMappers"))) + .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))); + + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/exception/NormalizationException.java b/src/main/java/de/adorsys/keycloak/config/exception/NormalizationException.java new file mode 100644 index 000000000..6261834b8 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/exception/NormalizationException.java @@ -0,0 +1,31 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.exception; + +public class NormalizationException extends RuntimeException { + public NormalizationException(String message, Throwable cause) { + super(message, cause); + } + + public NormalizationException(String message) { + super(message); + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java new file mode 100644 index 000000000..d9e04fee2 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java @@ -0,0 +1,77 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.provider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.adorsys.keycloak.config.exception.NormalizationException; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +@SuppressFBWarnings(value = {"NP_LOAD_OF_KNOWN_NULL_VALUE", "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"}, + justification = "Bug in Spotbugs, see https://github.com/spotbugs/spotbugs/issues/1338") +public class BaselineProvider { + + private static final String PLACEHOLDER = "REALM_NAME_PLACEHOLDER"; + + private final ObjectMapper objectMapper; + + @Autowired + public BaselineProvider(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public RealmRepresentation getRealm(String version, String realmName) { + try (var inputStream = getClass() + .getResourceAsStream(String.format("/baseline/%s/realm/realm.json", version))) { + if (inputStream == null) { + throw new NormalizationException(String.format("Reference realm for version %s does not exist", version)); + } + /* + * Replace the placeholder with the realm name to import. This sets some internal values like role names, + * baseUrls and redirectUrls so that they don't get picked up as "changes" + */ + var realmString = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, realmName); + return objectMapper.readValue(realmString, RealmRepresentation.class); + } catch (IOException ex) { + throw new NormalizationException(String.format("Failed to load baseline realm for version %s", version), ex); + } + } + + public ClientRepresentation getClient(String version, String clientId) { + try (var is = getClass() + .getResourceAsStream(String.format("/baseline/%s/client/client.json", version))) { + var client = objectMapper.readValue(is, ClientRepresentation.class); + client.setClientId(clientId); + return client; + } catch (IOException ex) { + throw new NormalizationException(String.format("Failed to load baseline client for version %s", version), ex); + } + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java new file mode 100644 index 000000000..16d0f699f --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -0,0 +1,135 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.provider.BaselineProvider; +import de.adorsys.keycloak.config.util.JaversUtil; +import org.javers.core.Javers; +import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class ClientNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(ClientNormalizationService.class); + private final Javers javers; + private final BaselineProvider baselineProvider; + private final JaversUtil javersUtil; + private final ProtocolMapperNormalizationService protocolMapperNormalizationService; + + public ClientNormalizationService(Javers javers, + BaselineProvider baselineProvider, + JaversUtil javersUtil, + ProtocolMapperNormalizationService protocolMapperNormalizationService) { + this.javers = javers; + this.baselineProvider = baselineProvider; + this.javersUtil = javersUtil; + this.protocolMapperNormalizationService = protocolMapperNormalizationService; + } + + public List normalizeClients(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { + var exportedClientMap = new HashMap(); + for (var exportedClient : exportedRealm.getClients()) { + exportedClientMap.put(exportedClient.getClientId(), exportedClient); + } + + var baselineClientMap = new HashMap(); + var clients = new ArrayList(); + for (var baselineRealmClient : baselineRealm.getClients()) { + var clientId = baselineRealmClient.getClientId(); + baselineClientMap.put(clientId, baselineRealmClient); + var exportedClient = exportedClientMap.get(clientId); + if (exportedClient == null) { + logger.warn("Default realm client '{}' was deleted in exported realm. It may be reintroduced during import!", clientId); + /* + * Here we need to define a configuration parameter: If we want the import *not* to reintroduce default clients that were + * deleted, we need to add *all* clients, not just default clients to the dump. Then during import, set the mode that + * makes clients fully managed, so that *only* clients that are in the dump end up in the realm + */ + continue; + } + if (clientChanged(exportedClient, baselineRealmClient)) { + // We know the client has changed in some way. Now, compare it to a default client to minimize it + clients.add(normalizeClient(exportedClient, exportedRealm.getKeycloakVersion())); + } + } + + // Now iterate over all the clients that are *not* default clients + for (Map.Entry e : exportedClientMap.entrySet()) { + if (!baselineClientMap.containsKey(e.getKey())) { + clients.add(normalizeClient(e.getValue(), exportedRealm.getKeycloakVersion())); + } + } + return clients; + } + + public ClientRepresentation normalizeClient(ClientRepresentation client, String keycloakVersion) { + var clientId = client.getClientId(); + var baselineClient = baselineProvider.getClient(keycloakVersion, clientId); + var diff = javers.compare(baselineClient, client); + var normalizedClient = new ClientRepresentation(); + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedClient, change); + } + normalizedClient.setProtocolMappers(client.getProtocolMappers()); + for (var mapper : normalizedClient.getProtocolMappers()) { + mapper.setId(null); + } + normalizedClient.setAuthorizationSettings(client.getAuthorizationSettings()); + normalizedClient.setClientId(clientId); + return normalizedClient; + } + + public boolean clientChanged(ClientRepresentation exportedClient, ClientRepresentation baselineClient) { + var diff = javers.compare(baselineClient, exportedClient); + if (diff.hasChanges()) { + return true; + } + if (protocolMappersChanged(exportedClient.getProtocolMappers(), baselineClient.getProtocolMappers())) { + return true; + } + return authorizationSettingsChanged(exportedClient.getAuthorizationSettings(), baselineClient.getAuthorizationSettings()); + } + + public boolean protocolMappersChanged(List exportedMappers, List baselineMappers) { + // CompareCollections doesn't handle nulls gracefully + return javers.compareCollections(baselineMappers == null ? List.of() : baselineMappers, + exportedMappers == null ? List.of() : exportedMappers, ProtocolMapperRepresentation.class).hasChanges(); + } + + public boolean authorizationSettingsChanged(ResourceServerRepresentation exportedSettings, ResourceServerRepresentation baselineSettings) { + return javers.compare(baselineSettings, exportedSettings).hasChanges(); + } + +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java new file mode 100644 index 000000000..936ff3260 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java @@ -0,0 +1,59 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import org.javers.core.Javers; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class ProtocolMapperNormalizationService { + + private final Javers unOrderedJavers; + + public ProtocolMapperNormalizationService(Javers unOrderedJavers) { + this.unOrderedJavers = unOrderedJavers; + } + + public List normalizeProtocolMappers(List exportedMappers, + List baselineMappers) { + List exportedOrEmpty = exportedMappers == null ? List.of() : exportedMappers; + List baselineOrEmpty = baselineMappers == null ? List.of() : baselineMappers; + + List normalizedMappers = null; + if (unOrderedJavers.compareCollections(baselineOrEmpty, exportedOrEmpty, ProtocolMapperRepresentation.class).hasChanges()) { + /* + * If the mapper lists differ, add all the mappers from the exported list. Otherwise, just return null + */ + normalizedMappers = new ArrayList<>(); + for (var mapper : exportedOrEmpty) { + mapper.setId(null); + normalizedMappers.add(mapper); + } + } + return normalizedMappers; + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 1f0bd80ae..3a9e9ea72 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -20,112 +20,58 @@ package de.adorsys.keycloak.config.service.normalize; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; -import de.adorsys.keycloak.config.KeycloakConfigRunner; -import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import de.adorsys.keycloak.config.properties.NormalizationKeycloakConfigProperties; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import de.adorsys.keycloak.config.provider.BaselineProvider; +import de.adorsys.keycloak.config.util.JaversUtil; import org.javers.core.Javers; -import org.javers.core.JaversBuilder; -import org.javers.core.diff.ListCompareAlgorithm; import org.javers.core.diff.changetype.PropertyChange; -import org.javers.core.metamodel.clazz.EntityDefinition; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.ScopeMappingRepresentation; -import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") -@SuppressFBWarnings(value = {"NP_LOAD_OF_KNOWN_NULL_VALUE", "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"}, - justification = "Bug in Spotbugs, see https://github.com/spotbugs/spotbugs/issues/1338") public class RealmNormalizationService { - private static final Logger logger = LoggerFactory.getLogger(KeycloakConfigRunner.class); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final ObjectMapper YAML_MAPPER = new YAMLMapper(); + private static final Logger logger = LoggerFactory.getLogger(RealmNormalizationService.class); - private static final String PLACEHOLDER = "REALM_NAME_PLACEHOLDER"; - private static final Javers JAVERS; - - static { - YAML_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); - var realmIgnoredProperties = new ArrayList(); - realmIgnoredProperties.add("id"); - realmIgnoredProperties.add("groups"); - realmIgnoredProperties.add("roles"); - realmIgnoredProperties.add("defaultRole"); - realmIgnoredProperties.add("clientProfiles"); // - realmIgnoredProperties.add("clientPolicies"); // - realmIgnoredProperties.add("users"); - realmIgnoredProperties.add("federatedUsers"); - realmIgnoredProperties.add("scopeMappings"); // - realmIgnoredProperties.add("clientScopeMappings"); // - realmIgnoredProperties.add("clients"); // - realmIgnoredProperties.add("clientScopes"); // - realmIgnoredProperties.add("userFederationProviders"); - realmIgnoredProperties.add("userFederationMappers"); - realmIgnoredProperties.add("identityProviders"); - realmIgnoredProperties.add("identityProviderMappers"); - realmIgnoredProperties.add("protocolMappers"); // - realmIgnoredProperties.add("components"); - realmIgnoredProperties.add("authenticationFlows"); - realmIgnoredProperties.add("authenticatorConfig"); - realmIgnoredProperties.add("requiredActions"); - realmIgnoredProperties.add("applicationScopeMappings"); - realmIgnoredProperties.add("applications"); - realmIgnoredProperties.add("oauthClients"); - realmIgnoredProperties.add("clientTemplates"); - realmIgnoredProperties.add("attributes"); - - JAVERS = JaversBuilder.javers() - .registerEntity(new EntityDefinition(RealmRepresentation.class, "realm", realmIgnoredProperties)) - .registerEntity(new EntityDefinition(ClientRepresentation.class, "clientId", - List.of("id", "authorizationSettings", "protocolMappers"))) - .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) - .withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE) - .build(); - } - - private final NormalizationConfigProperties normalizationConfigProperties; private final NormalizationKeycloakConfigProperties keycloakConfigProperties; + private final Javers javers; + private final BaselineProvider baselineProvider; + private final ClientNormalizationService clientNormalizationService; + private final ScopeMappingNormalizationService scopeMappingNormalizationService; + private final ProtocolMapperNormalizationService protocolMapperNormalizationService; + private final JaversUtil javersUtil; @Autowired - public RealmNormalizationService(NormalizationConfigProperties normalizationConfigProperties, - NormalizationKeycloakConfigProperties keycloakConfigProperties) { - this.normalizationConfigProperties = normalizationConfigProperties; + public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakConfigProperties, + Javers javers, + BaselineProvider baselineProvider, + ClientNormalizationService clientNormalizationService, + ScopeMappingNormalizationService scopeMappingNormalizationService, + ProtocolMapperNormalizationService protocolMapperNormalizationService, + JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; + this.javers = javers; + this.baselineProvider = baselineProvider; + this.clientNormalizationService = clientNormalizationService; + this.scopeMappingNormalizationService = scopeMappingNormalizationService; + this.protocolMapperNormalizationService = protocolMapperNormalizationService; + this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? // TODO Ignore clients by regex } - public void normalize(RealmRepresentation exportedRealm) throws Exception { - var outputLocation = Paths.get(normalizationConfigProperties.getFiles().getOutputDirectory()); - if (!Files.exists(outputLocation)) { - logger.info("Creating output directory '{}'", outputLocation); - Files.createDirectories(outputLocation); - } - if (!Files.isDirectory(outputLocation)) { - logger.error("Output location '{}' is not a directory. Aborting.", outputLocation); - return; - } + public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { var keycloakConfigVersion = keycloakConfigProperties.getVersion(); var exportVersion = exportedRealm.getKeycloakVersion(); if (!exportVersion.equals(keycloakConfigVersion)) { @@ -136,20 +82,8 @@ public void normalize(RealmRepresentation exportedRealm) throws Exception { } var exportedRealmRealm = exportedRealm.getRealm(); logger.info("Exporting realm {}", exportedRealmRealm); - RealmRepresentation baselineRealm; - try (var defaultRealmIs = getClass() - .getResourceAsStream(String.format("/reference-realms/%s/realm.json", exportVersion))) { - if (defaultRealmIs == null) { - logger.error("Reference realm for version {} does not exist", exportVersion); - return; - } - /* - * Replace the placeholder with the realm name to import. This sets some internal values like role names, - * baseUrls and redirectUrls so that they don't get picked up as "changes" - */ - var realmString = new String(defaultRealmIs.readAllBytes(), StandardCharsets.UTF_8).replace(PLACEHOLDER, exportedRealmRealm); - baselineRealm = OBJECT_MAPPER.readValue(realmString, RealmRepresentation.class); - } + var baselineRealm = baselineProvider.getRealm(exportVersion, exportedRealmRealm); + /* * Trick javers into thinking this is the "same" object, by setting the ID on the reference realm * to the ID of the current realm. That way we only get actual changes, not a full list of changes @@ -160,13 +94,13 @@ public void normalize(RealmRepresentation exportedRealm) throws Exception { handleBaseRealm(exportedRealm, baselineRealm, minimizedRealm); - var clients = getMinimizedClients(exportedRealm, baselineRealm); + var clients = clientNormalizationService.normalizeClients(exportedRealm, baselineRealm); if (!clients.isEmpty()) { minimizedRealm.setClients(clients); } // No setter for some reason... - var minimizedScopeMappings = getMinimizedScopeMappings(exportedRealm, baselineRealm); + var minimizedScopeMappings = scopeMappingNormalizationService.normalizeScopeMappings(exportedRealm, baselineRealm); if (!minimizedScopeMappings.isEmpty()) { var scopeMappings = minimizedRealm.getScopeMappings(); if (scopeMappings == null) { @@ -174,10 +108,10 @@ public void normalize(RealmRepresentation exportedRealm) throws Exception { scopeMappings = minimizedRealm.getScopeMappings(); scopeMappings.clear(); } - scopeMappings.addAll(getMinimizedScopeMappings(exportedRealm, baselineRealm)); + scopeMappings.addAll(minimizedScopeMappings); } - var clientScopeMappings = getMinimizedClientScopeMappings(exportedRealm, baselineRealm); + var clientScopeMappings = scopeMappingNormalizationService.normalizeClientScopeMappings(exportedRealm, baselineRealm); if (!clientScopeMappings.isEmpty()) { minimizedRealm.setClientScopeMappings(clientScopeMappings); } @@ -187,17 +121,9 @@ public void normalize(RealmRepresentation exportedRealm) throws Exception { minimizedRealm.setAttributes(attributes); } - var protocolMappers = getMinimizedProtocolMappers(exportedRealm.getProtocolMappers(), - baselineRealm.getProtocolMappers()); - if (!protocolMappers.isEmpty()) { - minimizedRealm.setProtocolMappers(protocolMappers); - } - - var outputFile = outputLocation.resolve(String.format("%s.yaml", exportedRealmRealm)); - - try (var os = new FileOutputStream(outputFile.toFile())) { - YAML_MAPPER.writeValue(os, minimizedRealm); - } + minimizedRealm.setProtocolMappers(protocolMapperNormalizationService.normalizeProtocolMappers(exportedRealm.getProtocolMappers(), + baselineRealm.getProtocolMappers())); + return minimizedRealm; } private Map getMinimizedAttributes(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { @@ -222,73 +148,10 @@ private Map getMinimizedAttributes(RealmRepresentation exportedR return minimizedAttributes; } - private List getMinimizedProtocolMappers(List exportedMappers, - List baselineMappers) { - logger.info("Temporary measure so PMD shuts up {} {}", exportedMappers.size(), baselineMappers.size()); - return List.of(); - } - - private List getMinimizedClients(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) - throws IOException, NoSuchFieldException, IllegalAccessException { - // Get a client map for better lookups - var exportedClientMap = new HashMap(); - for (var exportedClient : exportedRealm.getClients()) { - exportedClientMap.put(exportedClient.getClientId(), exportedClient); - } - - var baselineClientMap = new HashMap(); - - var clients = new ArrayList(); - for (var baselineRealmClient : baselineRealm.getClients()) { - var clientId = baselineRealmClient.getClientId(); - baselineClientMap.put(clientId, baselineRealmClient); - var exportedClient = exportedClientMap.get(clientId); - if (exportedClient == null) { - logger.warn("Default realm client '{}' was deleted in exported realm. It will be reintroduced during import!", clientId); - /* - * Here we need to define a configuration parameter: If we want the import *not* to reintroduce default clients that were - * deleted, we need to add *all* clients, not just default clients to the dump. Then during import, set the mode that - * makes clients fully managed, so that *only* clients that are in the dump end up in the realm - */ - continue; - } - if (clientChanged(baselineRealmClient, exportedClient)) { - // We know the client has changed in some way. Now, compare it to a default client to minimize it - clients.add(getMinimizedClient(exportedClient, clientId, exportedRealm.getKeycloakVersion())); - } - } - - // Now iterate over all the clients that are *not* default clients - for (Map.Entry e : exportedClientMap.entrySet()) { - var clientId = e.getKey(); - if (!baselineClientMap.containsKey(clientId)) { - clients.add(getMinimizedClient(e.getValue(), clientId, exportedRealm.getKeycloakVersion())); - } - } - return clients; - } - - private ClientRepresentation getMinimizedClient(ClientRepresentation exportedClient, String clientId, String keycloakVersion) - throws IOException, NoSuchFieldException, IllegalAccessException { - var baselineClient = getBaselineClient(clientId, keycloakVersion); - var clientDiff = JAVERS.compare(baselineClient, exportedClient); - var minimizedClient = new ClientRepresentation(); - for (var change : clientDiff.getChangesByType(PropertyChange.class)) { - applyChange(minimizedClient, change); - } - // For now, don't minimize authorizationSettings and protocolMappers. Add them as-is - minimizedClient.setProtocolMappers(exportedClient.getProtocolMappers()); - minimizedClient.setAuthorizationSettings(exportedClient.getAuthorizationSettings()); - minimizedClient.setClientId(clientId); - return minimizedClient; - } - - - private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm, RealmRepresentation minimizedRealm) - throws NoSuchFieldException, IllegalAccessException { - var diff = JAVERS.compare(baselineRealm, exportedRealm); + private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm, RealmRepresentation minimizedRealm) { + var diff = javers.compare(baselineRealm, exportedRealm); for (var change : diff.getChangesByType(PropertyChange.class)) { - applyChange(minimizedRealm, change); + javersUtil.applyChange(minimizedRealm, change); } // Now that Javers is done, clean up a bit afterwards. We always need to set the realm and enabled fields @@ -302,108 +165,4 @@ private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentat minimizedRealm.setId(exportedRealm.getId()); } } - - private List getMinimizedScopeMappings(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { - /* - * TODO: are the mappings in scopeMappings always clientScope/role? If not, this breaks - */ - // First handle the "default" scopeMappings present in the - var exportedMappingsMap = new HashMap(); - for (var exportedMapping : exportedRealm.getScopeMappings()) { - exportedMappingsMap.put(exportedMapping.getClientScope(), exportedMapping); - } - - var baselineMappingsMap = new HashMap(); - - var mappings = new ArrayList(); - for (var baselineRealmMapping : baselineRealm.getScopeMappings()) { - var clientScope = baselineRealmMapping.getClientScope(); - baselineMappingsMap.put(clientScope, baselineRealmMapping); - var exportedMapping = exportedMappingsMap.get(clientScope); - if (exportedMapping == null) { - logger.warn("Default realm scopeMapping '{}' was deleted in exported realm. It will be reintroduced during import!", clientScope); - continue; - } - // If the exported scopeMapping is different from the one that is present in the baseline realm, export it in the yml - if (scopeMappingChanged(baselineRealmMapping, exportedMapping)) { - mappings.add(exportedMapping); - } - } - - for (Map.Entry e : exportedMappingsMap.entrySet()) { - var clientScope = e.getKey(); - if (!baselineMappingsMap.containsKey(clientScope)) { - mappings.add(e.getValue()); - } - } - return mappings; - } - - private Map> getMinimizedClientScopeMappings(RealmRepresentation exportedRealm, - RealmRepresentation baselineRealm) { - var baselineMappings = baselineRealm.getClientScopeMappings(); - var exportedMappings = exportedRealm.getClientScopeMappings(); - - var mappings = new HashMap>(); - for (var e : baselineMappings.entrySet()) { - var key = e.getKey(); - if (!exportedMappings.containsKey(key)) { - logger.warn("Default realm clientScopeMapping '{}' was deleted in exported realm. It will be reintroduced during import!", key); - continue; - } - var scopeMappings = exportedMappings.get(key); - if (JAVERS.compareCollections(e.getValue(), scopeMappings, ScopeMappingRepresentation.class).hasChanges()) { - mappings.put(key, scopeMappings); - } - } - - for (var e : exportedMappings.entrySet()) { - var key = e.getKey(); - if (!baselineMappings.containsKey(key)) { - mappings.put(key, e.getValue()); - } - } - return mappings; - } - - - private boolean scopeMappingChanged(ScopeMappingRepresentation baselineRealmMapping, ScopeMappingRepresentation exportedMapping) { - return JAVERS.compare(baselineRealmMapping, exportedMapping).hasChanges(); - } - - private void applyChange(Object object, PropertyChange change) throws NoSuchFieldException, IllegalAccessException { - var field = object.getClass().getDeclaredField(change.getPropertyName()); - field.setAccessible(true); - field.set(object, change.getRight()); - } - - private boolean clientChanged(ClientRepresentation baselineClient, ClientRepresentation exportedClient) { - var diff = JAVERS.compare(baselineClient, exportedClient); - if (diff.hasChanges()) { - return true; - } - if (protocolMappersChanged(baselineClient.getProtocolMappers(), exportedClient.getProtocolMappers())) { - return true; - } - return authorizationSettingsChanged(baselineClient.getAuthorizationSettings(), exportedClient.getAuthorizationSettings()); - } - - private boolean protocolMappersChanged(List defaultMappers, List exportedMappers) { - // CompareCollections doesn't handle nulls gracefully - return JAVERS.compareCollections(defaultMappers == null ? List.of() : defaultMappers, - exportedMappers == null ? List.of() : exportedMappers, ProtocolMapperRepresentation.class).hasChanges(); - } - - private boolean authorizationSettingsChanged(ResourceServerRepresentation defaultSettings, ResourceServerRepresentation exportedSettings) { - return JAVERS.compare(defaultSettings, exportedSettings).hasChanges(); - } - - private ClientRepresentation getBaselineClient(String clientId, String keycloakVersion) throws IOException { - try (var is = getClass() - .getResourceAsStream(String.format("/reference-realms/%s/client.json", keycloakVersion))) { - var client = OBJECT_MAPPER.readValue(is, ClientRepresentation.class); - client.setClientId(clientId); - return client; - } - } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java new file mode 100644 index 000000000..6bf35666a --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java @@ -0,0 +1,115 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import org.javers.core.Javers; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.ScopeMappingRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class ScopeMappingNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(ScopeMappingNormalizationService.class); + + private final Javers javers; + + public ScopeMappingNormalizationService(Javers javers) { + this.javers = javers; + } + + public List normalizeScopeMappings(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { + /* + * TODO: are the mappings in scopeMappings always clientScope/role? If not, this breaks + */ + // First handle the "default" scopeMappings present in the + var exportedMappingsMap = new HashMap(); + for (var exportedMapping : exportedRealm.getScopeMappings()) { + exportedMappingsMap.put(exportedMapping.getClientScope(), exportedMapping); + } + + var baselineMappingsMap = new HashMap(); + + var mappings = new ArrayList(); + for (var baselineRealmMapping : baselineRealm.getScopeMappings()) { + var clientScope = baselineRealmMapping.getClientScope(); + baselineMappingsMap.put(clientScope, baselineRealmMapping); + var exportedMapping = exportedMappingsMap.get(clientScope); + if (exportedMapping == null) { + logger.warn("Default realm scopeMapping '{}' was deleted in exported realm. It will be reintroduced during import!", clientScope); + continue; + } + // If the exported scopeMapping is different from the one that is present in the baseline realm, export it in the yml + if (scopeMappingChanged(exportedMapping, baselineRealmMapping)) { + mappings.add(exportedMapping); + } + } + + for (Map.Entry e : exportedMappingsMap.entrySet()) { + var clientScope = e.getKey(); + if (!baselineMappingsMap.containsKey(clientScope)) { + mappings.add(e.getValue()); + } + } + return mappings; + } + + public Map> normalizeClientScopeMappings(RealmRepresentation exportedRealm, + RealmRepresentation baselineRealm) { + var baselineMappings = baselineRealm.getClientScopeMappings(); + var exportedMappings = exportedRealm.getClientScopeMappings(); + + var mappings = new HashMap>(); + for (var e : baselineMappings.entrySet()) { + var key = e.getKey(); + if (!exportedMappings.containsKey(key)) { + logger.warn("Default realm clientScopeMapping '{}' was deleted in exported realm. It may be reintroduced during import!", key); + continue; + } + var scopeMappings = exportedMappings.get(key); + if (javers.compareCollections(e.getValue(), scopeMappings, ScopeMappingRepresentation.class).hasChanges()) { + mappings.put(key, scopeMappings); + } + } + + for (var e : exportedMappings.entrySet()) { + var key = e.getKey(); + if (!baselineMappings.containsKey(key)) { + mappings.put(key, e.getValue()); + } + } + return mappings; + } + + public boolean scopeMappingChanged(ScopeMappingRepresentation exportedMapping, ScopeMappingRepresentation baselineRealmMapping) { + return javers.compare(baselineRealmMapping, exportedMapping).hasChanges(); + } + +} diff --git a/src/main/java/de/adorsys/keycloak/config/util/JaversUtil.java b/src/main/java/de/adorsys/keycloak/config/util/JaversUtil.java new file mode 100644 index 000000000..6ec32867c --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/util/JaversUtil.java @@ -0,0 +1,42 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.util; + +import de.adorsys.keycloak.config.exception.NormalizationException; +import org.javers.core.diff.changetype.PropertyChange; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class JaversUtil { + + public void applyChange(Object object, PropertyChange change) { + try { + var field = object.getClass().getDeclaredField(change.getPropertyName()); + field.setAccessible(true); + field.set(object, change.getRight()); + } catch (NoSuchFieldException | IllegalAccessException ex) { + throw new NormalizationException(String.format("Failed to set property %s on object of type %s", + change.getPropertyName(), object.getClass().getName()), ex); + } + } +} diff --git a/src/main/resources/application-normalize-dev.properties b/src/main/resources/application-normalize-dev.properties index 477367e1e..ada43bd0e 100644 --- a/src/main/resources/application-normalize-dev.properties +++ b/src/main/resources/application-normalize-dev.properties @@ -2,5 +2,5 @@ spring.output.ansi.enabled=ALWAYS spring.config.import=classpath:application-debug.properties run.operation=NORMALIZE -normalization.files.input-locations=./exports/in/realm.json +normalization.files.input-locations=./exports/in/*.json normalization.files.output-directory=./exports/out diff --git a/src/main/resources/baseline/19.0.3/client/client.json b/src/main/resources/baseline/19.0.3/client/client.json new file mode 100644 index 000000000..858b1115b --- /dev/null +++ b/src/main/resources/baseline/19.0.3/client/client.json @@ -0,0 +1,45 @@ +{ + "id": "caa6606a-6056-475c-af05-8b0365bb8164", + "clientId": "reference-client", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "q0t682YLCCk2qd5dntjtcniGozLXIZ7h", + "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": "1667920370" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} diff --git a/src/main/resources/baseline/19.0.3/realm/realm.json b/src/main/resources/baseline/19.0.3/realm/realm.json new file mode 100644 index 000000000..5e65e9a84 --- /dev/null +++ b/src/main/resources/baseline/19.0.3/realm/realm.json @@ -0,0 +1,2176 @@ +{ + "id": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "realm": "REALM_NAME_PLACEHOLDER", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "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, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "0825e37b-b5bb-413d-9c1c-23457f17cb66", + "name": "default-roles-REALM_NAME_PLACEHOLDER", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "manage-account", + "view-profile" + ] + } + }, + "clientRole": false, + "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "attributes": {} + }, + { + "id": "08b30ae4-52ba-45a9-b723-07e6ac57ae2f", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "attributes": {} + }, + { + "id": "372ddfc2-cd6e-4ca3-a30f-d39cb92ac12d", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "caf6ae68-6a16-4dfa-9a71-1149ceb8f79c", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "913aeddb-8be9-4060-86c7-30cf0fa892ee", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "15ee649c-ef5f-4fe8-a6c1-67a9db14b0c5", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "5d56f881-d3ff-49ad-929c-214afd641f8f", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "0ba58546-8487-4508-b81f-0735432accf2", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "f6f1b3c0-5d29-41b4-b2ec-cfab60e28a3b", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "2c6ddc11-206e-4e82-b894-9fca7cc85866", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "f9917916-7aca-413b-b7bc-452d3fca7a48", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "ad1d551b-3f03-445d-8ac0-51b1196413f0", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "f645cca6-9d25-40f0-8ce9-d988a04d9bec", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "ce56402a-8925-4750-9269-6b035cfa334f", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "1e70f63f-0883-4452-bb1b-f179bf3c8c30", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "5cef1f90-8af8-4445-b6a3-05d6eb60c46c", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "e87e4aa8-8c15-44ba-9622-eec4eeae1997", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "baa31234-e4ed-4821-a696-6ccd686b5e1f", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "0d7127a0-cd7b-478c-9156-629fc321bca4", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "99d07b51-633c-4d25-9dc1-f779a219c246", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-users", + "manage-identity-providers", + "view-clients", + "query-clients", + "view-events", + "query-users", + "view-realm", + "manage-events", + "view-users", + "create-client", + "manage-authorization", + "impersonation", + "manage-realm", + "view-identity-providers", + "query-groups", + "query-realms", + "manage-clients", + "view-authorization" + ] + } + }, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "118c289f-0b71-4edf-ac2a-7016cc0c674d", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + }, + { + "id": "30f43104-6914-4e4f-9f6d-219daf2f7991", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "631f9fd2-7539-4190-8983-0e94614c5b73", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "6045a69a-aec3-4a11-b4c3-3cae53f7a914", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "6eed48a3-64bd-48ed-ac7c-607f8e724258", + "attributes": {} + } + ], + "account": [ + { + "id": "4e27eb76-06ea-4547-ab5d-de8f9c745597", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes": {} + }, + { + "id": "60ae83a6-6ed1-4b4f-81cc-701b9a95ceb9", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes": {} + }, + { + "id": "f5f244ba-53de-45d7-9774-d4c915cd4ccb", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes": {} + }, + { + "id": "2abb2ef2-3e4c-43d5-9ab7-b096e19f3a56", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes": {} + }, + { + "id": "8eb01f07-3771-4b28-ab78-6f216079b508", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes": {} + }, + { + "id": "23954dae-8150-4183-8733-bce1349fb0ec", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes": {} + }, + { + "id": "734d080d-1f67-48e9-942f-9233f6eca901", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "0825e37b-b5bb-413d-9c1c-23457f17cb66", + "name": "default-roles-REALM_NAME_PLACEHOLDER", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/REALM_NAME_PLACEHOLDER/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/REALM_NAME_PLACEHOLDER/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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "fe6ddaaf-3a90-4942-ae6a-1f170c87fc3b", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/REALM_NAME_PLACEHOLDER/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/REALM_NAME_PLACEHOLDER/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": "9f385a11-7b42-4f92-ac9b-7d286590a392", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "61ecc83e-d3f3-4b4f-a821-845094b3d9d4", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "6eed48a3-64bd-48ed-ac7c-607f8e724258", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "631f9fd2-7539-4190-8983-0e94614c5b73", + "clientId": "realm-management", + "name": "${client_realm-management}", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "05f3367b-492b-40e1-ba0c-f000aa3ad0ef", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/REALM_NAME_PLACEHOLDER/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/REALM_NAME_PLACEHOLDER/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": "9d1f5814-fa63-4c36-ae10-747d30f47c69", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "23fc5623-9366-478f-9924-801d71f32489", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "31541a2f-1c88-43d9-96fe-9f9efbd096d4", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "1859a4f3-d051-4800-963f-45b624cccd57", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "a7777bff-a046-4fe3-a5a9-a520d79865ec", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "7d011629-d7f1-45ad-a09b-b4decb3d47ed", + "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": "22a74f8c-4493-4a1a-bf97-f51466e336b3", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "7fb8529d-cb6f-4f31-b4c3-f6be6f74b47d", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "5bd8bfba-d8a3-4792-aaec-5f0513397193", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "89146d36-28d5-4750-933e-75014b203dcf", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "01025c61-c655-463d-967d-a45e88368472", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "ad80bcc7-66d4-44c0-b34e-65822a57359a", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "73346462-e569-4679-a57d-fe623bdc5a95", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "3842db0a-837f-4564-9e48-07c87f5d3258", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "f79aab58-4d4a-40ee-b879-a586ff956f12", + "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": "ccb6bedc-b921-4c83-abe6-13de8c0e9795", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "19ae8192-0189-4644-a362-d08a0bce5680", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "a0d6d0c2-afd3-4b29-a99f-ff3002866519", + "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": "1dcbfe97-e4ab-4df7-8517-4eaaa26ea410", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "8448e164-b72e-406e-b14b-ae5d1e72393a", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "c573ae92-f94a-494e-9403-f875d22d3e8d", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "09f992cc-2d64-4d4d-88ca-59a1b63325e9", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "04658d90-dc3d-4865-ac52-6f16570fcd76", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "de199ca1-e720-419f-aeee-e112568f5cff", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "08daf912-8959-4ba5-9c38-ffcd395d6487", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "d76e56a2-6237-46d5-a56b-0ecd1f979e85", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "6bc40007-ab16-48f6-ae81-a66a9062ad5c", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "0385dbe8-587c-49ab-a317-a15b5b52d456", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "28946b3e-0eab-44a5-8eee-d42088072306", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "e61c48dc-d083-4029-92d1-3c7a6f3d8bb9", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "62545d7c-ef2e-48a6-bff8-e2e9a2b16c3d", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "f1c75501-26b2-4d94-82aa-851e4fa3dd7c", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "046a0b0d-5256-48fe-9b8f-b5193d87ebdd", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "d807e61e-3661-44ff-a8cd-285458e6f763", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "fede614b-c46e-484c-8dba-9590cd0205fe", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "c8a4b7a9-c8ba-412b-a17d-e90a3c6393fd", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "de4bafae-e88c-4c2f-9001-311f5c141633", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "9aa11aac-4178-435b-83b3-2d85508e34bd", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "808ef7c4-b5d1-491c-ade0-559f38287e68", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "2a0ad1a6-d6f9-43cb-8ec1-8913a23339d7", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper" + ] + } + }, + { + "id": "3695208c-32af-497b-9af9-5de878749899", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "b7317013-6157-4099-a1ee-194df4808b2d", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "2464d485-980d-447a-94e2-34cf96aad1f1", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "6460fb5a-bbb9-400b-aba7-85cbe8666341", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "c2093410-4c63-4436-9294-535c492912dc", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "f9ef3de5-1ad1-4c9c-92bd-62a070e13ba2", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "d75363d0-aed5-4fea-a97f-d0c1adb4fa63", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "kid": [ + "ea50f128-6340-4f3e-8050-1e79ba559121" + ], + "secret": [ + "QYJipEnEXtsqk2OqtX1oyyQrANj03UWaGGf1p4yD28-x4MMOSxXVpEVTwZZvpgJeB-4L2lztTaJxVhANSO1lDQ" + ], + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "594b0771-b860-4aa8-8a81-4b29ee5ac384", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEowIBAAKCAQEAsZSMHIVGaorVvBfVYzTAHzXO7/ilxcjty/hayFAB3C1CkEnKeYIs2hUv8s3DFrG26GBFRWI+HVX8yCCuwksDkF4kB6ZxpNV/NCwt4+Hv1Fr/A58eVHkriXajlOE9LUkvrSd04bGGD+SplAgCweQzVcHF5Iy4bvsIJ8ow5WKJ7dGtJlnU2ddBEdja2KhlFWBdqn2YCdvpuLGEYNGr5/ANBM6evcv/bU3tVdq64TPSC13kw/QicKzAU6H+4k8reIAgaEhtudqb6sWI9G0JiSPtb98psTZMFjoGb9yphOFsoSmljA3Ozp2wDVltV/zyk8Aw7J2I8EOCdjcX8BdkkQc6oQIDAQABAoIBABEdhJ2RGNbW97+vumDb6jJ34LCPUgbslULF9pX85BkBAbvfaNTqP4FrblokC8wJp9vgv3xu+hagvYLaZ42RZlAJSsaz+5sL+r0gDvI6Sf+5H4ANW4J/xTr0BNMqHFfbiG1Tcrf4ALhSbSe31/AxGuOGkBi1mWcU6dXP7oOFSk7x79FVmirB0bKGGwd2TNbrmtBSiDU33vUPxwGnDSsmw8TVHyjISiM0BfvVkS9RrBGqEnNm0iKccukRgengrqCK8D4fq65YPrQQQ9o9I50eU0qHoCiVJyNC2+MBpiOniShLO8jyiLAuhDKDfoKis5C6Hqm1yyf2PzwB1rIjV5XVf2ECgYEA6JiWMHHKfyR3QNvGjRp3Jfz/WQvLGXZCwVDowhlNRtfvGC56tEC+QAWxM/4l12u1NMyYSDeF44tYpzq0dGtVkTWTzQKNczTkGhValNMgFEUa8pwumBVVfrfLllA0VGrMW6fEAr8ta2gFODOWoViLSD0s0Jmu6CpdKSUzafcnMjkCgYEAw3LQ2sLgGL8LajjPl++LgWcKRZF3M80IFF2fE78I80zVow4x6Ei+EyrvObmJ5necXKkRA7o7h/7Xb+ATy2h0ZCdr/OXpmR/yGASfUdNXDrtQ0nYF6TNz1Xqadi9cGv7YKy8SOgbB0SM5S6aRy+ouoaUwNCSsWDleAgEOQvpzq6kCgYBrPv/xMmaWHTBHXY69PPi3MWJjooZxJRA+ppnL9XKmOaZq1fOJ7VhLmNRODt9P5r/UqomEsuUvN+8WnIDcNSltHPEbVBP4jOioBjSP7pEaB4sXVmA9i4iyNvjORAj864lysXY1dgTxQzM06MSJfJQsKNjjDhmRvwbZk+eS8nzGMQKBgAFjAipbMZ3bVShmyMpKL9I2OfNuacsbTFBgra1FMLoRNH7Yre/4/ChEqLffIiRZeumJZY6CNsPrQfoQO/O4hQLk6LY9p1+nw176QWsiNb7sA1HK9pXGAK9mFEx8X4ntfvknd1ikDaH/PvvTbbtlqPkKpAHqtLJXjdwzx7cf8cwpAoGBALxwuNYA7NBV8qRzTsLfSGoZEfl6jQogD3GR+EmFswIEYzdp8Mp+VwcpBjP5D28v9kFKa8dCe7TU9aY1JZm5W+N3ZqfhLp6TNepKVpRsyKsF4Pjz9fDZu1Q9AU81ImSO4w6FkLqQc069w7M5O/tT4/WjYbfqEp16UkP77Gh1F4O6" + ], + "keyUse": [ + "SIG" + ], + "certificate": [ + "MIIClzCCAX8CBgGEV1OLDTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARkZW1vMB4XDTIyMTEwODEyNTgyM1oXDTMyMTEwODEzMDAwM1owDzENMAsGA1UEAwwEZGVtbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGUjByFRmqK1bwX1WM0wB81zu/4pcXI7cv4WshQAdwtQpBJynmCLNoVL/LNwxaxtuhgRUViPh1V/MggrsJLA5BeJAemcaTVfzQsLePh79Ra/wOfHlR5K4l2o5ThPS1JL60ndOGxhg/kqZQIAsHkM1XBxeSMuG77CCfKMOViie3RrSZZ1NnXQRHY2tioZRVgXap9mAnb6bixhGDRq+fwDQTOnr3L/21N7VXauuEz0gtd5MP0InCswFOh/uJPK3iAIGhIbbnam+rFiPRtCYkj7W/fKbE2TBY6Bm/cqYThbKEppYwNzs6dsA1ZbVf88pPAMOydiPBDgnY3F/AXZJEHOqECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdo+c58iEquMpcBiMQ44lhSnroyrRwHazMxQ/8fHUUh2QxNwlGynGPfhMNyUtn06sPZqabV1ixTDfb6NbEpJK/HN3meWUNl4I4i5Zabew5DuUCh/BLRUbgOApsoRyHabDlR68inuXIPaP4M8lOfQsZO2/xNuGnr/eedKIR1WotXtmm2WJv79A9tJQkplizS78HoCa+HlyP1UAuAUDO0IZsJwY8CbKq1wgrhs9by8amdzZRBVILuDnuqEqeRxSY4o4BOvtM7TG5aA0iBVQc473NT1IvY10ojW6zs/ahqs0yG44+W2aBG35DvoDNVqP9Lw1vaTAD7OHdzQGhREz07cemA==" + ], + "priority": [ + "100" + ] + } + }, + { + "id": "da244786-6bf6-40c4-b7c3-094f883d969e", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "kid": [ + "6ee413ed-3b74-458f-acb7-a89d5e05f0b9" + ], + "secret": [ + "sANwaoZOUoMcaPlAr5U1sQ" + ], + "priority": [ + "100" + ] + } + }, + { + "id": "1eeba879-03e8-4aad-b91a-0159cf513d73", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEowIBAAKCAQEA4FmIA8jfFAMVm8BuINWDWCn//Xzlk48yoUly1kP8RXriGOYfXrQQTqae0nhBBqlaJFBXAjI41UGJE9+4j23/aGp8vcTTUy9M4qBl9jlt9Pqz178/KaMDuBkeb3TV8PbR6PXrCSuWOuT3LJaXWEitpeMM/+dFSOxD4M1LWOwoQltSlmiZblcb5NsRal60sHvFUA5ecp35jW7Rj+xAQKR7QxgOr8rf83P9NPWYww4+lbKdbgnD2IMfdzZ9soSiHX4WtRrl1dPAqnihfEeaOoDXXTa1m49Q4xORnMVs6E15A+Zviu0xhR1385GT7sHLhZbSh+oLH4nx9jFinry+AgGlUwIDAQABAoIBABbvIBfe82r8y7sxxzBFE1myZXBY0bEtbMwPEZW0unex0aYg9Cj+uEIKB2dVkrQnIMdgjRx03Nl0CxrEfn3vDTJz3E+b7Mxuo+nw4qtygHqQHE1cSA0uFGW/75wOMgahfKDXbtDvqzpXCKt+s3b7awDvvnb0geEsAd5bri2napApy7qEg8iD04NhJhPj+nnoZ/jWTs3N++Dy/a0Y4/TsmIJXjtOY4JUcbYSJDBgFW0hikU/JKRMYXRZNCOzgDlUO+ejxKR2/HJjNK3ynf8Z5seROvVLI6sDTAA20b/PQLW4yEC7BtjWor8zag5tN6ZORdf4tZrsWLAiSAU2NPZepRx0CgYEA9duql4nPIs9Vf7GBZeVJd8Vl5z4WuAbaLgZ0TxaUwU83b9oaw1JmQgZ7gOCDgBkdECJk7iZAgdCCDKyFZyq+aeE8jn2uWXXrh/nMw2rXtAu3K464oyN2kWd4d7vinEsm7B5Vz1tiT20uuACS8AWi63pdBsx7yIsoq/yTantSMN0CgYEA6Zq7A++W2vwCWf0bzuwZ8ozObHafr3awgnEzlD2CXUW0mBC3ptxbo9+ug9SnZC7UMVNEqTVArd2mFZYEnDZGvGbsiN7Qnfw3foKdNpvxMP+CfuQw7vxmCa1JOd5nLz6zKBEPGjIuCy0+wB6ASpUQNxUQQkQoC7L8Qt5cysUSM+8CgYAiSM6iMSp8bTM8ClHEFtRG6nUKaSMb6IC2WFoRyVFXH6fYZi7DPBNcc7D3SNetnlLqNBGlEBqAv8XS5J/5wgEpnKooKKiOex4sKQ5/1b9csSGK5m0i+sgHAMnQ0JeKOgSkepp2vwSXlN8l85aJ+A8/DSI513wPfDBgw2j/OVE91QKBgCnanTNBVAf8Kvewj7DtQGDitYFdZ5LqcwmL+q/OrXLEsGymYiE1Tf34b64TBcK/WSlVP/IJJoOAOOeZL05FszrCPhLvyPTlYZP7FuvX2MjsnpbZj6Lh+e416+7AWEBwvWyqUchhwTojayDE1juGpZcY4Qbea0ZdVTEt4fY6hN5lAoGBAIjrfcjMELZqlLEECwjvTgPmYqTgyJKgVw/SrYBvPpNNqVeIwb62FK19BjXuoIjecXBX8jvefGNa6qTw/OjZLk7JNNbbOdJ1PFp8GkD+e0PPUoISSY0+rAq77geLLfZbcZUwoE0UqKwSYcz0w549aUVKc/5x3tHOxEb7UcWWS5DY" + ], + "keyUse": [ + "ENC" + ], + "certificate": [ + "MIIClzCCAX8CBgGEV1OMBzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARkZW1vMB4XDTIyMTEwODEyNTgyM1oXDTMyMTEwODEzMDAwM1owDzENMAsGA1UEAwwEZGVtbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBZiAPI3xQDFZvAbiDVg1gp//185ZOPMqFJctZD/EV64hjmH160EE6mntJ4QQapWiRQVwIyONVBiRPfuI9t/2hqfL3E01MvTOKgZfY5bfT6s9e/PymjA7gZHm901fD20ej16wkrljrk9yyWl1hIraXjDP/nRUjsQ+DNS1jsKEJbUpZomW5XG+TbEWpetLB7xVAOXnKd+Y1u0Y/sQECke0MYDq/K3/Nz/TT1mMMOPpWynW4Jw9iDH3c2fbKEoh1+FrUa5dXTwKp4oXxHmjqA1102tZuPUOMTkZzFbOhNeQPmb4rtMYUdd/ORk+7By4WW0ofqCx+J8fYxYp68vgIBpVMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAwPwABXVkSlRYqA9sMKJrw9piGH8tkwf6wQeAFhQsInbDzXLeuLt0A3gSuh5nL2zOGcxXddIK4IgUSqI+DlFlFsSkVqHFrQBAdIVRXsYFvGbARKhVuInHlpaOy6Y/VC6opL1BnqsmUOPEv7pk4Nhf/z7y5yZfTUaGiD+K1KA/mEf56NytOFJYsxiCZaAGX6BYIavRJp3YKPAcsNlJS//1G4meOlYcx5HiTA+qY/spc7vPeKuowSh3v26x4tgypLqoD0BAS5KrK4PEVaQM0IcBC3jrIY+7dGbE1Z374nm+1FDU7md48TJdI0c75r60cpBnHUH2bLJo2Z0ezrEojP6mvQ==" + ], + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "95570b1f-da9e-42a0-9532-aa9b690b04cb", + "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": "854f25b9-b37b-468a-b8ac-1e0da29133ba", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "ebcd0954-f08f-4c69-970e-43569320bb07", + "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": "cd4b03ca-1a0f-4a9b-8bd9-388e0459d7ac", + "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": "692bd371-d43a-440b-886b-2a0884dd5b9f", + "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": "dce38371-4f6b-45d1-94b0-5e91182c31fd", + "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": "357541a3-2499-42da-9ef0-db129f38f39b", + "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": "9c37a124-af5e-44a2-b619-b3a88e1033c6", + "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": "94355d8f-b66f-405d-80e5-55ec030fb18c", + "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": "8fcdac85-ec7e-4a3b-908a-4f00978d4724", + "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": "cd9ce79e-ba0b-4d58-8249-40f8b2139a56", + "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": "40f9ac10-edc0-46a6-b020-ba1785199911", + "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": "0ac19480-8675-4679-b222-64508c62bcdb", + "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": "9fd2aa99-79d7-435b-a852-5c18fad28546", + "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": "da0a8a3d-9643-4746-b42d-2e8c915f76e7", + "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": "5eea7962-6fe5-4b69-8401-313afd0f6559", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "f80d7c69-9314-4f4b-a61a-28a73e2d0879", + "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": "deffbcd6-aa05-40e2-9bca-2ed50be54bd6", + "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-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "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 + } + ] + }, + { + "id": "0b06e91d-77f8-4d8e-a55b-21caf478524a", + "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": "d51e394a-e9a8-495b-ac30-3adb1e66a369", + "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": "3c1657ce-3153-4718-bbec-9d0c508eb8a8", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "70a33e93-226e-492e-b119-f164e2cc457b", + "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": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5" + }, + "keycloakVersion": "19.0.3", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/src/main/resources/reference-realms/19.0.3/client.json b/src/main/resources/reference-realms/19.0.3/client.json deleted file mode 100644 index 995372ec9..000000000 --- a/src/main/resources/reference-realms/19.0.3/client.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "id" : "caa6606a-6056-475c-af05-8b0365bb8164", - "clientId" : "reference-client", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "secret" : "q0t682YLCCk2qd5dntjtcniGozLXIZ7h", - "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" : "1667920370" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : -1, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ], - "access" : { - "view" : true, - "configure" : true, - "manage" : true - } -} diff --git a/src/main/resources/reference-realms/19.0.3/realm.json b/src/main/resources/reference-realms/19.0.3/realm.json deleted file mode 100644 index f121b05f9..000000000 --- a/src/main/resources/reference-realms/19.0.3/realm.json +++ /dev/null @@ -1,1770 +0,0 @@ -{ - "id" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", - "realm" : "REALM_NAME_PLACEHOLDER", - "notBefore" : 0, - "defaultSignatureAlgorithm" : "RS256", - "revokeRefreshToken" : false, - "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, - "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, - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "roles" : { - "realm" : [ { - "id" : "0825e37b-b5bb-413d-9c1c-23457f17cb66", - "name" : "default-roles-REALM_NAME_PLACEHOLDER", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "manage-account", "view-profile" ] - } - }, - "clientRole" : false, - "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", - "attributes" : { } - }, { - "id" : "08b30ae4-52ba-45a9-b723-07e6ac57ae2f", - "name" : "offline_access", - "description" : "${role_offline-access}", - "composite" : false, - "clientRole" : false, - "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", - "attributes" : { } - }, { - "id" : "372ddfc2-cd6e-4ca3-a30f-d39cb92ac12d", - "name" : "uma_authorization", - "description" : "${role_uma_authorization}", - "composite" : false, - "clientRole" : false, - "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8", - "attributes" : { } - } ], - "client" : { - "realm-management" : [ { - "id" : "caf6ae68-6a16-4dfa-9a71-1149ceb8f79c", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "913aeddb-8be9-4060-86c7-30cf0fa892ee", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "15ee649c-ef5f-4fe8-a6c1-67a9db14b0c5", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-clients" ] - } - }, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "5d56f881-d3ff-49ad-929c-214afd641f8f", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "0ba58546-8487-4508-b81f-0735432accf2", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "f6f1b3c0-5d29-41b4-b2ec-cfab60e28a3b", - "name" : "query-users", - "description" : "${role_query-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "2c6ddc11-206e-4e82-b894-9fca7cc85866", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "f9917916-7aca-413b-b7bc-452d3fca7a48", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "ad1d551b-3f03-445d-8ac0-51b1196413f0", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-groups", "query-users" ] - } - }, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "f645cca6-9d25-40f0-8ce9-d988a04d9bec", - "name" : "create-client", - "description" : "${role_create-client}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "ce56402a-8925-4750-9269-6b035cfa334f", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "1e70f63f-0883-4452-bb1b-f179bf3c8c30", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "5cef1f90-8af8-4445-b6a3-05d6eb60c46c", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "e87e4aa8-8c15-44ba-9622-eec4eeae1997", - "name" : "query-groups", - "description" : "${role_query-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "baa31234-e4ed-4821-a696-6ccd686b5e1f", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "0d7127a0-cd7b-478c-9156-629fc321bca4", - "name" : "query-realms", - "description" : "${role_query-realms}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "99d07b51-633c-4d25-9dc1-f779a219c246", - "name" : "realm-admin", - "description" : "${role_realm-admin}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "manage-users", "manage-identity-providers", "view-clients", "query-clients", "view-events", "query-users", "view-realm", "manage-events", "view-users", "create-client", "manage-authorization", "impersonation", "manage-realm", "view-identity-providers", "query-groups", "query-realms", "manage-clients", "view-authorization" ] - } - }, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "118c289f-0b71-4edf-ac2a-7016cc0c674d", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - }, { - "id" : "30f43104-6914-4e4f-9f6d-219daf2f7991", - "name" : "view-authorization", - "description" : "${role_view-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "attributes" : { } - } ], - "security-admin-console" : [ ], - "admin-cli" : [ ], - "account-console" : [ ], - "broker" : [ { - "id" : "6045a69a-aec3-4a11-b4c3-3cae53f7a914", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false, - "clientRole" : true, - "containerId" : "6eed48a3-64bd-48ed-ac7c-607f8e724258", - "attributes" : { } - } ], - "account" : [ { - "id" : "4e27eb76-06ea-4547-ab5d-de8f9c745597", - "name" : "manage-consent", - "description" : "${role_manage-consent}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "view-consent" ] - } - }, - "clientRole" : true, - "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "attributes" : { } - }, { - "id" : "60ae83a6-6ed1-4b4f-81cc-701b9a95ceb9", - "name" : "delete-account", - "description" : "${role_delete-account}", - "composite" : false, - "clientRole" : true, - "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "attributes" : { } - }, { - "id" : "f5f244ba-53de-45d7-9774-d4c915cd4ccb", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } - }, - "clientRole" : true, - "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "attributes" : { } - }, { - "id" : "2abb2ef2-3e4c-43d5-9ab7-b096e19f3a56", - "name" : "view-consent", - "description" : "${role_view-consent}", - "composite" : false, - "clientRole" : true, - "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "attributes" : { } - }, { - "id" : "8eb01f07-3771-4b28-ab78-6f216079b508", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false, - "clientRole" : true, - "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "attributes" : { } - }, { - "id" : "23954dae-8150-4183-8733-bce1349fb0ec", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", - "composite" : false, - "clientRole" : true, - "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "attributes" : { } - }, { - "id" : "734d080d-1f67-48e9-942f-9233f6eca901", - "name" : "view-applications", - "description" : "${role_view-applications}", - "composite" : false, - "clientRole" : true, - "containerId" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "attributes" : { } - } ] - } - }, - "groups" : [ ], - "defaultRole" : { - "id" : "0825e37b-b5bb-413d-9c1c-23457f17cb66", - "name" : "default-roles-REALM_NAME_PLACEHOLDER", - "description" : "${role_default-roles}", - "composite" : true, - "clientRole" : false, - "containerId" : "791bfad8-bdb8-4117-87f3-35a0acf453a8" - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], - "webAuthnPolicyRpEntityName" : "keycloak", - "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyRpId" : "", - "webAuthnPolicyAttestationConveyancePreference" : "not specified", - "webAuthnPolicyAuthenticatorAttachment" : "not specified", - "webAuthnPolicyRequireResidentKey" : "not specified", - "webAuthnPolicyUserVerificationRequirement" : "not specified", - "webAuthnPolicyCreateTimeout" : 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyAcceptableAaguids" : [ ], - "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyPasswordlessRpId" : "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", - "webAuthnPolicyPasswordlessCreateTimeout" : 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], - "scopeMappings" : [ { - "clientScope" : "offline_access", - "roles" : [ "offline_access" ] - } ], - "clientScopeMappings" : { - "account" : [ { - "client" : "account-console", - "roles" : [ "manage-account" ] - } ] - }, - "clients" : [ { - "id" : "6c7757f2-9ed7-4609-8e41-f4316ca8d31e", - "clientId" : "account", - "name" : "${client_account}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/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", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "fe6ddaaf-3a90-4942-ae6a-1f170c87fc3b", - "clientId" : "account-console", - "name" : "${client_account-console}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/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" : "9f385a11-7b42-4f92-ac9b-7d286590a392", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "61ecc83e-d3f3-4b4f-a821-845094b3d9d4", - "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", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "6eed48a3-64bd-48ed-ac7c-607f8e724258", - "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", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "631f9fd2-7539-4190-8983-0e94614c5b73", - "clientId" : "realm-management", - "name" : "${client_realm-management}", - "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", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "05f3367b-492b-40e1-ba0c-f000aa3ad0ef", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "rootUrl" : "${authAdminUrl}", - "baseUrl" : "/admin/REALM_NAME_PLACEHOLDER/console/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/admin/REALM_NAME_PLACEHOLDER/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" : "9d1f5814-fa63-4c36-ae10-747d30f47c69", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - } ], - "clientScopes" : [ { - "id" : "23fc5623-9366-478f-9924-801d71f32489", - "name" : "role_list", - "description" : "SAML role list", - "protocol" : "saml", - "attributes" : { - "consent.screen.text" : "${samlRoleListScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "31541a2f-1c88-43d9-96fe-9f9efbd096d4", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - } ] - }, { - "id" : "1859a4f3-d051-4800-963f-45b624cccd57", - "name" : "web-origins", - "description" : "OpenID Connect scope for add allowed web origins to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" - }, - "protocolMappers" : [ { - "id" : "a7777bff-a046-4fe3-a5a9-a520d79865ec", - "name" : "allowed web origins", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", - "consentRequired" : false, - "config" : { } - } ] - }, { - "id" : "7d011629-d7f1-45ad-a09b-b4decb3d47ed", - "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" : "22a74f8c-4493-4a1a-bf97-f51466e336b3", - "name" : "acr loa level", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "7fb8529d-cb6f-4f31-b4c3-f6be6f74b47d", - "name" : "phone", - "description" : "OpenID Connect built-in scope: phone", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "5bd8bfba-d8a3-4792-aaec-5f0513397193", - "name" : "phone number verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumberVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "89146d36-28d5-4750-933e-75014b203dcf", - "name" : "phone number", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumber", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "01025c61-c655-463d-967d-a45e88368472", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "ad80bcc7-66d4-44c0-b34e-65822a57359a", - "name" : "realm roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "73346462-e569-4679-a57d-fe623bdc5a95", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - }, { - "id" : "3842db0a-837f-4564-9e48-07c87f5d3258", - "name" : "client roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - } ] - }, { - "id" : "f79aab58-4d4a-40ee-b879-a586ff956f12", - "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" : "ccb6bedc-b921-4c83-abe6-13de8c0e9795", - "name" : "groups", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "multivalued" : "true", - "user.attribute" : "foo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" - } - }, { - "id" : "19ae8192-0189-4644-a362-d08a0bce5680", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "upn", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "a0d6d0c2-afd3-4b29-a99f-ff3002866519", - "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" : "1dcbfe97-e4ab-4df7-8517-4eaaa26ea410", - "name" : "profile", - "description" : "OpenID Connect built-in scope: profile", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "8448e164-b72e-406e-b14b-ae5d1e72393a", - "name" : "gender", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "gender", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "gender", - "jsonType.label" : "String" - } - }, { - "id" : "c573ae92-f94a-494e-9403-f875d22d3e8d", - "name" : "profile", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "profile", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "profile", - "jsonType.label" : "String" - } - }, { - "id" : "09f992cc-2d64-4d4d-88ca-59a1b63325e9", - "name" : "website", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "website", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "website", - "jsonType.label" : "String" - } - }, { - "id" : "04658d90-dc3d-4865-ac52-6f16570fcd76", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "true" - } - }, { - "id" : "de199ca1-e720-419f-aeee-e112568f5cff", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "08daf912-8959-4ba5-9c38-ffcd395d6487", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "d76e56a2-6237-46d5-a56b-0ecd1f979e85", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "6bc40007-ab16-48f6-ae81-a66a9062ad5c", - "name" : "picture", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "picture", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "picture", - "jsonType.label" : "String" - } - }, { - "id" : "0385dbe8-587c-49ab-a317-a15b5b52d456", - "name" : "zoneinfo", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "zoneinfo", - "jsonType.label" : "String" - } - }, { - "id" : "28946b3e-0eab-44a5-8eee-d42088072306", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - }, { - "id" : "e61c48dc-d083-4029-92d1-3c7a6f3d8bb9", - "name" : "birthdate", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" - } - }, { - "id" : "62545d7c-ef2e-48a6-bff8-e2e9a2b16c3d", - "name" : "middle name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "middleName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "middle_name", - "jsonType.label" : "String" - } - }, { - "id" : "f1c75501-26b2-4d94-82aa-851e4fa3dd7c", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "046a0b0d-5256-48fe-9b8f-b5193d87ebdd", - "name" : "updated at", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" - } - } ] - }, { - "id" : "d807e61e-3661-44ff-a8cd-285458e6f763", - "name" : "address", - "description" : "OpenID Connect built-in scope: address", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "fede614b-c46e-484c-8dba-9590cd0205fe", - "name" : "address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-address-mapper", - "consentRequired" : false, - "config" : { - "user.attribute.formatted" : "formatted", - "user.attribute.country" : "country", - "user.attribute.postal_code" : "postal_code", - "userinfo.token.claim" : "true", - "user.attribute.street" : "street", - "id.token.claim" : "true", - "user.attribute.region" : "region", - "access.token.claim" : "true", - "user.attribute.locality" : "locality" - } - } ] - }, { - "id" : "c8a4b7a9-c8ba-412b-a17d-e90a3c6393fd", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "de4bafae-e88c-4c2f-9001-311f5c141633", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - }, { - "id" : "9aa11aac-4178-435b-83b3-2d85508e34bd", - "name" : "email verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" - } - } ] - } ], - "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], - "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], - "browserSecurityHeaders" : { - "contentSecurityPolicyReportOnly" : "", - "xContentTypeOptions" : "nosniff", - "xRobotsTag" : "none", - "xFrameOptions" : "SAMEORIGIN", - "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "xXSSProtection" : "1; mode=block", - "strictTransportSecurity" : "max-age=31536000; includeSubDomains" - }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityProviders" : [ ], - "identityProviderMappers" : [ ], - "components" : { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { - "id" : "808ef7c4-b5d1-491c-ade0-559f38287e68", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "2a0ad1a6-d6f9-43cb-8ec1-8913a23339d7", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper" ] - } - }, { - "id" : "3695208c-32af-497b-9af9-5de878749899", - "name" : "Consent Required", - "providerId" : "consent-required", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "b7317013-6157-4099-a1ee-194df4808b2d", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "2464d485-980d-447a-94e2-34cf96aad1f1", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "6460fb5a-bbb9-400b-aba7-85cbe8666341", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] - } - }, { - "id" : "c2093410-4c63-4436-9294-535c492912dc", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper" ] - } - }, { - "id" : "f9ef3de5-1ad1-4c9c-92bd-62a070e13ba2", - "name" : "Max Clients Limit", - "providerId" : "max-clients", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "max-clients" : [ "200" ] - } - } ], - "org.keycloak.keys.KeyProvider" : [ { - "id" : "d75363d0-aed5-4fea-a97f-d0c1adb4fa63", - "name" : "hmac-generated", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "ea50f128-6340-4f3e-8050-1e79ba559121" ], - "secret" : [ "QYJipEnEXtsqk2OqtX1oyyQrANj03UWaGGf1p4yD28-x4MMOSxXVpEVTwZZvpgJeB-4L2lztTaJxVhANSO1lDQ" ], - "priority" : [ "100" ], - "algorithm" : [ "HS256" ] - } - }, { - "id" : "594b0771-b860-4aa8-8a81-4b29ee5ac384", - "name" : "rsa-generated", - "providerId" : "rsa-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEowIBAAKCAQEAsZSMHIVGaorVvBfVYzTAHzXO7/ilxcjty/hayFAB3C1CkEnKeYIs2hUv8s3DFrG26GBFRWI+HVX8yCCuwksDkF4kB6ZxpNV/NCwt4+Hv1Fr/A58eVHkriXajlOE9LUkvrSd04bGGD+SplAgCweQzVcHF5Iy4bvsIJ8ow5WKJ7dGtJlnU2ddBEdja2KhlFWBdqn2YCdvpuLGEYNGr5/ANBM6evcv/bU3tVdq64TPSC13kw/QicKzAU6H+4k8reIAgaEhtudqb6sWI9G0JiSPtb98psTZMFjoGb9yphOFsoSmljA3Ozp2wDVltV/zyk8Aw7J2I8EOCdjcX8BdkkQc6oQIDAQABAoIBABEdhJ2RGNbW97+vumDb6jJ34LCPUgbslULF9pX85BkBAbvfaNTqP4FrblokC8wJp9vgv3xu+hagvYLaZ42RZlAJSsaz+5sL+r0gDvI6Sf+5H4ANW4J/xTr0BNMqHFfbiG1Tcrf4ALhSbSe31/AxGuOGkBi1mWcU6dXP7oOFSk7x79FVmirB0bKGGwd2TNbrmtBSiDU33vUPxwGnDSsmw8TVHyjISiM0BfvVkS9RrBGqEnNm0iKccukRgengrqCK8D4fq65YPrQQQ9o9I50eU0qHoCiVJyNC2+MBpiOniShLO8jyiLAuhDKDfoKis5C6Hqm1yyf2PzwB1rIjV5XVf2ECgYEA6JiWMHHKfyR3QNvGjRp3Jfz/WQvLGXZCwVDowhlNRtfvGC56tEC+QAWxM/4l12u1NMyYSDeF44tYpzq0dGtVkTWTzQKNczTkGhValNMgFEUa8pwumBVVfrfLllA0VGrMW6fEAr8ta2gFODOWoViLSD0s0Jmu6CpdKSUzafcnMjkCgYEAw3LQ2sLgGL8LajjPl++LgWcKRZF3M80IFF2fE78I80zVow4x6Ei+EyrvObmJ5necXKkRA7o7h/7Xb+ATy2h0ZCdr/OXpmR/yGASfUdNXDrtQ0nYF6TNz1Xqadi9cGv7YKy8SOgbB0SM5S6aRy+ouoaUwNCSsWDleAgEOQvpzq6kCgYBrPv/xMmaWHTBHXY69PPi3MWJjooZxJRA+ppnL9XKmOaZq1fOJ7VhLmNRODt9P5r/UqomEsuUvN+8WnIDcNSltHPEbVBP4jOioBjSP7pEaB4sXVmA9i4iyNvjORAj864lysXY1dgTxQzM06MSJfJQsKNjjDhmRvwbZk+eS8nzGMQKBgAFjAipbMZ3bVShmyMpKL9I2OfNuacsbTFBgra1FMLoRNH7Yre/4/ChEqLffIiRZeumJZY6CNsPrQfoQO/O4hQLk6LY9p1+nw176QWsiNb7sA1HK9pXGAK9mFEx8X4ntfvknd1ikDaH/PvvTbbtlqPkKpAHqtLJXjdwzx7cf8cwpAoGBALxwuNYA7NBV8qRzTsLfSGoZEfl6jQogD3GR+EmFswIEYzdp8Mp+VwcpBjP5D28v9kFKa8dCe7TU9aY1JZm5W+N3ZqfhLp6TNepKVpRsyKsF4Pjz9fDZu1Q9AU81ImSO4w6FkLqQc069w7M5O/tT4/WjYbfqEp16UkP77Gh1F4O6" ], - "keyUse" : [ "SIG" ], - "certificate" : [ "MIIClzCCAX8CBgGEV1OLDTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARkZW1vMB4XDTIyMTEwODEyNTgyM1oXDTMyMTEwODEzMDAwM1owDzENMAsGA1UEAwwEZGVtbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGUjByFRmqK1bwX1WM0wB81zu/4pcXI7cv4WshQAdwtQpBJynmCLNoVL/LNwxaxtuhgRUViPh1V/MggrsJLA5BeJAemcaTVfzQsLePh79Ra/wOfHlR5K4l2o5ThPS1JL60ndOGxhg/kqZQIAsHkM1XBxeSMuG77CCfKMOViie3RrSZZ1NnXQRHY2tioZRVgXap9mAnb6bixhGDRq+fwDQTOnr3L/21N7VXauuEz0gtd5MP0InCswFOh/uJPK3iAIGhIbbnam+rFiPRtCYkj7W/fKbE2TBY6Bm/cqYThbKEppYwNzs6dsA1ZbVf88pPAMOydiPBDgnY3F/AXZJEHOqECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdo+c58iEquMpcBiMQ44lhSnroyrRwHazMxQ/8fHUUh2QxNwlGynGPfhMNyUtn06sPZqabV1ixTDfb6NbEpJK/HN3meWUNl4I4i5Zabew5DuUCh/BLRUbgOApsoRyHabDlR68inuXIPaP4M8lOfQsZO2/xNuGnr/eedKIR1WotXtmm2WJv79A9tJQkplizS78HoCa+HlyP1UAuAUDO0IZsJwY8CbKq1wgrhs9by8amdzZRBVILuDnuqEqeRxSY4o4BOvtM7TG5aA0iBVQc473NT1IvY10ojW6zs/ahqs0yG44+W2aBG35DvoDNVqP9Lw1vaTAD7OHdzQGhREz07cemA==" ], - "priority" : [ "100" ] - } - }, { - "id" : "da244786-6bf6-40c4-b7c3-094f883d969e", - "name" : "aes-generated", - "providerId" : "aes-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "6ee413ed-3b74-458f-acb7-a89d5e05f0b9" ], - "secret" : [ "sANwaoZOUoMcaPlAr5U1sQ" ], - "priority" : [ "100" ] - } - }, { - "id" : "1eeba879-03e8-4aad-b91a-0159cf513d73", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEowIBAAKCAQEA4FmIA8jfFAMVm8BuINWDWCn//Xzlk48yoUly1kP8RXriGOYfXrQQTqae0nhBBqlaJFBXAjI41UGJE9+4j23/aGp8vcTTUy9M4qBl9jlt9Pqz178/KaMDuBkeb3TV8PbR6PXrCSuWOuT3LJaXWEitpeMM/+dFSOxD4M1LWOwoQltSlmiZblcb5NsRal60sHvFUA5ecp35jW7Rj+xAQKR7QxgOr8rf83P9NPWYww4+lbKdbgnD2IMfdzZ9soSiHX4WtRrl1dPAqnihfEeaOoDXXTa1m49Q4xORnMVs6E15A+Zviu0xhR1385GT7sHLhZbSh+oLH4nx9jFinry+AgGlUwIDAQABAoIBABbvIBfe82r8y7sxxzBFE1myZXBY0bEtbMwPEZW0unex0aYg9Cj+uEIKB2dVkrQnIMdgjRx03Nl0CxrEfn3vDTJz3E+b7Mxuo+nw4qtygHqQHE1cSA0uFGW/75wOMgahfKDXbtDvqzpXCKt+s3b7awDvvnb0geEsAd5bri2napApy7qEg8iD04NhJhPj+nnoZ/jWTs3N++Dy/a0Y4/TsmIJXjtOY4JUcbYSJDBgFW0hikU/JKRMYXRZNCOzgDlUO+ejxKR2/HJjNK3ynf8Z5seROvVLI6sDTAA20b/PQLW4yEC7BtjWor8zag5tN6ZORdf4tZrsWLAiSAU2NPZepRx0CgYEA9duql4nPIs9Vf7GBZeVJd8Vl5z4WuAbaLgZ0TxaUwU83b9oaw1JmQgZ7gOCDgBkdECJk7iZAgdCCDKyFZyq+aeE8jn2uWXXrh/nMw2rXtAu3K464oyN2kWd4d7vinEsm7B5Vz1tiT20uuACS8AWi63pdBsx7yIsoq/yTantSMN0CgYEA6Zq7A++W2vwCWf0bzuwZ8ozObHafr3awgnEzlD2CXUW0mBC3ptxbo9+ug9SnZC7UMVNEqTVArd2mFZYEnDZGvGbsiN7Qnfw3foKdNpvxMP+CfuQw7vxmCa1JOd5nLz6zKBEPGjIuCy0+wB6ASpUQNxUQQkQoC7L8Qt5cysUSM+8CgYAiSM6iMSp8bTM8ClHEFtRG6nUKaSMb6IC2WFoRyVFXH6fYZi7DPBNcc7D3SNetnlLqNBGlEBqAv8XS5J/5wgEpnKooKKiOex4sKQ5/1b9csSGK5m0i+sgHAMnQ0JeKOgSkepp2vwSXlN8l85aJ+A8/DSI513wPfDBgw2j/OVE91QKBgCnanTNBVAf8Kvewj7DtQGDitYFdZ5LqcwmL+q/OrXLEsGymYiE1Tf34b64TBcK/WSlVP/IJJoOAOOeZL05FszrCPhLvyPTlYZP7FuvX2MjsnpbZj6Lh+e416+7AWEBwvWyqUchhwTojayDE1juGpZcY4Qbea0ZdVTEt4fY6hN5lAoGBAIjrfcjMELZqlLEECwjvTgPmYqTgyJKgVw/SrYBvPpNNqVeIwb62FK19BjXuoIjecXBX8jvefGNa6qTw/OjZLk7JNNbbOdJ1PFp8GkD+e0PPUoISSY0+rAq77geLLfZbcZUwoE0UqKwSYcz0w549aUVKc/5x3tHOxEb7UcWWS5DY" ], - "keyUse" : [ "ENC" ], - "certificate" : [ "MIIClzCCAX8CBgGEV1OMBzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARkZW1vMB4XDTIyMTEwODEyNTgyM1oXDTMyMTEwODEzMDAwM1owDzENMAsGA1UEAwwEZGVtbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBZiAPI3xQDFZvAbiDVg1gp//185ZOPMqFJctZD/EV64hjmH160EE6mntJ4QQapWiRQVwIyONVBiRPfuI9t/2hqfL3E01MvTOKgZfY5bfT6s9e/PymjA7gZHm901fD20ej16wkrljrk9yyWl1hIraXjDP/nRUjsQ+DNS1jsKEJbUpZomW5XG+TbEWpetLB7xVAOXnKd+Y1u0Y/sQECke0MYDq/K3/Nz/TT1mMMOPpWynW4Jw9iDH3c2fbKEoh1+FrUa5dXTwKp4oXxHmjqA1102tZuPUOMTkZzFbOhNeQPmb4rtMYUdd/ORk+7By4WW0ofqCx+J8fYxYp68vgIBpVMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAwPwABXVkSlRYqA9sMKJrw9piGH8tkwf6wQeAFhQsInbDzXLeuLt0A3gSuh5nL2zOGcxXddIK4IgUSqI+DlFlFsSkVqHFrQBAdIVRXsYFvGbARKhVuInHlpaOy6Y/VC6opL1BnqsmUOPEv7pk4Nhf/z7y5yZfTUaGiD+K1KA/mEf56NytOFJYsxiCZaAGX6BYIavRJp3YKPAcsNlJS//1G4meOlYcx5HiTA+qY/spc7vPeKuowSh3v26x4tgypLqoD0BAS5KrK4PEVaQM0IcBC3jrIY+7dGbE1Z374nm+1FDU7md48TJdI0c75r60cpBnHUH2bLJo2Z0ezrEojP6mvQ==" ], - "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] - } - } ] - }, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "id" : "95570b1f-da9e-42a0-9532-aa9b690b04cb", - "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" : "854f25b9-b37b-468a-b8ac-1e0da29133ba", - "alias" : "Authentication Options", - "description" : "Authentication options.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "basic-auth", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "basic-auth-otp", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "ebcd0954-f08f-4c69-970e-43569320bb07", - "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" : "cd4b03ca-1a0f-4a9b-8bd9-388e0459d7ac", - "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" : "692bd371-d43a-440b-886b-2a0884dd5b9f", - "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" : "dce38371-4f6b-45d1-94b0-5e91182c31fd", - "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" : "357541a3-2499-42da-9ef0-db129f38f39b", - "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" : "9c37a124-af5e-44a2-b619-b3a88e1033c6", - "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" : "94355d8f-b66f-405d-80e5-55ec030fb18c", - "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" : "8fcdac85-ec7e-4a3b-908a-4f00978d4724", - "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" : "cd9ce79e-ba0b-4d58-8249-40f8b2139a56", - "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" : "40f9ac10-edc0-46a6-b020-ba1785199911", - "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" : "0ac19480-8675-4679-b222-64508c62bcdb", - "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" : "9fd2aa99-79d7-435b-a852-5c18fad28546", - "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" : "da0a8a3d-9643-4746-b42d-2e8c915f76e7", - "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" : "5eea7962-6fe5-4b69-8401-313afd0f6559", - "alias" : "http challenge", - "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "no-cookie-redirect", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Authentication Options", - "userSetupAllowed" : false - } ] - }, { - "id" : "f80d7c69-9314-4f4b-a61a-28a73e2d0879", - "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" : "deffbcd6-aa05-40e2-9bca-2ed50be54bd6", - "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-profile-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 40, - "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 - } ] - }, { - "id" : "0b06e91d-77f8-4d8e-a55b-21caf478524a", - "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" : "d51e394a-e9a8-495b-ac30-3adb1e66a369", - "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" : "3c1657ce-3153-4718-bbec-9d0c508eb8a8", - "alias" : "create unique user config", - "config" : { - "require.password.update.after.registration" : "false" - } - }, { - "id" : "70a33e93-226e-492e-b119-f164e2cc457b", - "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" : "update_user_locale", - "name" : "Update User Locale", - "providerId" : "update_user_locale", - "enabled" : true, - "defaultAction" : false, - "priority" : 1000, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients", - "dockerAuthenticationFlow" : "docker auth", - "attributes" : { - "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaExpiresIn" : "120", - "cibaAuthRequestedUserHint" : "login_hint", - "oauth2DeviceCodeLifespan" : "600", - "oauth2DevicePollingInterval" : "5", - "parRequestUriLifespan" : "60", - "cibaInterval" : "5" - }, - "keycloakVersion" : "19.0.3", - "userManagedAccessAllowed" : false, - "clientProfiles" : { - "profiles" : [ ] - }, - "clientPolicies" : { - "policies" : [ ] - } -} From 759fa1bb8cfa1558ca0a1907b45f8f2a4dbf04b8 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 12 Dec 2022 10:14:43 +0100 Subject: [PATCH 17/49] ClientScope normalization --- .../NormalizationConfiguration.java | 5 +- .../ClientScopeNormalizationService.java | 106 ++++++++++++++++++ .../normalize/RealmNormalizationService.java | 6 + .../ScopeMappingNormalizationService.java | 2 +- 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 904a6bd13..0a100de58 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -27,6 +27,7 @@ import org.javers.core.diff.ListCompareAlgorithm; import org.javers.core.metamodel.clazz.EntityDefinition; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -94,7 +95,7 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(RealmRepresentation.class, "realm", realmIgnoredProperties)) .registerEntity(new EntityDefinition(ClientRepresentation.class, "clientId", List.of("id", "authorizationSettings", "protocolMappers"))) - .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))); - + .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) + .registerEntity(new EntityDefinition(ClientScopeRepresentation.class, "name", List.of("id", "protocolMappers"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java new file mode 100644 index 000000000..b78067b0f --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java @@ -0,0 +1,106 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.util.JaversUtil; +import org.javers.core.Javers; +import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class ClientScopeNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(ClientScopeNormalizationService.class); + + private final Javers unOrderedJavers; + private final JaversUtil javersUtil; + + public ClientScopeNormalizationService(Javers unOrderedJavers, JaversUtil javersUtil) { + this.unOrderedJavers = unOrderedJavers; + this.javersUtil = javersUtil; + } + + public List normalizeClientScopes(List exportedScopes, + List baselineScopes) { + List exportedOrEmpty = exportedScopes == null ? List.of() : exportedScopes; + List baselineOrEmpty = baselineScopes == null ? List.of() : baselineScopes; + + var exportedMap = exportedOrEmpty.stream().collect(Collectors.toMap(ClientScopeRepresentation::getName, + Function.identity())); + var baselineMap = baselineOrEmpty.stream().collect(Collectors.toMap(ClientScopeRepresentation::getName, + Function.identity())); + + var normalizedScopes = new ArrayList(); + for (var entry : baselineMap.entrySet()) { + var scopeName = entry.getKey(); + var baselineScope = entry.getValue(); + var exportedScope = exportedMap.remove(scopeName); + + if (exportedScope == null) { + logger.warn("Default realm clientScope '{}' was deleted in exported realm. It may be reintroduced during import!", scopeName); + continue; + } + + if (clientScopeChanged(exportedScope, baselineScope)) { + var normalizedScope = new ClientScopeRepresentation(); + var diff = unOrderedJavers.compare(baselineScope, exportedScope); + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedScope, change); + } + normalizedScope.setProtocolMappers(exportedScope.getProtocolMappers()); + for (var mapper : normalizedScope.getProtocolMappers()) { + mapper.setId(null); + } + normalizedScopes.add(normalizedScope); + } + } + + for (var scope : exportedMap.values()) { + scope.setId(null); + normalizedScopes.add(scope); + } + return normalizedScopes.isEmpty() ? null : normalizedScopes; + } + + public boolean clientScopeChanged(ClientScopeRepresentation exportedScope, ClientScopeRepresentation baselineScope) { + if (unOrderedJavers.compare(baselineScope, exportedScope).hasChanges()) { + return true; + } + + return protocolMappersChanged(exportedScope.getProtocolMappers(), baselineScope.getProtocolMappers()); + } + + public boolean protocolMappersChanged(List exportedMappers, List baselineMappers) { + return unOrderedJavers.compareCollections(baselineMappers == null ? List.of() : baselineMappers, + exportedMappers == null ? List.of() : exportedMappers, ProtocolMapperRepresentation.class).hasChanges(); + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 3a9e9ea72..1ec6a6ef6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -48,6 +48,7 @@ public class RealmNormalizationService { private final ClientNormalizationService clientNormalizationService; private final ScopeMappingNormalizationService scopeMappingNormalizationService; private final ProtocolMapperNormalizationService protocolMapperNormalizationService; + private final ClientScopeNormalizationService clientScopeNormalizationService; private final JaversUtil javersUtil; @Autowired @@ -57,6 +58,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC ClientNormalizationService clientNormalizationService, ScopeMappingNormalizationService scopeMappingNormalizationService, ProtocolMapperNormalizationService protocolMapperNormalizationService, + ClientScopeNormalizationService clientScopeNormalizationService, JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; this.javers = javers; @@ -64,6 +66,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC this.clientNormalizationService = clientNormalizationService; this.scopeMappingNormalizationService = scopeMappingNormalizationService; this.protocolMapperNormalizationService = protocolMapperNormalizationService; + this.clientScopeNormalizationService = clientScopeNormalizationService; this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? @@ -123,6 +126,9 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { minimizedRealm.setProtocolMappers(protocolMapperNormalizationService.normalizeProtocolMappers(exportedRealm.getProtocolMappers(), baselineRealm.getProtocolMappers())); + + minimizedRealm.setClientScopes(clientScopeNormalizationService.normalizeClientScopes(exportedRealm.getClientScopes(), + baselineRealm.getClientScopes())); return minimizedRealm; } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java index 6bf35666a..d72763cba 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java @@ -63,7 +63,7 @@ public List normalizeScopeMappings(RealmRepresentati baselineMappingsMap.put(clientScope, baselineRealmMapping); var exportedMapping = exportedMappingsMap.get(clientScope); if (exportedMapping == null) { - logger.warn("Default realm scopeMapping '{}' was deleted in exported realm. It will be reintroduced during import!", clientScope); + logger.warn("Default realm scopeMapping '{}' was deleted in exported realm. It may be reintroduced during import!", clientScope); continue; } // If the exported scopeMapping is different from the one that is present in the baseline realm, export it in the yml From 105969df00a6421e7d44c089ac10c4550ee7ae5f Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 12 Dec 2022 10:43:47 +0100 Subject: [PATCH 18/49] Allow JSON export --- .../KeycloakConfigNormalizationRunner.java | 17 ++++++++++++++--- .../NormalizationConfiguration.java | 14 +++++++++++--- .../NormalizationConfigProperties.java | 14 +++++++++++++- .../ClientScopeNormalizationService.java | 2 ++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java index e0e36c07d..591f03705 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java @@ -20,6 +20,7 @@ package de.adorsys.keycloak.config; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import de.adorsys.keycloak.config.properties.NormalizationKeycloakConfigProperties; @@ -40,6 +41,8 @@ import java.text.SimpleDateFormat; import java.util.Date; +import static de.adorsys.keycloak.config.properties.NormalizationConfigProperties.OutputFormat.YAML; + @Component @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") @EnableConfigurationProperties({NormalizationConfigProperties.class, NormalizationKeycloakConfigProperties.class}) @@ -52,17 +55,20 @@ public class KeycloakConfigNormalizationRunner implements CommandLineRunner, Exi private final KeycloakExportProvider exportProvider; private final NormalizationConfigProperties normalizationConfigProperties; private final YAMLMapper yamlMapper; + private final ObjectMapper objectMapper; private int exitCode; @Autowired public KeycloakConfigNormalizationRunner(RealmNormalizationService normalizationService, KeycloakExportProvider exportProvider, NormalizationConfigProperties normalizationConfigProperties, - YAMLMapper yamlMapper) { + YAMLMapper yamlMapper, + ObjectMapper objectMapper) { this.normalizationService = normalizationService; this.exportProvider = exportProvider; this.normalizationConfigProperties = normalizationConfigProperties; this.yamlMapper = yamlMapper; + this.objectMapper = objectMapper; } @Override @@ -84,9 +90,14 @@ public void run(String... args) throws Exception { logger.info("Normalizing file '{}'", export.getKey()); for (var realm : export.getValue()) { var normalizedRealm = normalizationService.normalizeRealm(realm); - var outputFile = outputLocation.resolve(String.format("%s.yaml", normalizedRealm.getRealm())); + var suffix = normalizationConfigProperties.getOutputFormat() == YAML ? "yaml" : "json"; + var outputFile = outputLocation.resolve(String.format("%s.%s", normalizedRealm.getRealm(), suffix)); try (var os = new FileOutputStream(outputFile.toFile())) { - yamlMapper.writeValue(os, normalizedRealm); + if (normalizationConfigProperties.getOutputFormat() == YAML) { + yamlMapper.writeValue(os, normalizedRealm); + } else { + objectMapper.writeValue(os, normalizedRealm); + } } } } diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 0a100de58..43c809bfb 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -21,6 +21,7 @@ package de.adorsys.keycloak.config.configuration; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import org.javers.core.Javers; import org.javers.core.JaversBuilder; @@ -57,9 +58,16 @@ public Javers unOrderedJavers() { @Bean public YAMLMapper yamlMapper() { - YAMLMapper mapper = new YAMLMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - return mapper; + var ym = new YAMLMapper(); + ym.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return ym; + } + + @Bean + public ObjectMapper objectMapper() { + var om = new ObjectMapper(); + om.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return om; } private JaversBuilder commonJavers() { diff --git a/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java index 4d9925df4..2d6e85b88 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java @@ -37,14 +37,22 @@ public class NormalizationConfigProperties { @Valid private final NormalizationFilesProperties files; - public NormalizationConfigProperties(@DefaultValue NormalizationFilesProperties files) { + private final OutputFormat outputFormat; + + public NormalizationConfigProperties(@DefaultValue NormalizationFilesProperties files, + @DefaultValue("yaml") OutputFormat outputFormat) { this.files = files; + this.outputFormat = outputFormat; } public NormalizationFilesProperties getFiles() { return files; } + public OutputFormat getOutputFormat() { + return outputFormat; + } + public static class NormalizationFilesProperties { @NotNull @@ -85,4 +93,8 @@ public String getOutputDirectory() { return outputDirectory; } } + + public enum OutputFormat { + JSON, YAML + } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java index b78067b0f..c44564043 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java @@ -73,6 +73,8 @@ public List normalizeClientScopes(List Date: Mon, 12 Dec 2022 11:19:18 +0100 Subject: [PATCH 19/49] Fix NPE in protocolMapper handling --- .../service/normalize/ClientNormalizationService.java | 9 ++++++--- .../normalize/ClientScopeNormalizationService.java | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index 16d0f699f..2a70078e0 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -102,9 +102,12 @@ public ClientRepresentation normalizeClient(ClientRepresentation client, String for (var change : diff.getChangesByType(PropertyChange.class)) { javersUtil.applyChange(normalizedClient, change); } - normalizedClient.setProtocolMappers(client.getProtocolMappers()); - for (var mapper : normalizedClient.getProtocolMappers()) { - mapper.setId(null); + var mappers = client.getProtocolMappers(); + normalizedClient.setProtocolMappers(mappers); + if (mappers != null) { + for (var mapper : mappers) { + mapper.setId(null); + } } normalizedClient.setAuthorizationSettings(client.getAuthorizationSettings()); normalizedClient.setClientId(clientId); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java index c44564043..0081f69dc 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java @@ -78,9 +78,12 @@ public List normalizeClientScopes(List Date: Mon, 12 Dec 2022 13:57:43 +0100 Subject: [PATCH 20/49] Implement fallback-version parameter --- pom.xml | 8 --- .../NormalizationConfigProperties.java | 10 +++- .../config/provider/BaselineProvider.java | 56 +++++++++++++++---- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index 8023ce41e..e129d6246 100644 --- a/pom.xml +++ b/pom.xml @@ -251,14 +251,6 @@ snakeyaml - - com.github.spotbugs - spotbugs-annotations - ${spotbugs.version} - provided - - - net.logstash.logback diff --git a/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java index 2d6e85b88..a91e4b706 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/NormalizationConfigProperties.java @@ -39,10 +39,14 @@ public class NormalizationConfigProperties { private final OutputFormat outputFormat; + private final String fallbackVersion; + public NormalizationConfigProperties(@DefaultValue NormalizationFilesProperties files, - @DefaultValue("yaml") OutputFormat outputFormat) { + @DefaultValue("yaml") OutputFormat outputFormat, + String fallbackVersion) { this.files = files; this.outputFormat = outputFormat; + this.fallbackVersion = fallbackVersion; } public NormalizationFilesProperties getFiles() { @@ -53,6 +57,10 @@ public OutputFormat getOutputFormat() { return outputFormat; } + public String getFallbackVersion() { + return fallbackVersion; + } + public static class NormalizationFilesProperties { @NotNull diff --git a/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java index d9e04fee2..f9493f4e2 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java @@ -22,37 +22,38 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.adorsys.keycloak.config.exception.NormalizationException; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import de.adorsys.keycloak.config.properties.NormalizationConfigProperties; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; @Component @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") -@SuppressFBWarnings(value = {"NP_LOAD_OF_KNOWN_NULL_VALUE", "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"}, - justification = "Bug in Spotbugs, see https://github.com/spotbugs/spotbugs/issues/1338") public class BaselineProvider { + private static final Logger logger = LoggerFactory.getLogger(BaselineProvider.class); private static final String PLACEHOLDER = "REALM_NAME_PLACEHOLDER"; private final ObjectMapper objectMapper; + private final String fallbackVersion; + @Autowired - public BaselineProvider(ObjectMapper objectMapper) { + public BaselineProvider(ObjectMapper objectMapper, NormalizationConfigProperties normalizationConfigProperties) { this.objectMapper = objectMapper; + this.fallbackVersion = normalizationConfigProperties.getFallbackVersion(); } public RealmRepresentation getRealm(String version, String realmName) { - try (var inputStream = getClass() - .getResourceAsStream(String.format("/baseline/%s/realm/realm.json", version))) { - if (inputStream == null) { - throw new NormalizationException(String.format("Reference realm for version %s does not exist", version)); - } + try (var inputStream = getRealmInputStream(version)) { /* * Replace the placeholder with the realm name to import. This sets some internal values like role names, * baseUrls and redirectUrls so that they don't get picked up as "changes" @@ -65,8 +66,7 @@ public RealmRepresentation getRealm(String version, String realmName) { } public ClientRepresentation getClient(String version, String clientId) { - try (var is = getClass() - .getResourceAsStream(String.format("/baseline/%s/client/client.json", version))) { + try (var is = getClientInputStream(version)) { var client = objectMapper.readValue(is, ClientRepresentation.class); client.setClientId(clientId); return client; @@ -74,4 +74,38 @@ public ClientRepresentation getClient(String version, String clientId) { throw new NormalizationException(String.format("Failed to load baseline client for version %s", version), ex); } } + + public InputStream getRealmInputStream(String version) { + var inputStream = getClass().getResourceAsStream(String.format("/baseline/%s/realm/realm.json", version)); + if (inputStream == null) { + if (fallbackVersion != null) { + logger.warn("Reference realm not found for version {}. Using fallback version {}!", version, fallbackVersion); + inputStream = getClass().getResourceAsStream(String.format("/baseline/%s/realm/realm.json", fallbackVersion)); + if (inputStream == null) { + throw new NormalizationException(String.format("Reference realm for version %s does not exist, " + + "and fallback version %s does not exist either. Aborting!", version, fallbackVersion)); + } + } else { + throw new NormalizationException(String.format("Reference realm for version %s does not exist. Aborting!", version)); + } + } + return inputStream; + } + + public InputStream getClientInputStream(String version) { + var inputStream = getClass().getResourceAsStream(String.format("/baseline/%s/client/client.json", version)); + if (inputStream == null) { + if (fallbackVersion != null) { + logger.warn("Reference client not found for version {}. Using fallback version {}!", version, fallbackVersion); + inputStream = getClass().getResourceAsStream(String.format("/baseline/%s/client/client.json", fallbackVersion)); + if (inputStream == null) { + throw new NormalizationException(String.format("Reference client for version %s does not exist, " + + "and fallback version %s does not exist either. Aborting!", version, fallbackVersion)); + } + } else { + throw new NormalizationException(String.format("Reference client for version %s does not exist. Aborting!", version)); + } + } + return inputStream; + } } From 0030cc029afc622591fc438262bda5ea8a69488d Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 15 Dec 2022 09:36:38 +0100 Subject: [PATCH 21/49] Normalize roles --- .../KeycloakConfigNormalizationRunner.java | 2 + .../NormalizationConfiguration.java | 4 +- .../AttributeNormalizationService.java | 50 +++++++ .../normalize/RealmNormalizationService.java | 35 ++--- .../normalize/RoleNormalizationService.java | 136 ++++++++++++++++++ 5 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java index 591f03705..44b410b79 100644 --- a/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java +++ b/src/main/java/de/adorsys/keycloak/config/KeycloakConfigNormalizationRunner.java @@ -102,6 +102,8 @@ public void run(String... args) throws Exception { } } } + } catch (NullPointerException e) { + throw e; } catch (Exception e) { logger.error(e.getMessage()); diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 43c809bfb..855f07eb8 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -31,6 +31,7 @@ import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -104,6 +105,7 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(ClientRepresentation.class, "clientId", List.of("id", "authorizationSettings", "protocolMappers"))) .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) - .registerEntity(new EntityDefinition(ClientScopeRepresentation.class, "name", List.of("id", "protocolMappers"))); + .registerEntity(new EntityDefinition(ClientScopeRepresentation.class, "name", List.of("id", "protocolMappers"))) + .registerEntity(new EntityDefinition(RoleRepresentation.class, "name", List.of("id", "containerId", "composites"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java new file mode 100644 index 000000000..5fff554e7 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java @@ -0,0 +1,50 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class AttributeNormalizationService { + + public Map normalizeAttributes(Map exportedAttributes, Map baselineAttributes) { + Map exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; + Map baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + var normalizedAttributes = new HashMap(); + for (var entry : baselineOrEmpty.entrySet()) { + var attributeName = entry.getKey(); + var baselineAttribute = entry.getValue(); + var exportedAttribute = exportedOrEmpty.remove(attributeName); + + if (!Objects.equals(baselineAttribute, exportedAttribute)) { + normalizedAttributes.put(attributeName, exportedAttribute); + } + } + normalizedAttributes.putAll(exportedOrEmpty); + return normalizedAttributes.isEmpty() ? null : normalizedAttributes; + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 1ec6a6ef6..3cc46fe54 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -49,6 +49,8 @@ public class RealmNormalizationService { private final ScopeMappingNormalizationService scopeMappingNormalizationService; private final ProtocolMapperNormalizationService protocolMapperNormalizationService; private final ClientScopeNormalizationService clientScopeNormalizationService; + private final RoleNormalizationService roleNormalizationService; + private final AttributeNormalizationService attributeNormalizationService; private final JaversUtil javersUtil; @Autowired @@ -59,6 +61,8 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC ScopeMappingNormalizationService scopeMappingNormalizationService, ProtocolMapperNormalizationService protocolMapperNormalizationService, ClientScopeNormalizationService clientScopeNormalizationService, + RoleNormalizationService roleNormalizationService, + AttributeNormalizationService attributeNormalizationService, JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; this.javers = javers; @@ -67,6 +71,8 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC this.scopeMappingNormalizationService = scopeMappingNormalizationService; this.protocolMapperNormalizationService = protocolMapperNormalizationService; this.clientScopeNormalizationService = clientScopeNormalizationService; + this.roleNormalizationService = roleNormalizationService; + this.attributeNormalizationService = attributeNormalizationService; this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? @@ -119,39 +125,16 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { minimizedRealm.setClientScopeMappings(clientScopeMappings); } - var attributes = getMinimizedAttributes(exportedRealm, baselineRealm); - if (!attributes.isEmpty()) { - minimizedRealm.setAttributes(attributes); - } + minimizedRealm.setAttributes(attributeNormalizationService.normalizeAttributes(exportedRealm.getAttributes(), baselineRealm.getAttributes())); minimizedRealm.setProtocolMappers(protocolMapperNormalizationService.normalizeProtocolMappers(exportedRealm.getProtocolMappers(), baselineRealm.getProtocolMappers())); minimizedRealm.setClientScopes(clientScopeNormalizationService.normalizeClientScopes(exportedRealm.getClientScopes(), baselineRealm.getClientScopes())); - return minimizedRealm; - } - - private Map getMinimizedAttributes(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { - var exportedAttributes = exportedRealm.getAttributesOrEmpty(); - var baselineAttributes = baselineRealm.getAttributesOrEmpty(); - var minimizedAttributes = new HashMap(); - - for (var entry : baselineAttributes.entrySet()) { - var key = entry.getKey(); - var exportedValue = exportedAttributes.get(key); - if (!Objects.equals(exportedValue, entry.getValue())) { - minimizedAttributes.put(key, exportedValue); - } - } - for (var entry : exportedAttributes.entrySet()) { - var key = entry.getKey(); - if (!baselineAttributes.containsKey(key)) { - minimizedAttributes.put(key, entry.getValue()); - } - } - return minimizedAttributes; + minimizedRealm.setRoles(roleNormalizationService.normalizeRoles(exportedRealm.getRoles(), baselineRealm.getRoles())); + return minimizedRealm; } private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm, RealmRepresentation minimizedRealm) { diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java new file mode 100644 index 000000000..98441c4a0 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java @@ -0,0 +1,136 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.util.JaversUtil; +import org.javers.core.Javers; +import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.RolesRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class RoleNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(RoleNormalizationService.class); + + private final Javers unOrderedJavers; + private final JaversUtil javersUtil; + + private final AttributeNormalizationService attributeNormalizationService; + + @Autowired + public RoleNormalizationService(Javers unOrderedJavers, JaversUtil javersUtil, AttributeNormalizationService attributeNormalizationService) { + this.unOrderedJavers = unOrderedJavers; + this.javersUtil = javersUtil; + this.attributeNormalizationService = attributeNormalizationService; + } + + public RolesRepresentation normalizeRoles(RolesRepresentation exportedRoles, RolesRepresentation baselineRoles) { + var exportedOrEmpty = exportedRoles == null ? new RolesRepresentation() : exportedRoles; + var baselineOrEmpty = baselineRoles == null ? new RolesRepresentation() : baselineRoles; + var clientRoles = normalizeClientRoles(exportedOrEmpty.getClient(), baselineOrEmpty.getClient()); + var realmRoles = normalizeRealmRoles(exportedOrEmpty.getRealm(), baselineOrEmpty.getRealm()); + if (clientRoles == null && realmRoles == null) { + return null; + } + var normalizedRoles = new RolesRepresentation(); + normalizedRoles.setClient(clientRoles); + normalizedRoles.setRealm(realmRoles); + return normalizedRoles; + } + + public List normalizeRealmRoles(List exportedRealmRoles, List baselineRealmRoles) { + List exportedOrEmpty = exportedRealmRoles == null ? List.of() : exportedRealmRoles; + List baselineOrEmpty = baselineRealmRoles == null ? List.of() : baselineRealmRoles; + + var exportedMap = exportedOrEmpty.stream().collect(Collectors.toMap(RoleRepresentation::getName, Function.identity())); + var baselineMap = baselineOrEmpty.stream().collect(Collectors.toMap(RoleRepresentation::getName, Function.identity())); + + var normalizedRoles = new ArrayList(); + for (var entry : baselineMap.entrySet()) { + var roleName = entry.getKey(); + var baselineRole = entry.getValue(); + var exportedRole = exportedMap.remove(roleName); + if (exportedRole == null) { + logger.warn("Default realm role '{}' was deleted in exported realm. It may be reintroduced during import", roleName); + continue; + } + var diff = unOrderedJavers.compare(baselineRole, exportedRole); + if (diff.hasChanges() + || attributesChanged(baselineRole.getAttributes(), exportedRole.getAttributes()) + || compositesChanged(exportedRole.getComposites(), baselineRole.getComposites())) { + var normalizedRole = new RoleRepresentation(); + normalizedRole.setName(roleName); + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedRole, change); + } + normalizedRole.setAttributes(attributeNormalizationService.normalizeAttributes(exportedRole.getAttributes(), baselineRole.getAttributes())); + normalizedRoles.add(normalizedRole); + normalizedRole.setComposites(exportedRole.getComposites()); + } + } + return normalizedRoles.isEmpty() ? null : normalizedRoles; + } + + private boolean compositesChanged(RoleRepresentation.Composites exportedComposites, RoleRepresentation.Composites baselineComposites) { + return unOrderedJavers.compare(baselineComposites, exportedComposites).hasChanges(); + } + + private boolean attributesChanged(Map> exportedAttributes, Map> baselineAttributes) { + var exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; + var baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + + return !Objects.equals(exportedOrEmpty, baselineOrEmpty); + } + + public Map> normalizeClientRoles(Map> exportedClientRoles, + Map> baselineClientRoles) { + Map> exportedOrEmpty = exportedClientRoles == null ? Map.of() : exportedClientRoles; + Map> baselineOrEmpty = baselineClientRoles == null ? Map.of() : baselineClientRoles; + + Map> normalizedClientRoles = new HashMap<>(); + for (var entry : baselineOrEmpty.entrySet()) { + var clientName = entry.getKey(); + var baselineRoles = entry.getValue(); + var exportedRoles = exportedOrEmpty.remove(clientName); + + var normalizedRoles = normalizeRealmRoles(exportedRoles, baselineRoles); + if (normalizedRoles != null) { + normalizedClientRoles.put(clientName, normalizedRoles); + } + } + return normalizedClientRoles.isEmpty() ? null : normalizedClientRoles; + } +} From 83b162137b84f6c4d1c26adf345a7a8c770d6f18 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 15 Dec 2022 10:51:13 +0100 Subject: [PATCH 22/49] Fix checkstyle --- .../config/service/normalize/RealmNormalizationService.java | 2 -- .../config/service/normalize/RoleNormalizationService.java | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 3cc46fe54..dc8b4d446 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -32,8 +32,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; @Service diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java index 98441c4a0..a4dd90585 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java @@ -96,7 +96,8 @@ public List normalizeRealmRoles(List exp for (var change : diff.getChangesByType(PropertyChange.class)) { javersUtil.applyChange(normalizedRole, change); } - normalizedRole.setAttributes(attributeNormalizationService.normalizeAttributes(exportedRole.getAttributes(), baselineRole.getAttributes())); + normalizedRole.setAttributes(attributeNormalizationService.normalizeAttributes(exportedRole.getAttributes(), + baselineRole.getAttributes())); normalizedRoles.add(normalizedRole); normalizedRole.setComposites(exportedRole.getComposites()); } From c0195f30de7e3ba4239296889d57f7866cd68edf Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Fri, 16 Dec 2022 13:14:19 +0100 Subject: [PATCH 23/49] Group normalization --- .../NormalizationConfiguration.java | 4 +- .../AttributeNormalizationService.java | 52 +++++- .../normalize/ClientNormalizationService.java | 6 +- .../normalize/GroupNormalizationService.java | 150 ++++++++++++++++++ .../normalize/RealmNormalizationService.java | 9 +- .../normalize/RoleNormalizationService.java | 12 +- .../ScopeMappingNormalizationService.java | 16 +- 7 files changed, 224 insertions(+), 25 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 855f07eb8..aa66bafc9 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -29,6 +29,7 @@ import org.javers.core.metamodel.clazz.EntityDefinition; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; @@ -106,6 +107,7 @@ private JaversBuilder commonJavers() { List.of("id", "authorizationSettings", "protocolMappers"))) .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) .registerEntity(new EntityDefinition(ClientScopeRepresentation.class, "name", List.of("id", "protocolMappers"))) - .registerEntity(new EntityDefinition(RoleRepresentation.class, "name", List.of("id", "containerId", "composites"))); + .registerEntity(new EntityDefinition(RoleRepresentation.class, "name", List.of("id", "containerId", "composites"))) + .registerEntity(new EntityDefinition(GroupRepresentation.class, "path", List.of("id", "subGroups", "attributes", "clientRoles"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java index 5fff554e7..ef1237126 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java @@ -20,10 +20,12 @@ package de.adorsys.keycloak.config.service.normalize; +import org.javers.core.Javers; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -31,10 +33,16 @@ @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class AttributeNormalizationService { - public Map normalizeAttributes(Map exportedAttributes, Map baselineAttributes) { - Map exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; - Map baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; - var normalizedAttributes = new HashMap(); + private final Javers unOrderedJavers; + + public AttributeNormalizationService(Javers unOrderedJavers) { + this.unOrderedJavers = unOrderedJavers; + } + + public Map normalizeStringAttributes(Map exportedAttributes, Map baselineAttributes) { + Map exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; + Map baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + var normalizedAttributes = new HashMap(); for (var entry : baselineOrEmpty.entrySet()) { var attributeName = entry.getKey(); var baselineAttribute = entry.getValue(); @@ -47,4 +55,40 @@ public Map normalizeAttributes(Map exportedAttributes, normalizedAttributes.putAll(exportedOrEmpty); return normalizedAttributes.isEmpty() ? null : normalizedAttributes; } + + public Map> normalizeListAttributes(Map> exportedAttributes, + Map> baselineAttributes) { + Map> exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; + Map> baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + var normalizedAttributes = new HashMap>(); + for (var entry : baselineOrEmpty.entrySet()) { + var attributeName = entry.getKey(); + var baselineAttribute = entry.getValue(); + var exportedAttribute = exportedOrEmpty.remove(attributeName); + + if (unOrderedJavers.compareCollections(baselineAttribute, exportedAttribute, String.class).hasChanges()) { + normalizedAttributes.put(attributeName, exportedAttribute); + } + } + normalizedAttributes.putAll(exportedOrEmpty); + return normalizedAttributes.isEmpty() ? null : normalizedAttributes; + } + + public boolean listAttributesChanged(Map> exportedAttributes, Map> baselineAttributes) { + Map> exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; + Map> baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + + if (!Objects.equals(exportedOrEmpty.keySet(), baselineOrEmpty.keySet())) { + return true; + } + + for (var entry : baselineOrEmpty.entrySet()) { + if (unOrderedJavers.compareCollections(entry.getValue(), + exportedOrEmpty.get(entry.getKey()), String.class).hasChanges()) { + return true; + } + } + return false; + } + } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index 2a70078e0..1132b2255 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -59,14 +59,16 @@ public ClientNormalizationService(Javers javers, } public List normalizeClients(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { + List exportedOrEmpty = exportedRealm.getClients() == null ? List.of() : exportedRealm.getClients(); + List baselineOrEmpty = baselineRealm.getClients() == null ? List.of() : baselineRealm.getClients(); var exportedClientMap = new HashMap(); - for (var exportedClient : exportedRealm.getClients()) { + for (var exportedClient : exportedOrEmpty) { exportedClientMap.put(exportedClient.getClientId(), exportedClient); } var baselineClientMap = new HashMap(); var clients = new ArrayList(); - for (var baselineRealmClient : baselineRealm.getClients()) { + for (var baselineRealmClient : baselineOrEmpty) { var clientId = baselineRealmClient.getClientId(); baselineClientMap.put(clientId, baselineRealmClient); var exportedClient = exportedClientMap.get(clientId); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java new file mode 100644 index 000000000..520006c1b --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java @@ -0,0 +1,150 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.util.JaversUtil; +import org.javers.core.Javers; +import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.GroupRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class GroupNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(GroupNormalizationService.class); + + private final Javers unOrderedJavers; + private final JaversUtil javersUtil; + private final AttributeNormalizationService attributeNormalizationService; + + public GroupNormalizationService(Javers unOrderedJavers, + JaversUtil javersUtil, + AttributeNormalizationService attributeNormalizationService) { + this.unOrderedJavers = unOrderedJavers; + this.javersUtil = javersUtil; + this.attributeNormalizationService = attributeNormalizationService; + } + + public List normalizeGroups(List exportedGroups, List baselineGroups) { + List exportedOrEmpty = exportedGroups == null ? List.of() : exportedGroups; + List baselineOrEmpty = baselineGroups == null ? List.of() : baselineGroups; + var exportedGroupsMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(GroupRepresentation::getPath, Function.identity())); + var baselineGroupsMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(GroupRepresentation::getPath, Function.identity())); + + var normalizedGroups = new ArrayList(); + for (var entry : baselineGroupsMap.entrySet()) { + var groupPath = entry.getKey(); + var exportedGroup = exportedGroupsMap.remove(groupPath); + if (exportedGroup == null) { + logger.warn("Default realm group '{}' was deleted in exported realm. It may be reintroduced during import", groupPath); + continue; + } + var baselineGroup = entry.getValue(); + var diff = unOrderedJavers.compare(baselineGroup, exportedGroup); + + if (diff.hasChanges() || subGroupsChanged(exportedGroup, baselineGroup) + || attributeNormalizationService.listAttributesChanged(exportedGroup.getAttributes(), baselineGroup.getAttributes()) + || attributeNormalizationService.listAttributesChanged(exportedGroup.getClientRoles(), baselineGroup.getClientRoles())) { + var normalizedGroup = new GroupRepresentation(); + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedGroup, change); + } + normalizedGroup.setAttributes(exportedGroup.getAttributes()); + normalizedGroup.setClientRoles(exportedGroup.getClientRoles()); + normalizedGroup.setPath(exportedGroup.getPath()); + normalizedGroups.add(normalizedGroup); + } + } + normalizedGroups.addAll(exportedGroupsMap.values()); + normalizeGroupList(normalizedGroups); + return normalizedGroups.isEmpty() ? null : normalizedGroups; + } + + public boolean subGroupsChanged(GroupRepresentation exportedGroup, GroupRepresentation baselineGroup) { + if (exportedGroup.getSubGroups() == null && baselineGroup.getSubGroups() != null) { + return true; + } + if (exportedGroup.getSubGroups() != null && baselineGroup.getSubGroups() == null) { + return true; + } + if (exportedGroup.getSubGroups() == null && baselineGroup.getSubGroups() == null) { + return false; + } + + Map exportedSubGroups = exportedGroup.getSubGroups().stream() + .collect(Collectors.toMap(GroupRepresentation::getPath, Function.identity())); + Map baselineSubGroups = baselineGroup.getSubGroups().stream() + .collect(Collectors.toMap(GroupRepresentation::getPath, Function.identity())); + + for (var entry : baselineSubGroups.entrySet()) { + var groupPath = entry.getKey(); + var exportedSubGroup = exportedSubGroups.remove(groupPath); + + if (exportedSubGroup == null) { + // There's a subgroup in the baseline that's gone in the export. This counts as a change. + return true; + } + var baselineSubGroup = entry.getValue(); + if (unOrderedJavers.compare(baselineSubGroup, exportedSubGroup).hasChanges()) { + return true; + } + if (subGroupsChanged(exportedSubGroup, baselineSubGroup)) { + return true; + } + } + + // There are subgroups in the export that are not in the baseline. This is a change. + return !exportedSubGroups.isEmpty(); + } + + public void normalizeGroupList(List groups) { + for (var group : groups) { + if (group.getAttributes() != null && group.getAttributes().isEmpty()) { + group.setAttributes(null); + } + if (group.getRealmRoles() != null && group.getRealmRoles().isEmpty()) { + group.setRealmRoles(null); + } + if (group.getClientRoles() != null && group.getClientRoles().isEmpty()) { + group.setClientRoles(null); + } + if (group.getSubGroups() != null) { + if (group.getSubGroups().isEmpty()) { + group.setSubGroups(null); + } else { + normalizeGroupList(group.getSubGroups()); + } + } + } + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index dc8b4d446..2069350a3 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -49,6 +49,7 @@ public class RealmNormalizationService { private final ClientScopeNormalizationService clientScopeNormalizationService; private final RoleNormalizationService roleNormalizationService; private final AttributeNormalizationService attributeNormalizationService; + private final GroupNormalizationService groupNormalizationService; private final JaversUtil javersUtil; @Autowired @@ -61,6 +62,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC ClientScopeNormalizationService clientScopeNormalizationService, RoleNormalizationService roleNormalizationService, AttributeNormalizationService attributeNormalizationService, + GroupNormalizationService groupNormalizationService, JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; this.javers = javers; @@ -71,6 +73,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC this.clientScopeNormalizationService = clientScopeNormalizationService; this.roleNormalizationService = roleNormalizationService; this.attributeNormalizationService = attributeNormalizationService; + this.groupNormalizationService = groupNormalizationService; this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? @@ -123,7 +126,8 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { minimizedRealm.setClientScopeMappings(clientScopeMappings); } - minimizedRealm.setAttributes(attributeNormalizationService.normalizeAttributes(exportedRealm.getAttributes(), baselineRealm.getAttributes())); + minimizedRealm.setAttributes(attributeNormalizationService.normalizeStringAttributes(exportedRealm.getAttributes(), + baselineRealm.getAttributes())); minimizedRealm.setProtocolMappers(protocolMapperNormalizationService.normalizeProtocolMappers(exportedRealm.getProtocolMappers(), baselineRealm.getProtocolMappers())); @@ -132,6 +136,9 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { baselineRealm.getClientScopes())); minimizedRealm.setRoles(roleNormalizationService.normalizeRoles(exportedRealm.getRoles(), baselineRealm.getRoles())); + + minimizedRealm.setGroups(groupNormalizationService.normalizeGroups(exportedRealm.getGroups(), baselineRealm.getGroups())); + return minimizedRealm; } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java index a4dd90585..49e302cd8 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java @@ -35,7 +35,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @@ -89,14 +88,14 @@ public List normalizeRealmRoles(List exp } var diff = unOrderedJavers.compare(baselineRole, exportedRole); if (diff.hasChanges() - || attributesChanged(baselineRole.getAttributes(), exportedRole.getAttributes()) + || attributeNormalizationService.listAttributesChanged(exportedRole.getAttributes(), baselineRole.getAttributes()) || compositesChanged(exportedRole.getComposites(), baselineRole.getComposites())) { var normalizedRole = new RoleRepresentation(); normalizedRole.setName(roleName); for (var change : diff.getChangesByType(PropertyChange.class)) { javersUtil.applyChange(normalizedRole, change); } - normalizedRole.setAttributes(attributeNormalizationService.normalizeAttributes(exportedRole.getAttributes(), + normalizedRole.setAttributes(attributeNormalizationService.normalizeListAttributes(exportedRole.getAttributes(), baselineRole.getAttributes())); normalizedRoles.add(normalizedRole); normalizedRole.setComposites(exportedRole.getComposites()); @@ -109,13 +108,6 @@ private boolean compositesChanged(RoleRepresentation.Composites exportedComposit return unOrderedJavers.compare(baselineComposites, exportedComposites).hasChanges(); } - private boolean attributesChanged(Map> exportedAttributes, Map> baselineAttributes) { - var exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; - var baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; - - return !Objects.equals(exportedOrEmpty, baselineOrEmpty); - } - public Map> normalizeClientRoles(Map> exportedClientRoles, Map> baselineClientRoles) { Map> exportedOrEmpty = exportedClientRoles == null ? Map.of() : exportedClientRoles; diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java index d72763cba..43f587cbe 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java @@ -83,25 +83,27 @@ public List normalizeScopeMappings(RealmRepresentati public Map> normalizeClientScopeMappings(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { - var baselineMappings = baselineRealm.getClientScopeMappings(); - var exportedMappings = exportedRealm.getClientScopeMappings(); + Map> baselineOrEmpty = baselineRealm.getClientScopeMappings() == null + ? Map.of() : baselineRealm.getClientScopeMappings(); + Map> exportedOrEmpty = exportedRealm.getClientScopeMappings() == null + ? Map.of() : exportedRealm.getClientScopeMappings(); var mappings = new HashMap>(); - for (var e : baselineMappings.entrySet()) { + for (var e : baselineOrEmpty.entrySet()) { var key = e.getKey(); - if (!exportedMappings.containsKey(key)) { + if (!exportedOrEmpty.containsKey(key)) { logger.warn("Default realm clientScopeMapping '{}' was deleted in exported realm. It may be reintroduced during import!", key); continue; } - var scopeMappings = exportedMappings.get(key); + var scopeMappings = exportedOrEmpty.get(key); if (javers.compareCollections(e.getValue(), scopeMappings, ScopeMappingRepresentation.class).hasChanges()) { mappings.put(key, scopeMappings); } } - for (var e : exportedMappings.entrySet()) { + for (var e : exportedOrEmpty.entrySet()) { var key = e.getKey(); - if (!baselineMappings.containsKey(key)) { + if (!baselineOrEmpty.containsKey(key)) { mappings.put(key, e.getValue()); } } From b1663dfeaaa9f47f25afd615dc86c908c12b1a14 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 20 Dec 2022 10:32:13 +0100 Subject: [PATCH 24/49] Normalize AuthFlows and Configs --- .../NormalizationConfiguration.java | 4 +- .../AuthFlowNormalizationService.java | 172 ++++++++++++++++++ .../normalize/RealmNormalizationService.java | 7 + 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index aa66bafc9..da772447b 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -27,6 +27,7 @@ import org.javers.core.JaversBuilder; import org.javers.core.diff.ListCompareAlgorithm; import org.javers.core.metamodel.clazz.EntityDefinition; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.GroupRepresentation; @@ -108,6 +109,7 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) .registerEntity(new EntityDefinition(ClientScopeRepresentation.class, "name", List.of("id", "protocolMappers"))) .registerEntity(new EntityDefinition(RoleRepresentation.class, "name", List.of("id", "containerId", "composites"))) - .registerEntity(new EntityDefinition(GroupRepresentation.class, "path", List.of("id", "subGroups", "attributes", "clientRoles"))); + .registerEntity(new EntityDefinition(GroupRepresentation.class, "path", List.of("id", "subGroups", "attributes", "clientRoles"))) + .registerEntity(new EntityDefinition(AuthenticationFlowRepresentation.class, "alias", List.of("id", "authenticationExecutions"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java new file mode 100644 index 000000000..ecafca602 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -0,0 +1,172 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.util.JaversUtil; +import org.javers.core.Javers; +import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.AbstractAuthenticationExecutionRepresentation; +import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.function.Predicate.not; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class AuthFlowNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(AuthFlowNormalizationService.class); + + private final Javers unOrderedJavers; + + private final JaversUtil javersUtil; + + public AuthFlowNormalizationService(Javers unOrderedJavers, JaversUtil javersUtil) { + this.unOrderedJavers = unOrderedJavers; + this.javersUtil = javersUtil; + } + + public List normalizeAuthFlows(List exportedAuthFlows, + List baselineAuthFlows) { + var exportedFiltered = filterBuiltIn(exportedAuthFlows); + var baselineFiltered = filterBuiltIn(baselineAuthFlows); + + Map exportedMap = exportedFiltered.stream() + .collect(Collectors.toMap(AuthenticationFlowRepresentation::getAlias, Function.identity())); + Map baselineMap = baselineFiltered.stream() + .collect(Collectors.toMap(AuthenticationFlowRepresentation::getAlias, Function.identity())); + + List normalizedFlows = new ArrayList<>(); + for (var entry : baselineMap.entrySet()) { + var alias = entry.getKey(); + var exportedFlow = exportedMap.remove(alias); + if (exportedFlow == null) { + logger.warn("Default realm authentication flow '{}' was deleted in exported realm. It may be reintroduced during import", alias); + continue; + } + var baselineFlow = entry.getValue(); + var diff = unOrderedJavers.compare(baselineFlow, exportedFlow); + + if (diff.hasChanges() || executionsChanged(exportedFlow.getAuthenticationExecutions(), baselineFlow.getAuthenticationExecutions())) { + var normalizedFlow = new AuthenticationFlowRepresentation(); + normalizedFlow.setAlias(alias); + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedFlow, change); + } + normalizedFlow.setAuthenticationExecutions(exportedFlow.getAuthenticationExecutions()); + normalizedFlows.add(normalizedFlow); + } + } + normalizedFlows.addAll(exportedMap.values()); + return normalizedFlows.isEmpty() ? null : normalizedFlows; + } + + public List normalizeAuthConfig(List config, + List flows) { + List flowsOrEmpty = flows == null ? List.of() : flows; + // Find out which configs are actually used by the normalized flows + var usedConfigs = flowsOrEmpty.stream() + .map(AuthenticationFlowRepresentation::getAuthenticationExecutions) + .map(l -> l.stream() + .map(AbstractAuthenticationExecutionRepresentation::getAuthenticatorConfig) + .collect(Collectors.toList())).flatMap(Collection::stream) + .collect(Collectors.toSet()); + + List configOrEmpty = config == null ? List.of() : config; + // Only return configs that are used + var filteredConfigs = configOrEmpty.stream() + .filter(acr -> usedConfigs.contains(acr.getAlias())).collect(Collectors.toList()); + return filteredConfigs.isEmpty() ? null : filteredConfigs; + } + + private List filterBuiltIn(List flows) { + if (flows == null) { + return List.of(); + } + return flows.stream().filter(not(AuthenticationFlowRepresentation::isBuiltIn)).collect(Collectors.toList()); + } + + public boolean executionsChanged(List exportedExecutions, + List baselineExecutions) { + if (exportedExecutions == null && baselineExecutions != null) { + return true; + } + + if (exportedExecutions != null && baselineExecutions == null) { + return true; + } + + if (exportedExecutions == null) { + return false; + } + + if (exportedExecutions.size() != baselineExecutions.size()) { + return true; + } + + exportedExecutions.sort(Comparator.comparing(AbstractAuthenticationExecutionRepresentation::getPriority)); + baselineExecutions.sort(Comparator.comparing(AbstractAuthenticationExecutionRepresentation::getPriority)); + + for (int i = 0; i < exportedExecutions.size(); i++) { + if (executionChanged(exportedExecutions.get(i), baselineExecutions.get(i))) { + return true; + } + } + return false; + } + + public boolean executionChanged(AuthenticationExecutionExportRepresentation exportedExecution, + AuthenticationExecutionExportRepresentation baselineExecution) { + if (!Objects.equals(exportedExecution.getAuthenticatorConfig(), baselineExecution.getAuthenticatorConfig())) { + return true; + } + if (!Objects.equals(exportedExecution.getAuthenticator(), baselineExecution.getAuthenticator())) { + return true; + } + if (!Objects.equals(exportedExecution.isAuthenticatorFlow(), baselineExecution.isAuthenticatorFlow())) { + return true; + } + if (!Objects.equals(exportedExecution.getRequirement(), baselineExecution.getRequirement())) { + return true; + } + if (!Objects.equals(exportedExecution.getPriority(), baselineExecution.getPriority())) { + return true; + } + if (!Objects.equals(exportedExecution.getFlowAlias(), baselineExecution.getFlowAlias())) { + return true; + } + return !Objects.equals(exportedExecution.isUserSetupAllowed(), baselineExecution.isUserSetupAllowed()); + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 2069350a3..961926bf0 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -50,6 +50,7 @@ public class RealmNormalizationService { private final RoleNormalizationService roleNormalizationService; private final AttributeNormalizationService attributeNormalizationService; private final GroupNormalizationService groupNormalizationService; + private final AuthFlowNormalizationService authFlowNormalizationService; private final JaversUtil javersUtil; @Autowired @@ -63,6 +64,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC RoleNormalizationService roleNormalizationService, AttributeNormalizationService attributeNormalizationService, GroupNormalizationService groupNormalizationService, + AuthFlowNormalizationService authFlowNormalizationService, JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; this.javers = javers; @@ -74,6 +76,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC this.roleNormalizationService = roleNormalizationService; this.attributeNormalizationService = attributeNormalizationService; this.groupNormalizationService = groupNormalizationService; + this.authFlowNormalizationService = authFlowNormalizationService; this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? @@ -139,6 +142,10 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { minimizedRealm.setGroups(groupNormalizationService.normalizeGroups(exportedRealm.getGroups(), baselineRealm.getGroups())); + var authFlows = authFlowNormalizationService.normalizeAuthFlows(exportedRealm.getAuthenticationFlows(), + baselineRealm.getAuthenticationFlows()); + minimizedRealm.setAuthenticationFlows(authFlows); + minimizedRealm.setAuthenticatorConfig(authFlowNormalizationService.normalizeAuthConfig(exportedRealm.getAuthenticatorConfig(), authFlows)); return minimizedRealm; } From 1d5cfe611007a6b284e8814e90baf1730c2c7428 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 20 Dec 2022 16:33:21 +0100 Subject: [PATCH 25/49] IdP and IdP Mapper normalization --- .../NormalizationConfiguration.java | 6 +- .../IdentityProviderNormalizationService.java | 121 ++++++++++++++++++ .../normalize/RealmNormalizationService.java | 8 ++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index da772447b..4ab27149f 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -31,6 +31,8 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; @@ -110,6 +112,8 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(ClientScopeRepresentation.class, "name", List.of("id", "protocolMappers"))) .registerEntity(new EntityDefinition(RoleRepresentation.class, "name", List.of("id", "containerId", "composites"))) .registerEntity(new EntityDefinition(GroupRepresentation.class, "path", List.of("id", "subGroups", "attributes", "clientRoles"))) - .registerEntity(new EntityDefinition(AuthenticationFlowRepresentation.class, "alias", List.of("id", "authenticationExecutions"))); + .registerEntity(new EntityDefinition(AuthenticationFlowRepresentation.class, "alias", List.of("id", "authenticationExecutions"))) + .registerEntity(new EntityDefinition(IdentityProviderRepresentation.class, "alias", List.of("internalId"))) + .registerEntity(new EntityDefinition(IdentityProviderMapperRepresentation.class, "name", List.of("id"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java new file mode 100644 index 000000000..ba9053744 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java @@ -0,0 +1,121 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.util.JaversUtil; +import org.javers.core.Javers; +import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class IdentityProviderNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(IdentityProviderNormalizationService.class); + + private final Javers unOrderedJavers; + private final JaversUtil javersUtil; + + public IdentityProviderNormalizationService(Javers unOrderedJavers, JaversUtil javersUtil) { + this.unOrderedJavers = unOrderedJavers; + this.javersUtil = javersUtil; + } + + public List normalizeProviders(List exportedProviders, + List baselineProviders) { + List exportedOrEmpty = exportedProviders == null ? List.of() : exportedProviders; + List baselineOrEmpty = baselineProviders == null ? List.of() : baselineProviders; + + var exportedMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(IdentityProviderRepresentation::getAlias, Function.identity())); + var baselineMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(IdentityProviderRepresentation::getAlias, Function.identity())); + + var normalizedProviders = new ArrayList(); + for (var entry : baselineMap.entrySet()) { + var alias = entry.getKey(); + var exportedProvider = exportedMap.remove(alias); + if (exportedProvider == null) { + logger.warn("Default realm identityProvider '{}' was deleted in exported realm. It may be reintroduced during import!", alias); + continue; + } + var baselineProvider = entry.getValue(); + + var diff = unOrderedJavers.compare(baselineProvider, exportedProvider); + if (diff.hasChanges()) { + var normalizedProvider = new IdentityProviderRepresentation(); + normalizedProvider.setAlias(exportedProvider.getAlias()); + // Need to set manually because it's default true on the class, but it might not be on the baseline + normalizedProvider.setEnabled(exportedProvider.isEnabled()); + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedProvider, change); + } + normalizedProviders.add(normalizedProvider); + } + } + normalizedProviders.addAll(exportedMap.values()); + return normalizedProviders.isEmpty() ? null : normalizedProviders; + } + + public List normalizeMappers(List exportedMappers, + List baselineMappers) { + List exportedOrEmpty = exportedMappers == null ? List.of() : exportedMappers; + List baselineOrEmpty = baselineMappers == null ? List.of() : baselineMappers; + + var exportedMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(IdentityProviderMapperRepresentation::getName, Function.identity())); + var baselineMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(IdentityProviderMapperRepresentation::getName, Function.identity())); + + var normalizedMappers = new ArrayList(); + for (var entry : baselineMap.entrySet()) { + var name = entry.getKey(); + var exportedMapper = exportedMap.remove(name); + if (exportedMapper == null) { + logger.warn("Default realm identityProviderMapper '{}' was deleted in exported realm. It may be reintroduced during import!", name); + continue; + } + var baselineMapper = entry.getValue(); + var normalizedMapper = new IdentityProviderMapperRepresentation(); + normalizedMapper.setName(name); + + var diff = unOrderedJavers.compare(baselineMapper, exportedMapper); + if (diff.hasChanges()) { + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedMapper, change); + } + } + normalizedMappers.add(normalizedMapper); + } + normalizedMappers.addAll(exportedMap.values()); + return normalizedMappers.isEmpty() ? null : normalizedMappers; + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 961926bf0..48234aca0 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -51,6 +51,7 @@ public class RealmNormalizationService { private final AttributeNormalizationService attributeNormalizationService; private final GroupNormalizationService groupNormalizationService; private final AuthFlowNormalizationService authFlowNormalizationService; + private final IdentityProviderNormalizationService identityProviderNormalizationService; private final JaversUtil javersUtil; @Autowired @@ -65,6 +66,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC AttributeNormalizationService attributeNormalizationService, GroupNormalizationService groupNormalizationService, AuthFlowNormalizationService authFlowNormalizationService, + IdentityProviderNormalizationService identityProviderNormalizationService, JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; this.javers = javers; @@ -77,6 +79,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC this.attributeNormalizationService = attributeNormalizationService; this.groupNormalizationService = groupNormalizationService; this.authFlowNormalizationService = authFlowNormalizationService; + this.identityProviderNormalizationService = identityProviderNormalizationService; this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? @@ -146,6 +149,11 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { baselineRealm.getAuthenticationFlows()); minimizedRealm.setAuthenticationFlows(authFlows); minimizedRealm.setAuthenticatorConfig(authFlowNormalizationService.normalizeAuthConfig(exportedRealm.getAuthenticatorConfig(), authFlows)); + + minimizedRealm.setIdentityProviders(identityProviderNormalizationService.normalizeProviders(exportedRealm.getIdentityProviders(), + baselineRealm.getIdentityProviders())); + minimizedRealm.setIdentityProviderMappers(identityProviderNormalizationService.normalizeMappers(exportedRealm.getIdentityProviderMappers(), + baselineRealm.getIdentityProviderMappers())); return minimizedRealm; } From 9daaa0d437d66f727de6eea0a93f046a423b85cb Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 21 Dec 2022 11:23:54 +0100 Subject: [PATCH 26/49] Normalize Required Actions --- .../NormalizationConfiguration.java | 4 +- .../normalize/RealmNormalizationService.java | 6 ++ .../RequiredActionNormalizationService.java | 86 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 4ab27149f..4bdf72048 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -35,6 +35,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -114,6 +115,7 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(GroupRepresentation.class, "path", List.of("id", "subGroups", "attributes", "clientRoles"))) .registerEntity(new EntityDefinition(AuthenticationFlowRepresentation.class, "alias", List.of("id", "authenticationExecutions"))) .registerEntity(new EntityDefinition(IdentityProviderRepresentation.class, "alias", List.of("internalId"))) - .registerEntity(new EntityDefinition(IdentityProviderMapperRepresentation.class, "name", List.of("id"))); + .registerEntity(new EntityDefinition(IdentityProviderMapperRepresentation.class, "name", List.of("id"))) + .registerEntity(new EntityDefinition(RequiredActionProviderRepresentation.class, "alias")); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 48234aca0..0dcdd4d97 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -52,6 +52,7 @@ public class RealmNormalizationService { private final GroupNormalizationService groupNormalizationService; private final AuthFlowNormalizationService authFlowNormalizationService; private final IdentityProviderNormalizationService identityProviderNormalizationService; + private final RequiredActionNormalizationService requiredActionNormalizationService; private final JaversUtil javersUtil; @Autowired @@ -67,6 +68,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC GroupNormalizationService groupNormalizationService, AuthFlowNormalizationService authFlowNormalizationService, IdentityProviderNormalizationService identityProviderNormalizationService, + RequiredActionNormalizationService requiredActionNormalizationService, JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; this.javers = javers; @@ -80,6 +82,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC this.groupNormalizationService = groupNormalizationService; this.authFlowNormalizationService = authFlowNormalizationService; this.identityProviderNormalizationService = identityProviderNormalizationService; + this.requiredActionNormalizationService = requiredActionNormalizationService; this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? @@ -154,6 +157,9 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { baselineRealm.getIdentityProviders())); minimizedRealm.setIdentityProviderMappers(identityProviderNormalizationService.normalizeMappers(exportedRealm.getIdentityProviderMappers(), baselineRealm.getIdentityProviderMappers())); + + minimizedRealm.setRequiredActions(requiredActionNormalizationService.normalizeRequiredActions(exportedRealm.getRequiredActions(), + baselineRealm.getRequiredActions())); return minimizedRealm; } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java new file mode 100644 index 000000000..410ed9359 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java @@ -0,0 +1,86 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.util.JaversUtil; +import org.javers.core.Javers; +import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.RequiredActionProviderRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class RequiredActionNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(RequiredActionNormalizationService.class); + + private final Javers javers; + private final JaversUtil javersUtil; + + public RequiredActionNormalizationService(Javers javers, JaversUtil javersUtil) { + this.javers = javers; + this.javersUtil = javersUtil; + } + + public List normalizeRequiredActions(List exportedActions, + List baselineActions) { + List exportedOrEmpty = exportedActions == null ? List.of() : exportedActions; + List baselineOrEmpty = baselineActions == null ? List.of() : baselineActions; + + var exportedMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(RequiredActionProviderRepresentation::getAlias, Function.identity())); + var baselineMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(RequiredActionProviderRepresentation::getAlias, Function.identity())); + + var normalizedActions = new ArrayList(); + for (var entry : baselineMap.entrySet()) { + var alias = entry.getKey(); + var exportedAction = exportedMap.remove(alias); + if (exportedAction == null) { + logger.warn("Default realm requiredAction '{}' was deleted in exported realm. It may be reintroduced during import", alias); + continue; + } + var baselineAction = entry.getValue(); + + var diff = javers.compare(baselineAction, exportedAction); + if (diff.hasChanges()) { + var normalizedAction = new RequiredActionProviderRepresentation(); + normalizedAction.setAlias(alias); + for (var change : diff.getChangesByType(PropertyChange.class)) { + javersUtil.applyChange(normalizedAction, change); + } + normalizedAction.setEnabled(exportedAction.isEnabled()); + normalizedAction.setDefaultAction(exportedAction.isDefaultAction()); + normalizedActions.add(normalizedAction); + } + } + normalizedActions.addAll(exportedMap.values()); + return normalizedActions.isEmpty() ? null : normalizedActions; + } +} From cf4c3adc9a0ddaae549dfabe13ab9b696aabf4fd Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 14 Feb 2023 16:06:49 +0100 Subject: [PATCH 27/49] 20.0.3 baseline --- .../baseline/20.0.3/realm/realm.json | 1778 +++++++++++++++++ 1 file changed, 1778 insertions(+) create mode 100644 src/main/resources/baseline/20.0.3/realm/realm.json diff --git a/src/main/resources/baseline/20.0.3/realm/realm.json b/src/main/resources/baseline/20.0.3/realm/realm.json new file mode 100644 index 000000000..b9f072b4a --- /dev/null +++ b/src/main/resources/baseline/20.0.3/realm/realm.json @@ -0,0 +1,1778 @@ +{ + "id" : "REALM_NAME_PLACEHOLDER", + "realm" : "REALM_NAME_PLACEHOLDER", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "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, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "fe646314-5d85-48de-ad2a-7c699cc5d0b1", + "name" : "default-roles-REALM_NAME_PLACEHOLDER", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "REALM_NAME_PLACEHOLDER", + "attributes" : { } + }, { + "id" : "ddfbaacd-5ae8-4e79-8fc5-b3de030e1aa9", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "REALM_NAME_PLACEHOLDER", + "attributes" : { } + }, { + "id" : "c7236465-e6c0-41b7-b206-1c33ec097212", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "REALM_NAME_PLACEHOLDER", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "5406f48d-98c9-4306-acc0-2924c88c871f", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "b9ec3294-c55b-4ed2-ab48-ad20fdb91150", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "9cd205bf-046a-4b35-91b7-62af0c6de03d", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "d32388e5-e99d-4675-a9db-48490c41b86b", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "8450b684-6ebf-42b4-8625-a5e4589674a8", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "7190e385-42ea-4218-9945-d6f1877bf80a", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "ce05b5fd-0245-44a7-900c-f3eb7a2e52cf", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "04316910-c289-492f-9a36-b899ce1e1bc5", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "1f412284-d0bf-459f-8903-241e33d41a7e", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "view-identity-providers", "manage-authorization", "impersonation", "manage-users", "view-users", "query-realms", "manage-realm", "create-client", "manage-clients", "manage-identity-providers", "view-clients", "query-groups", "view-events", "manage-events", "query-users", "view-authorization", "view-realm", "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "936d0734-b1e7-48e8-90d4-9ca37a70aa1c", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "281d791a-5bf7-4f15-9879-90f38814eb90", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "f58918cd-9fa7-45ff-9923-77461f6703be", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "0d9af750-bcd0-47c4-a912-616d9b6d2f5c", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "b82c3486-6936-457f-a574-744df850e0ce", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "06850f6c-5215-4525-9cb3-9aa4c352ebc9", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "547c6ac7-f463-4bed-b50f-fd51b7cad44e", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "bf482b3c-1a94-460c-9c93-f17ce8323d3b", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "67f49d28-e854-42d5-b6d2-bbab94ffbf81", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + }, { + "id" : "3ab06f48-ae3e-4cf2-a73a-a846859bd0ae", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "9ba35276-b7a8-45a5-a4c0-51792399e52b", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "b4a282e1-0dc3-4841-b6e5-6b978efa945f", + "attributes" : { } + } ], + "account" : [ { + "id" : "efb75750-dff7-42b9-9e49-b81cad69683d", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + }, { + "id" : "65b56562-6f9b-4765-8a67-4c1850278207", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + }, { + "id" : "7cd9a351-fa28-44d8-9708-d0865c6b16c5", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + }, { + "id" : "32b5c183-fe02-4b92-a4a3-131f3668b43b", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + }, { + "id" : "eac2aa66-ed7e-4660-8c91-74b2e79037cd", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + }, { + "id" : "5682c606-875b-40cc-be5a-097964fc2674", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + }, { + "id" : "6c62fd99-f414-4f4c-8087-bcc0f3433abd", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + }, { + "id" : "81e41b35-e7d2-4331-bb41-c6fa6bd6cb8a", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "fe646314-5d85-48de-ad2a-7c699cc5d0b1", + "name" : "default-roles-REALM_NAME_PLACEHOLDER", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "REALM_NAME_PLACEHOLDER" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppFreeOTPName" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "2841004e-949f-45ec-bf80-304c92bfd02e", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/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", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "a4151274-6311-4571-9c88-a8c5f9d6f67e", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/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" : "2d2f1e79-74d0-4034-a12f-85bb795c4f28", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "ac095c61-18a0-42a6-8e28-c14039c0c2b8", + "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", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "b4a282e1-0dc3-4841-b6e5-6b978efa945f", + "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", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "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", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "a2fe5b0d-93c7-4527-a9ea-cab77d8c2894", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/REALM_NAME_PLACEHOLDER/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/REALM_NAME_PLACEHOLDER/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" : "2ff543fa-afce-481b-9f47-c62ec659bf09", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "eac7aa95-1d7a-4d2c-870a-6a117cb6e4cc", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "45192395-23af-4d25-970e-a5c7f58cc915", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "cc7059c4-97a9-40f0-9c4b-f9f185baecc6", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "fd4625bb-8d0a-465b-876b-bc3199c48e89", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1ca99df2-7843-4dfc-b124-71ecf271d642", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "8771d8e1-af09-4894-a3d9-41a61db8e88c", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "1fba188d-5586-4db9-918a-344b52d7e132", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "ef59b1b7-9685-4fb8-8b8a-271c0fa75aa6", + "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" : "3775ef0c-440c-456a-977a-1e05766f1e40", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "0a886470-9443-4e0f-abe6-bda296f028d5", + "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" : "0f7a1eef-0785-47a5-a7b4-0d9981305cba", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "52cc2865-4e65-4c79-80d0-06128dbb7237", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "4a94f78d-06e5-4adb-b1ee-6f74b15274d8", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "76cfb243-c416-4da3-8d57-858e27f6d2b8", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "0bc87ace-6420-40d4-b5db-bb1583e028c0", + "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" : "ee9bf484-6a3d-43a0-8919-c3cf57167b7d", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "9015f051-42c5-4258-b357-e74a5f2a7931", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "853dc1f3-b1dc-4239-8552-8880018bbc51", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "ac670383-8e32-45eb-a765-b7f2d7b0d655", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "63d3fd74-18c7-455f-89d9-bc6c8d0f8292", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "3bea839b-005f-4881-b0fd-c5fc8a76e7c2", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "e4b4fb81-bd0f-4d0d-8e64-aeb17e1f1142", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "211b8a03-74eb-40c8-88b7-d7caef3a8541", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "e6367671-17b8-468a-9ce3-443c07c34910", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "a0eff207-075b-48b9-bef9-3295b5cef7aa", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "f04b0ad0-aeee-4311-a542-d5f81033612b", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "aa7cf9ed-aa67-4cfc-a909-156273850148", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "f222d5c5-26f9-42ea-8b18-e67e96da71ac", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "26c89f6c-60d4-44b3-926f-735d69581ef7", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "d31c0cd0-9d46-45fb-af01-dccd915ddd46", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "8b105682-eca1-4cea-96cc-91ddf3aaa47f", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "4c35f4b3-02dc-4f9b-9a71-8438b9e164c0", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "ebfc259f-67e9-4fca-8970-76e2514ca8fa", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "ae52f1d2-a2a2-49e2-a346-6cc8b978173f", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "a1a900c6-a9e0-47e5-a4b9-f0162b61ce29", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "7643fd56-8747-498a-8ac0-50826747b38a", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "1c705cc2-6e68-4df1-8570-a29a4eb2043b", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "xContentTypeOptions" : "nosniff", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "a57f494a-2d08-4bef-8f25-8b4417593a80", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "63c6ef5d-3e2a-4e67-8a57-028fe50c5db0", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper" ] + } + }, { + "id" : "2f252121-75d0-4440-b738-3a4cca8c0a4b", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "4431aa99-bc82-4a0a-bf67-8d30b25e0dda", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "c502f0e3-3f8d-46a7-b543-1fd8e269b362", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "a5be73ee-e5c4-4021-b8fd-754ecf658d2f", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper" ] + } + }, { + "id" : "7a015fcb-607c-49ff-802c-e19bd384ee40", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "bac04512-481c-49e3-b420-49543988174a", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "e2dfaaac-b826-44dd-a80d-36e50864217b", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAoN16fM4OdG13SPzlnMrl8WDCMTYs+cNSMRwUh64HTXmOTRzWquw3k3CUihVeuHNV+/2A6dxeMZIpm4KFlBbr+KylB2nvweve2U2xEjBxbHVfR2DPNCh6d8pr+qup0AkxMx5EL0yKiGQA/eu+4Yz5rsd1U39ze/FSTkZb8sHogfqOlLcGgE6aPq6QGNWxDvdWSthRpCwvDxyJ5cI+EH3cSNaAxaTCbPotRjAv3cN2WNB3Tb6ZwfOagcbLCRsOm3o05s9Dc5prTj+eCbhrmcFaYqIs4tW+fA9D1rWgxlBsRRGeChpvdBkdJ55OJWLgut89Vlpd96yT8zL1wlYMxkyOyQIDAQABAoIBAA3sRWz2Z61fiRa93sC0lfTPj1czjW57n4pFGCjLIPItbmnRsGnAQqol8RFHeriDsQI7pXP4QQXQ1P2S7S1NU5SlGq561ofF+rZ7oTMGEtcUjI/QuvZUd48VK8XZJDwvg3zdZoDigs39u9E+XXnHmhesx0SYEB5+20UPlWc8LbvMGPBpYc+c7VtbJN56SwDK5+Jd/hKktAGzmCogeYohjINApSvcBZSD2uLd09AHcYv26SqX1cORHCBZ5hla1LKKKKgtncQTeIaVH/8Fmcc1SSrOiMMueXxHpqWVxM9G9i9LU+GJ8DujYKoR066mGQtKnvTl98eoVhrpw5IpEUnzgKECgYEA099mCn9V3QunAmRdbMaIWMsYfD+pjDRsKM71nh7kkkmuPwG+CHlkvb+o9HjZGsJqR0uX2ZO2zhZqMue1BSmIQy7h7cN0xgr3LHgj8JqWyAMV4kjR8CtgpLnn8r895IPVTv/fIzPkm+QFhpgdhG2mj1ZMZeZs1jXVdCL+vXbygvECgYEAwl53xs3+3fmWdDBIOX4WnSE/ncLkNGuo4dFvuW1hboR4VfYra4WOCdgpkzR6LcKV100gePd8niNlx+cLywiLuP6xackjtimP7d3qmfF/7Aj5yjD7ZH3gKyKbUTYqqpXOh02TpIxQsuOZ9tFCzUiIESSgTS5AmR137o1LFNFrmVkCgYAI3vmuxtYZgzti5kDjJQzgauCDk0bOEAPXUZMHI9P+LAeXq8MBJ9QH/dZdJhIxBKLeiVO6UGz/BAE+UGVL6glChtv+0Ig7Iopm2kDm+hIGHdr0tofTvn01aHNFvqwgvURZIm19U9bmkA2LLSNPQCgRKFPakGNONMLTMgPoeup8gQKBgEmt28Od1M46TYEvyEEa/eGEeAQKm80PPo6TgMGOqA9n4eHcFiuFDgnHSkNF8kaka36Y+wU/85pSmsODXuIRFD4FNiV+nK3x5WBq7gJ7HF3SLTYrCTDX0oEndi771IKjlQ2PJ7eJhRG622NHggYlR4cQFkALl2szU4I3R03V8O7JAoGBAMJerJp1uYAI5nma9Rs/4y3Fs6bssFNMjQeQjpr0YzZwxjQmFff4oqJ4H2wmA/nLu0srn8U7aK27g+hgEc/07c3TWw2+ZAkD/0XSi2Oy939IpxENzTnCXEXB25xx+MX+2xEF09bfWbauDcsHhLl3DJYWaFM9lpP9LfLOQsS8CRds" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICoTCCAYkCBgGGNmK7rDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wHhcNMjMwMjA5MTMzMzA1WhcNMzMwMjA5MTMzNDQ1WjAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCg3Xp8zg50bXdI/OWcyuXxYMIxNiz5w1IxHBSHrgdNeY5NHNaq7DeTcJSKFV64c1X7/YDp3F4xkimbgoWUFuv4rKUHae/B697ZTbESMHFsdV9HYM80KHp3ymv6q6nQCTEzHkQvTIqIZAD9677hjPmux3VTf3N78VJORlvyweiB+o6UtwaATpo+rpAY1bEO91ZK2FGkLC8PHInlwj4QfdxI1oDFpMJs+i1GMC/dw3ZY0HdNvpnB85qBxssJGw6bejTmz0NzmmtOP54JuGuZwVpioizi1b58D0PWtaDGUGxFEZ4KGm90GR0nnk4lYuC63z1WWl33rJPzMvXCVgzGTI7JAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIPfI2ci8HjmkLy1bYvNAPC+loZYi8oa6AyU5Zjq6fBQgbqfWfaou2aWANpU7wETdYy4+r6wNyBqGnXHrJH/DjnH0qa8nmIaoOQL6e88QRZHnTS1upQFq05zLQoATFzzZtkNzZymnfnADPy38skbEuBaY0deIefXfincPNBy4WG3x/tDq1mhf6Q7vW5g6g4IS1F9aGjYNvwD+ahaM4IxiqYiCFBa7jrSMLX/OTJbAHfBBSlZ9tNrZIn17NDBz2enNaIL6fV3IclxQx56AuC/Y85VwYP3/yJAyPtYeHDdBi42qdMSwD1niZKeciYiChZVYZtIYuUxIzDkicNBwC/wK6k=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "5a014cec-1303-47c5-86a4-845754d11f6f", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "4dd3a8df-f8a6-438b-aee9-9073a6acc042" ], + "secret" : [ "eTpCqpiimHnHwx1OGSWXQ1jciJqaBzb9QLBvG8lCD-IZPmTr9Vgs3wa6yUzfn9GOyrzH8zFknPhxLc_j16MAbQ" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "3c8cc563-5b4e-410e-9318-cf6019a62fb3", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAr7zgzlIYcj6ufYyypCuvfNPmw7h3278AGZ8yYRs0OLbFULILUbVZCv4itBVHtl/1u5IxnsCNBbIKoycLtXtK74gOa/DcmZbeOuW+INIMNbHFTqUwIiFvXt18koPz1Kptp2aZKI/w1IrOvgdHkdVvH0COB++gkTK5sej4FwC3L+eBItD+TgW4qQY7GRN/erq+GiwxhjhabecxOGZpo/XpeHKuD7wygKwkdjkUtFQeJ9MJHDYjSZvVaqkFNJusGdmWUP5N390kc6AbyTbQ3LD1Ck0YPCytYHhS4PJ0ztdVZ+pI/yQ0+AHxObB0xfXxvCMSJfF0od6mYOoa1y5KRqwyNQIDAQABAoIBADOsh7PTKvII4Vj0cqDYYZkEW/IH/2Jjlr1x9KfeDmxwvbM9LxvfBag4Uu795ODI9aIqWiHSf4UBtaQEuqop7z5glNEDeXJGDOFnpZDUApgVRi2aX/1hNHTCDrusRhmt5WFYR0fIj2mHyUPSMya8Chk366uEuAOJ1VVtaemlobfZT3quB2hh+QBDEz4dg9DwNE5NCeABCpU3x2yiIqwSMf2oTxjaQmTN7Bt5vg/N482QBk60TGgMrP4ewU8P7iDEayqCmyVnxGDhhocSl0n2zQwZsd2rdRYLFBJ8D+MRSoPUI82FR94IXBBMUaSIUd+/g2AXYVnWEFcY4PKooPY/YY8CgYEA2BtZuSe2+Iq8TFG0JXQMVDFvkl5tgiXNw03r3VaPbqhoe9LPzx75KS7Y5YaA9LONYN9wX7txZxVq+zYEvR/JNJfZAKAET7VvoV7FkoRau3AGHl4aQR8mCivKSu8dl7pGORBdzFwM8Q/FGP3ijj6zmWMohSurrERu+M5+MydLNZMCgYEA0C3KrsNVJ7iwASl+0tzvL5poECxX60S6FY/ARJpVOpqNXwZTZmcswW69ZsmbWG+GcOd4kpBc/c8SWo3wpfE+D8IrQEv7zIEpUau25XNjvey850pzH9enZYjHLm95sDgtQbiZeaETQUM6XoN0cmNAycTJl//+il9rQCthI3xJVhcCgYEAyvF8Y+XI7UUUKbO8skN15Ib8WP82ZbdssRF3A8frMKcUX9wMbVyP7j3hUELszV/kz+llFXHowD6b1cfX0WeNQ0eqcOzUv6oKaAehEMQclNNR26jRjQBcb8bJ1W8SYk967NHS1DxJpwQBO0QJQz3c2ZoGDnGBaWhmsl7woscu2OMCgYAayzoCAkwMf73PeHrFQwBZ/SKVqk9qjX7x1X2mlsKPd2LorxS+4lqlTs5700j2Oexwzan8Gy6M0EEJfKlTglg9iP0uGll/OcSKqXIx2AcnY7fwgOmOWKBH9jHErT5ZSvGwOsUuHWgwByoVDhBHxLSLA5P6x00InD1elfa7rXx24wKBgQCYefkhORVweEYZJM1g7rR8L6kpTHqrDR3X0V15p6dLxxP4ZcdcdnGeD6BqqxQ6J41k15jU7ZpqWpVgsZxbHQbRlmZV8RbsOAWZyRLP7iVTNTaAHC2JD4tocXoj9dhT65ohFz2r0u7lmis/kUbJIwsBHzwqYcNKG5SAqg3OFYOBMQ==" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICoTCCAYkCBgGGNmK7HDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wHhcNMjMwMjA5MTMzMzA1WhcNMzMwMjA5MTMzNDQ1WjAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvvODOUhhyPq59jLKkK6980+bDuHfbvwAZnzJhGzQ4tsVQsgtRtVkK/iK0FUe2X/W7kjGewI0FsgqjJwu1e0rviA5r8NyZlt465b4g0gw1scVOpTAiIW9e3XySg/PUqm2nZpkoj/DUis6+B0eR1W8fQI4H76CRMrmx6PgXALcv54Ei0P5OBbipBjsZE396ur4aLDGGOFpt5zE4Zmmj9el4cq4PvDKArCR2ORS0VB4n0wkcNiNJm9VqqQU0m6wZ2ZZQ/k3f3SRzoBvJNtDcsPUKTRg8LK1geFLg8nTO11Vn6kj/JDT4AfE5sHTF9fG8IxIl8XSh3qZg6hrXLkpGrDI1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBADWKt3hBcClaQB4ws16+uUFC7tijyz1d7y1dpgP12nxjsfcKDnoEySl3Epb7LWXJfsynOdvMepH4Od3PBh2YLUmTFx5Yf+6+ATEuvrXesuUojmFf8hklEZXborkfRBpkDqfW82ctCxEi9W9beD5m9ZEHbz0bs25oR5zroDj7OZrnHeVGAXXKoeraNmSdOmN3cMLJ7AKRuDuOmfT7XbxH96027FeWBT5sR44wPscFU9I8XQ6JaH5+v2XVUVMuKAN+fjEkqCzLX2Iti8MSMNJQ5t8dC59gMamlrujTd+jH0G5nDz2LritJ+VkHU0kGLDwh1qGROVBVb2FJIuzRgEZMagY=" ], + "priority" : [ "100" ] + } + }, { + "id" : "1a2b0841-81a8-4a34-9725-865207b3eea9", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "28988101-aab0-4a6d-ac5a-8c9354921a8f" ], + "secret" : [ "Ukfu-vGx0yhCe2vDWh2eTQ" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "2f656614-b183-4d5d-ac49-77ec1ce255dd", + "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" : "2320409f-2ea6-4829-9f79-e20f96ee0c0d", + "alias" : "Authentication Options", + "description" : "Authentication options.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "basic-auth", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "basic-auth-otp", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "9600922d-2b69-48d5-9bdd-7301ac7e2d86", + "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" : "b7a6599c-c0ed-4528-b5f6-bd532f13d1fb", + "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" : "a3cb92d3-7aec-4714-acf9-d875a14e54cd", + "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" : "faee7139-7369-4794-823c-4aa00db351b9", + "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" : "1b987aac-f443-4d3d-a731-c67af88cc83c", + "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" : "e11a18f3-e58b-4a45-85f2-de69cc48f19c", + "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" : "6c057f26-b8ed-40df-a27b-bef2d2ec0416", + "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" : "3b77214d-0f25-43d8-8429-3427d50a153f", + "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" : "19faf3b5-9c26-421a-9ba1-94721f442a37", + "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" : "1cf28966-e26e-48d2-b6d9-887e591524ff", + "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" : "e4698ed9-1fa3-4e30-a07c-dc7876a6b7c9", + "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" : "c9bda4af-29a5-4298-9882-306c22be2ecf", + "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" : "74883789-91ed-47b7-83b9-7738179b8a2e", + "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" : "ec833733-3e09-41aa-816d-c6c68ccf59bf", + "alias" : "http challenge", + "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "no-cookie-redirect", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Authentication Options", + "userSetupAllowed" : false + } ] + }, { + "id" : "5fce633c-3e98-406c-9d9f-4fa9a60aa1f7", + "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" : "3d2443de-dbb3-4c32-aaa7-3e7e607b0848", + "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-profile-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 40, + "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 + } ] + }, { + "id" : "97bcd182-ed8e-451e-af92-f1c2a9d2381a", + "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" : "59340546-1da0-42e4-bd4b-934a84ee1098", + "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" : "4b9bb466-54be-4b63-b97a-8556986bd192", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "b0488377-25d8-4518-a9b8-672e4c9ddd18", + "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" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DeviceCodeLifespan" : "600", + "oauth2DevicePollingInterval" : "5", + "parRequestUriLifespan" : "60", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false" + }, + "keycloakVersion" : "20.0.3", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} From f49400414f85cb0fb9fbdffd46a6abf84d3bd653 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 15 Feb 2023 11:43:56 +0100 Subject: [PATCH 28/49] Update baseline representations --- .../baseline/19.0.3/realm/realm.json | 10 +- .../baseline/20.0.3/client/client.json | 45 + .../baseline/20.0.3/realm/realm.json | 3894 +++++++++-------- 3 files changed, 2202 insertions(+), 1747 deletions(-) create mode 100644 src/main/resources/baseline/20.0.3/client/client.json diff --git a/src/main/resources/baseline/19.0.3/realm/realm.json b/src/main/resources/baseline/19.0.3/realm/realm.json index 5e65e9a84..bf14005cb 100644 --- a/src/main/resources/baseline/19.0.3/realm/realm.json +++ b/src/main/resources/baseline/19.0.3/realm/realm.json @@ -1,5 +1,5 @@ { - "id": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "id": "REALM_NAME_PLACEHOLDER", "realm": "REALM_NAME_PLACEHOLDER", "notBefore": 0, "defaultSignatureAlgorithm": "RS256", @@ -63,7 +63,7 @@ } }, "clientRole": false, - "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "containerId": "REALM_NAME_PLACEHOLDER", "attributes": {} }, { @@ -72,7 +72,7 @@ "description": "${role_offline-access}", "composite": false, "clientRole": false, - "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "containerId": "REALM_NAME_PLACEHOLDER", "attributes": {} }, { @@ -81,7 +81,7 @@ "description": "${role_uma_authorization}", "composite": false, "clientRole": false, - "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8", + "containerId": "REALM_NAME_PLACEHOLDER", "attributes": {} } ], @@ -400,7 +400,7 @@ "description": "${role_default-roles}", "composite": true, "clientRole": false, - "containerId": "791bfad8-bdb8-4117-87f3-35a0acf453a8" + "containerId": "REALM_NAME_PLACEHOLDER" }, "requiredCredentials": [ "password" diff --git a/src/main/resources/baseline/20.0.3/client/client.json b/src/main/resources/baseline/20.0.3/client/client.json new file mode 100644 index 000000000..d555d4f48 --- /dev/null +++ b/src/main/resources/baseline/20.0.3/client/client.json @@ -0,0 +1,45 @@ +{ + "id": "8a641514-bb92-4a5e-8ea4-27b90ef3e637", + "clientId": "reference-client", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "hzjJYnHVxMf3I3ugD4le0CgT1iI3rCx2", + "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": "1676457441" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} diff --git a/src/main/resources/baseline/20.0.3/realm/realm.json b/src/main/resources/baseline/20.0.3/realm/realm.json index b9f072b4a..ee28d9e47 100644 --- a/src/main/resources/baseline/20.0.3/realm/realm.json +++ b/src/main/resources/baseline/20.0.3/realm/realm.json @@ -1,1778 +1,2188 @@ { - "id" : "REALM_NAME_PLACEHOLDER", - "realm" : "REALM_NAME_PLACEHOLDER", - "notBefore" : 0, - "defaultSignatureAlgorithm" : "RS256", - "revokeRefreshToken" : false, - "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, - "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, - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "roles" : { - "realm" : [ { - "id" : "fe646314-5d85-48de-ad2a-7c699cc5d0b1", - "name" : "default-roles-REALM_NAME_PLACEHOLDER", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "view-profile", "manage-account" ] - } + "id": "REALM_NAME_PLACEHOLDER", + "realm": "REALM_NAME_PLACEHOLDER", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "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, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "c1c757e3-1483-4a13-a650-57d13762063d", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "a63b0d92-16b3-4110-8dd8-b25ed575035a", + "attributes": {} + }, + { + "id": "1df44d36-8c3e-47a9-8b37-28b31c9c5fd1", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "a63b0d92-16b3-4110-8dd8-b25ed575035a", + "attributes": {} }, - "clientRole" : false, - "containerId" : "REALM_NAME_PLACEHOLDER", - "attributes" : { } - }, { - "id" : "ddfbaacd-5ae8-4e79-8fc5-b3de030e1aa9", - "name" : "offline_access", - "description" : "${role_offline-access}", - "composite" : false, - "clientRole" : false, - "containerId" : "REALM_NAME_PLACEHOLDER", - "attributes" : { } - }, { - "id" : "c7236465-e6c0-41b7-b206-1c33ec097212", - "name" : "uma_authorization", - "description" : "${role_uma_authorization}", - "composite" : false, - "clientRole" : false, - "containerId" : "REALM_NAME_PLACEHOLDER", - "attributes" : { } - } ], - "client" : { - "realm-management" : [ { - "id" : "5406f48d-98c9-4306-acc0-2924c88c871f", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "b9ec3294-c55b-4ed2-ab48-ad20fdb91150", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "9cd205bf-046a-4b35-91b7-62af0c6de03d", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "d32388e5-e99d-4675-a9db-48490c41b86b", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "8450b684-6ebf-42b4-8625-a5e4589674a8", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-groups", "query-users" ] + { + "id": "03ee0166-2480-429b-bc22-8f6fcd4f8126", + "name": "default-roles-REALM_NAME_PLACEHOLDER", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] } }, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "7190e385-42ea-4218-9945-d6f1877bf80a", - "name" : "query-realms", - "description" : "${role_query-realms}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "ce05b5fd-0245-44a7-900c-f3eb7a2e52cf", - "name" : "create-client", - "description" : "${role_create-client}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "04316910-c289-492f-9a36-b899ce1e1bc5", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "1f412284-d0bf-459f-8903-241e33d41a7e", - "name" : "realm-admin", - "description" : "${role_realm-admin}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "view-identity-providers", "manage-authorization", "impersonation", "manage-users", "view-users", "query-realms", "manage-realm", "create-client", "manage-clients", "manage-identity-providers", "view-clients", "query-groups", "view-events", "manage-events", "query-users", "view-authorization", "view-realm", "query-clients" ] - } + "clientRole": false, + "containerId": "a63b0d92-16b3-4110-8dd8-b25ed575035a", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "1305643f-47b2-471a-95b0-42f962443c0e", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} }, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "936d0734-b1e7-48e8-90d4-9ca37a70aa1c", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "281d791a-5bf7-4f15-9879-90f38814eb90", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "f58918cd-9fa7-45ff-9923-77461f6703be", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-clients" ] - } + { + "id": "deb716da-2b9c-429d-a23a-21e3e40caf11", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} }, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "0d9af750-bcd0-47c4-a912-616d9b6d2f5c", - "name" : "query-groups", - "description" : "${role_query-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "b82c3486-6936-457f-a574-744df850e0ce", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "06850f6c-5215-4525-9cb3-9aa4c352ebc9", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "547c6ac7-f463-4bed-b50f-fd51b7cad44e", - "name" : "query-users", - "description" : "${role_query-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "bf482b3c-1a94-460c-9c93-f17ce8323d3b", - "name" : "view-authorization", - "description" : "${role_view-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "67f49d28-e854-42d5-b6d2-bbab94ffbf81", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - }, { - "id" : "3ab06f48-ae3e-4cf2-a73a-a846859bd0ae", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "attributes" : { } - } ], - "security-admin-console" : [ ], - "admin-cli" : [ ], - "account-console" : [ ], - "broker" : [ { - "id" : "9ba35276-b7a8-45a5-a4c0-51792399e52b", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false, - "clientRole" : true, - "containerId" : "b4a282e1-0dc3-4841-b6e5-6b978efa945f", - "attributes" : { } - } ], - "account" : [ { - "id" : "efb75750-dff7-42b9-9e49-b81cad69683d", - "name" : "delete-account", - "description" : "${role_delete-account}", - "composite" : false, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - }, { - "id" : "65b56562-6f9b-4765-8a67-4c1850278207", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - }, { - "id" : "7cd9a351-fa28-44d8-9708-d0865c6b16c5", - "name" : "view-consent", - "description" : "${role_view-consent}", - "composite" : false, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - }, { - "id" : "32b5c183-fe02-4b92-a4a3-131f3668b43b", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } + { + "id": "be59b49b-4a79-400f-a67b-0a17903155c9", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} }, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - }, { - "id" : "eac2aa66-ed7e-4660-8c91-74b2e79037cd", - "name" : "view-applications", - "description" : "${role_view-applications}", - "composite" : false, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - }, { - "id" : "5682c606-875b-40cc-be5a-097964fc2674", - "name" : "view-groups", - "description" : "${role_view-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - }, { - "id" : "6c62fd99-f414-4f4c-8087-bcc0f3433abd", - "name" : "manage-consent", - "description" : "${role_manage-consent}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "view-consent" ] - } + { + "id": "9b2e7245-5859-49fd-a3b0-aeb215dc6e12", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "7e010c9f-565b-450b-a222-0122ab71010d", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "92004b3a-9187-4f73-9169-319dbdec02bb", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "696b1e73-b5f0-4990-a00f-943a53ff4555", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "aae4db77-9a27-4cb7-b6f6-3f109f553502", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "e2fd690b-88be-4953-8bff-3225e40fdbd4", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "39b3b817-bea6-47f5-8b34-4fc32211e433", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "2615c6ce-7dc4-448b-940f-26905a99b25d", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "254a5871-81e9-4e96-b6a1-cf6c27d3ddb2", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "81582a5b-5be7-45be-9c0b-52582ede762a", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "322ce24f-42f5-40c6-a8bd-d31734ac9834", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "30e5d91d-ac17-4812-ba11-cba05e1add77", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-authorization", + "view-realm", + "manage-realm", + "view-users", + "manage-clients", + "query-groups", + "manage-users", + "query-users", + "view-events", + "manage-authorization", + "query-realms", + "manage-events", + "manage-identity-providers", + "impersonation", + "view-clients", + "create-client", + "query-clients", + "view-identity-providers" + ] + } + }, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "c21b3f56-ea00-4e69-905d-88fdb6e78dfa", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "93612d37-df0a-4898-9c9a-c01afb1249b8", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "1e59ed3e-a91c-4ee3-8b29-a8da6c035549", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + }, + { + "id": "15a8e4ef-f406-44e6-b4dc-21fdce6fd023", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "61f1ed79-6efa-4109-9051-cd26de56f538", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "ed91f070-bce3-4b4c-a1bd-066bc96ff3e0", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "db9b8810-7b16-47a9-8b9e-8de58449c206", + "attributes": {} + } + ], + "account": [ + { + "id": "f583ff69-51dc-4c6e-8a1d-addf142c3220", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} + }, + { + "id": "9ac5bf5b-bb5d-4138-b397-04ea73622a60", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} + }, + { + "id": "ab3ce8f1-ec74-4a35-b00a-259db0bc0878", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} + }, + { + "id": "6907ab95-1c60-4f5b-912b-ee6d5c98346d", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} + }, + { + "id": "073dfba9-b2b5-42de-a147-e0ac00b9cd76", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} + }, + { + "id": "e9989640-ed85-40e5-86fc-51473fdafd4f", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} + }, + { + "id": "d5b8032e-e9b2-44fb-8354-ac3e068fb6d3", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} }, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - }, { - "id" : "81e41b35-e7d2-4331-bb41-c6fa6bd6cb8a", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", - "composite" : false, - "clientRole" : true, - "containerId" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "attributes" : { } - } ] + { + "id": "93560eab-3dd4-4ff4-b252-6f6c2103ff31", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "attributes": {} + } + ] } }, - "groups" : [ ], - "defaultRole" : { - "id" : "fe646314-5d85-48de-ad2a-7c699cc5d0b1", - "name" : "default-roles-REALM_NAME_PLACEHOLDER", - "description" : "${role_default-roles}", - "composite" : true, - "clientRole" : false, - "containerId" : "REALM_NAME_PLACEHOLDER" + "groups": [], + "defaultRole": { + "id": "03ee0166-2480-429b-bc22-8f6fcd4f8126", + "name": "default-roles-REALM_NAME_PLACEHOLDER", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "a63b0d92-16b3-4110-8dd8-b25ed575035a" }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppFreeOTPName" ], - "webAuthnPolicyRpEntityName" : "keycloak", - "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyRpId" : "", - "webAuthnPolicyAttestationConveyancePreference" : "not specified", - "webAuthnPolicyAuthenticatorAttachment" : "not specified", - "webAuthnPolicyRequireResidentKey" : "not specified", - "webAuthnPolicyUserVerificationRequirement" : "not specified", - "webAuthnPolicyCreateTimeout" : 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyAcceptableAaguids" : [ ], - "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyPasswordlessRpId" : "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", - "webAuthnPolicyPasswordlessCreateTimeout" : 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], - "scopeMappings" : [ { - "clientScope" : "offline_access", - "roles" : [ "offline_access" ] - } ], - "clientScopeMappings" : { - "account" : [ { - "client" : "account-console", - "roles" : [ "manage-account", "view-groups" ] - } ] + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppGoogleName", + "totpAppFreeOTPName" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] }, - "clients" : [ { - "id" : "2841004e-949f-45ec-bf80-304c92bfd02e", - "clientId" : "account", - "name" : "${client_account}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/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" : "+" + "clients": [ + { + "id": "28e61d0d-c4dc-4a91-8012-1a2f0325945c", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/REALM_NAME_PLACEHOLDER/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/REALM_NAME_PLACEHOLDER/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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "a4151274-6311-4571-9c88-a8c5f9d6f67e", - "clientId" : "account-console", - "name" : "${client_account-console}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/REALM_NAME_PLACEHOLDER/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/REALM_NAME_PLACEHOLDER/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" + { + "id": "a1797864-8592-4d75-b2c0-af15ca029abd", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/REALM_NAME_PLACEHOLDER/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/REALM_NAME_PLACEHOLDER/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": "7e32f2c3-d9fb-40e0-a618-fb68c921f9d9", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "2d2f1e79-74d0-4034-a12f-85bb795c4f28", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "ac095c61-18a0-42a6-8e28-c14039c0c2b8", - "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", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "b4a282e1-0dc3-4841-b6e5-6b978efa945f", - "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", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "b38bc4ca-ec20-4045-9e13-c9356fb51443", - "clientId" : "realm-management", - "name" : "${client_realm-management}", - "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", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "a2fe5b0d-93c7-4527-a9ea-cab77d8c2894", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "rootUrl" : "${authAdminUrl}", - "baseUrl" : "/admin/REALM_NAME_PLACEHOLDER/console/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/admin/REALM_NAME_PLACEHOLDER/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" + { + "id": "bb4d6126-c9c3-4c39-8016-805d04d55829", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "2ff543fa-afce-481b-9f47-c62ec659bf09", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - } ], - "clientScopes" : [ { - "id" : "eac7aa95-1d7a-4d2c-870a-6a117cb6e4cc", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" + { + "id": "db9b8810-7b16-47a9-8b9e-8de58449c206", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, - "protocolMappers" : [ { - "id" : "45192395-23af-4d25-970e-a5c7f58cc915", - "name" : "email verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "cc7059c4-97a9-40f0-9c4b-f9f185baecc6", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" + { + "id": "61f1ed79-6efa-4109-9051-cd26de56f538", + "clientId": "realm-management", + "name": "${client_realm-management}", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "6c009e16-d012-43ac-9093-ce95786c2cb8", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/REALM_NAME_PLACEHOLDER/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/REALM_NAME_PLACEHOLDER/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": "84f24df3-9b58-41fb-a05a-e591fc47e9d7", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "aa08b21f-f33f-4079-be3d-1e925b44c935", + "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" : "fd4625bb-8d0a-465b-876b-bc3199c48e89", - "name" : "web-origins", - "description" : "OpenID Connect scope for add allowed web origins to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" }, - "protocolMappers" : [ { - "id" : "1ca99df2-7843-4dfc-b124-71ecf271d642", - "name" : "allowed web origins", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", - "consentRequired" : false, - "config" : { } - } ] - }, { - "id" : "8771d8e1-af09-4894-a3d9-41a61db8e88c", - "name" : "address", - "description" : "OpenID Connect built-in scope: address", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" + { + "id": "ab4f2643-cc27-463e-bfba-44712cffb45e", + "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": "bcc3ec1d-d4bb-4171-82c3-e9b7f2de7371", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] }, - "protocolMappers" : [ { - "id" : "1fba188d-5586-4db9-918a-344b52d7e132", - "name" : "address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-address-mapper", - "consentRequired" : false, - "config" : { - "user.attribute.formatted" : "formatted", - "user.attribute.country" : "country", - "user.attribute.postal_code" : "postal_code", - "userinfo.token.claim" : "true", - "user.attribute.street" : "street", - "id.token.claim" : "true", - "user.attribute.region" : "region", - "access.token.claim" : "true", - "user.attribute.locality" : "locality" - } - } ] - }, { - "id" : "ef59b1b7-9685-4fb8-8b8a-271c0fa75aa6", - "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" + { + "id": "804093f4-8a08-4cca-a1a1-6008b7695e92", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "17a68f02-d8dc-4771-aeeb-d6e262e4fd07", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] }, - "protocolMappers" : [ { - "id" : "3775ef0c-440c-456a-977a-1e05766f1e40", - "name" : "acr loa level", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "0a886470-9443-4e0f-abe6-bda296f028d5", - "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" : "0f7a1eef-0785-47a5-a7b4-0d9981305cba", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" + { + "id": "bb39a86e-661c-47ef-aa54-d3d2da043340", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "17d26261-10f5-4f60-8cf5-c4106af3a3ee", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "4a18f46f-63ac-4577-89c1-caa895a05255", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] }, - "protocolMappers" : [ { - "id" : "52cc2865-4e65-4c79-80d0-06128dbb7237", - "name" : "realm roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "4a94f78d-06e5-4adb-b1ee-6f74b15274d8", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - }, { - "id" : "76cfb243-c416-4da3-8d57-858e27f6d2b8", - "name" : "client roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - } ] - }, { - "id" : "0bc87ace-6420-40d4-b5db-bb1583e028c0", - "name" : "microprofile-jwt", - "description" : "Microprofile - JWT built-in scope", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "false" + { + "id": "c91c9396-3cca-48c7-93b8-e93567f56f7e", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "888660e1-577c-4249-9703-e86241b9e714", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "3ddfe32a-e127-4808-8f59-a7ba97345258", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "ab023b96-89e4-41ed-b923-288bac047809", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "010b1c53-3403-476c-904f-81f51eaf297a", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "558d8acf-56c5-4011-a366-f4370ebfaa4c", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "5ca091f1-dd75-4f51-92cf-8fe52e1bd91a", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "c77ea1c7-af58-4fe4-8760-d5fc367a537d", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "8aa68575-a903-46ae-adb7-43b4c5573f88", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "319c76bc-93b5-4f78-b371-760f01143ffb", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "682141fe-c658-4a27-9c4f-d12cd6fd639d", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "31285f11-a0a0-4e95-a204-acda390961be", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "ee8c508a-f021-46c8-ba42-9f32b32aa504", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "6aad901c-f1c7-46d8-bfc2-ddd17b37e73d", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "72f7e384-849a-475f-ad2d-0ea19319edd3", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + } + ] }, - "protocolMappers" : [ { - "id" : "ee9bf484-6a3d-43a0-8919-c3cf57167b7d", - "name" : "groups", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "multivalued" : "true", - "user.attribute" : "foo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" - } - }, { - "id" : "9015f051-42c5-4258-b357-e74a5f2a7931", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "upn", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "853dc1f3-b1dc-4239-8552-8880018bbc51", - "name" : "phone", - "description" : "OpenID Connect built-in scope: phone", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" + { + "id": "204b6707-5432-43d6-928f-ed17669edc8e", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "6ffca153-999d-45dc-a98c-37ad1f79b04e", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "c8e75ea4-0006-4f7c-882c-8f1499bf14ca", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] }, - "protocolMappers" : [ { - "id" : "ac670383-8e32-45eb-a765-b7f2d7b0d655", - "name" : "phone number verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumberVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "63d3fd74-18c7-455f-89d9-bc6c8d0f8292", - "name" : "phone number", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumber", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "3bea839b-005f-4881-b0fd-c5fc8a76e7c2", - "name" : "role_list", - "description" : "SAML role list", - "protocol" : "saml", - "attributes" : { - "consent.screen.text" : "${samlRoleListScopeConsentText}", - "display.on.consent.screen" : "true" + { + "id": "24c8b752-95fa-4b11-9c99-1da3d7028783", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "07b45364-f53c-453f-9d6f-1122586e8633", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] }, - "protocolMappers" : [ { - "id" : "e4b4fb81-bd0f-4d0d-8e64-aeb17e1f1142", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - } ] - }, { - "id" : "211b8a03-74eb-40c8-88b7-d7caef3a8541", - "name" : "profile", - "description" : "OpenID Connect built-in scope: profile", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" + { + "id": "e0c34249-b2e2-49cf-b414-9aac15acd82b", + "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": "fcf43c71-170f-4c04-90b5-d6aefb16c7a5", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "88097d77-681a-4b3a-a025-92fae6182a15", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] }, - "protocolMappers" : [ { - "id" : "e6367671-17b8-468a-9ce3-443c07c34910", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "a0eff207-075b-48b9-bef9-3295b5cef7aa", - "name" : "gender", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "gender", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "gender", - "jsonType.label" : "String" - } - }, { - "id" : "f04b0ad0-aeee-4311-a542-d5f81033612b", - "name" : "updated at", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" - } - }, { - "id" : "aa7cf9ed-aa67-4cfc-a909-156273850148", - "name" : "birthdate", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" - } - }, { - "id" : "f222d5c5-26f9-42ea-8b18-e67e96da71ac", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - }, { - "id" : "26c89f6c-60d4-44b3-926f-735d69581ef7", - "name" : "profile", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "profile", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "profile", - "jsonType.label" : "String" - } - }, { - "id" : "d31c0cd0-9d46-45fb-af01-dccd915ddd46", - "name" : "zoneinfo", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "zoneinfo", - "jsonType.label" : "String" - } - }, { - "id" : "8b105682-eca1-4cea-96cc-91ddf3aaa47f", - "name" : "picture", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "picture", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "picture", - "jsonType.label" : "String" - } - }, { - "id" : "4c35f4b3-02dc-4f9b-9a71-8438b9e164c0", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "ebfc259f-67e9-4fca-8970-76e2514ca8fa", - "name" : "website", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "website", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "website", - "jsonType.label" : "String" - } - }, { - "id" : "ae52f1d2-a2a2-49e2-a346-6cc8b978173f", - "name" : "middle name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "middleName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "middle_name", - "jsonType.label" : "String" - } - }, { - "id" : "a1a900c6-a9e0-47e5-a4b9-f0162b61ce29", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "7643fd56-8747-498a-8ac0-50826747b38a", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "true" - } - }, { - "id" : "1c705cc2-6e68-4df1-8570-a29a4eb2043b", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - } ] - } ], - "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], - "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], - "browserSecurityHeaders" : { - "xContentTypeOptions" : "nosniff", - "xRobotsTag" : "none", - "xFrameOptions" : "SAMEORIGIN", - "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "xXSSProtection" : "1; mode=block", - "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + { + "id": "ac7c6a5d-9d21-4712-889f-951ba412ced0", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "9f08ee07-98a9-496d-b90c-897b38bd2dd3", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "6ba80bef-0a4f-4be1-9e34-89f1a9c8c976", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "9e774de8-10f9-4c1f-b70e-242f07532cf7", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "2086dce7-3707-4101-9174-7ca97082d228", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "7da3ae82-c74d-456e-a1f4-23f5d2ad6919", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityProviders" : [ ], - "identityProviderMappers" : [ ], - "components" : { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { - "id" : "a57f494a-2d08-4bef-8f25-8b4417593a80", - "name" : "Consent Required", - "providerId" : "consent-required", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "63c6ef5d-3e2a-4e67-8a57-028fe50c5db0", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper" ] - } - }, { - "id" : "2f252121-75d0-4440-b738-3a4cca8c0a4b", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "4431aa99-bc82-4a0a-bf67-8d30b25e0dda", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "c502f0e3-3f8d-46a7-b543-1fd8e269b362", - "name" : "Max Clients Limit", - "providerId" : "max-clients", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "max-clients" : [ "200" ] - } - }, { - "id" : "a5be73ee-e5c4-4021-b8fd-754ecf658d2f", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper" ] - } - }, { - "id" : "7a015fcb-607c-49ff-802c-e19bd384ee40", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "bac04512-481c-49e3-b420-49543988174a", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] - } - } ], - "org.keycloak.keys.KeyProvider" : [ { - "id" : "e2dfaaac-b826-44dd-a80d-36e50864217b", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEowIBAAKCAQEAoN16fM4OdG13SPzlnMrl8WDCMTYs+cNSMRwUh64HTXmOTRzWquw3k3CUihVeuHNV+/2A6dxeMZIpm4KFlBbr+KylB2nvweve2U2xEjBxbHVfR2DPNCh6d8pr+qup0AkxMx5EL0yKiGQA/eu+4Yz5rsd1U39ze/FSTkZb8sHogfqOlLcGgE6aPq6QGNWxDvdWSthRpCwvDxyJ5cI+EH3cSNaAxaTCbPotRjAv3cN2WNB3Tb6ZwfOagcbLCRsOm3o05s9Dc5prTj+eCbhrmcFaYqIs4tW+fA9D1rWgxlBsRRGeChpvdBkdJ55OJWLgut89Vlpd96yT8zL1wlYMxkyOyQIDAQABAoIBAA3sRWz2Z61fiRa93sC0lfTPj1czjW57n4pFGCjLIPItbmnRsGnAQqol8RFHeriDsQI7pXP4QQXQ1P2S7S1NU5SlGq561ofF+rZ7oTMGEtcUjI/QuvZUd48VK8XZJDwvg3zdZoDigs39u9E+XXnHmhesx0SYEB5+20UPlWc8LbvMGPBpYc+c7VtbJN56SwDK5+Jd/hKktAGzmCogeYohjINApSvcBZSD2uLd09AHcYv26SqX1cORHCBZ5hla1LKKKKgtncQTeIaVH/8Fmcc1SSrOiMMueXxHpqWVxM9G9i9LU+GJ8DujYKoR066mGQtKnvTl98eoVhrpw5IpEUnzgKECgYEA099mCn9V3QunAmRdbMaIWMsYfD+pjDRsKM71nh7kkkmuPwG+CHlkvb+o9HjZGsJqR0uX2ZO2zhZqMue1BSmIQy7h7cN0xgr3LHgj8JqWyAMV4kjR8CtgpLnn8r895IPVTv/fIzPkm+QFhpgdhG2mj1ZMZeZs1jXVdCL+vXbygvECgYEAwl53xs3+3fmWdDBIOX4WnSE/ncLkNGuo4dFvuW1hboR4VfYra4WOCdgpkzR6LcKV100gePd8niNlx+cLywiLuP6xackjtimP7d3qmfF/7Aj5yjD7ZH3gKyKbUTYqqpXOh02TpIxQsuOZ9tFCzUiIESSgTS5AmR137o1LFNFrmVkCgYAI3vmuxtYZgzti5kDjJQzgauCDk0bOEAPXUZMHI9P+LAeXq8MBJ9QH/dZdJhIxBKLeiVO6UGz/BAE+UGVL6glChtv+0Ig7Iopm2kDm+hIGHdr0tofTvn01aHNFvqwgvURZIm19U9bmkA2LLSNPQCgRKFPakGNONMLTMgPoeup8gQKBgEmt28Od1M46TYEvyEEa/eGEeAQKm80PPo6TgMGOqA9n4eHcFiuFDgnHSkNF8kaka36Y+wU/85pSmsODXuIRFD4FNiV+nK3x5WBq7gJ7HF3SLTYrCTDX0oEndi771IKjlQ2PJ7eJhRG622NHggYlR4cQFkALl2szU4I3R03V8O7JAoGBAMJerJp1uYAI5nma9Rs/4y3Fs6bssFNMjQeQjpr0YzZwxjQmFff4oqJ4H2wmA/nLu0srn8U7aK27g+hgEc/07c3TWw2+ZAkD/0XSi2Oy939IpxENzTnCXEXB25xx+MX+2xEF09bfWbauDcsHhLl3DJYWaFM9lpP9LfLOQsS8CRds" ], - "keyUse" : [ "ENC" ], - "certificate" : [ "MIICoTCCAYkCBgGGNmK7rDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wHhcNMjMwMjA5MTMzMzA1WhcNMzMwMjA5MTMzNDQ1WjAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCg3Xp8zg50bXdI/OWcyuXxYMIxNiz5w1IxHBSHrgdNeY5NHNaq7DeTcJSKFV64c1X7/YDp3F4xkimbgoWUFuv4rKUHae/B697ZTbESMHFsdV9HYM80KHp3ymv6q6nQCTEzHkQvTIqIZAD9677hjPmux3VTf3N78VJORlvyweiB+o6UtwaATpo+rpAY1bEO91ZK2FGkLC8PHInlwj4QfdxI1oDFpMJs+i1GMC/dw3ZY0HdNvpnB85qBxssJGw6bejTmz0NzmmtOP54JuGuZwVpioizi1b58D0PWtaDGUGxFEZ4KGm90GR0nnk4lYuC63z1WWl33rJPzMvXCVgzGTI7JAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIPfI2ci8HjmkLy1bYvNAPC+loZYi8oa6AyU5Zjq6fBQgbqfWfaou2aWANpU7wETdYy4+r6wNyBqGnXHrJH/DjnH0qa8nmIaoOQL6e88QRZHnTS1upQFq05zLQoATFzzZtkNzZymnfnADPy38skbEuBaY0deIefXfincPNBy4WG3x/tDq1mhf6Q7vW5g6g4IS1F9aGjYNvwD+ahaM4IxiqYiCFBa7jrSMLX/OTJbAHfBBSlZ9tNrZIn17NDBz2enNaIL6fV3IclxQx56AuC/Y85VwYP3/yJAyPtYeHDdBi42qdMSwD1niZKeciYiChZVYZtIYuUxIzDkicNBwC/wK6k=" ], - "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "fc6af199-36bb-4721-8805-65b15ac23677", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "4eca2c9d-3fd9-4798-a96f-66ab020f0999", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "a6493944-7b42-47c9-8161-15bcce5e1dea", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "20f18ede-0082-4211-8193-97b3faf58832", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "59fd957f-035a-4381-ba69-b07befc54769", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "b5cf58aa-ebfe-4da0-adcf-ad3faee18502", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "acec3143-8444-4d8f-9020-5fdd1992bfa8", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "6555ff94-661a-4659-9cde-0eb2147892ea", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } } - }, { - "id" : "5a014cec-1303-47c5-86a4-845754d11f6f", - "name" : "hmac-generated", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "4dd3a8df-f8a6-438b-aee9-9073a6acc042" ], - "secret" : [ "eTpCqpiimHnHwx1OGSWXQ1jciJqaBzb9QLBvG8lCD-IZPmTr9Vgs3wa6yUzfn9GOyrzH8zFknPhxLc_j16MAbQ" ], - "priority" : [ "100" ], - "algorithm" : [ "HS256" ] + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "f957405f-8f50-432a-8ade-8805d27e8b04", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEpAIBAAKCAQEA68PJqiC6cMTIaFvxsnhkyHXiOuwMxa3Udvu3lYJggl+3hMK9Pn1x7ojApUEch53qCIDLJADmr93aofdylZlLLdK/8vDViq8XWnodDfVDi6FttLhGhWiO7e37KswQ2wbosbQFbWIdU8brbsH1sm7snkwGTUuTXt3RDmWOlamomlbeumxNo9SaG+SSMdqwzB1KAu6IQwARvbA8LvzfMhlc6jh16o7CO1QTNFlbZ/8RDmu0T/pOF9TUccsw0hPiFbGzVy7+lANEVFXY9Un0sZTtb3xhJ6l9zsn07ZZEZ6D4COY0yGROWD5bvNUusZvDuzkg75myEHNDb4iZY0+DaiVRwQIDAQABAoIBABaWcUWOPCkzWTtSWm4tsFjaSRyPDUw1HW7G1VhgH4HuIgV4qNaKAIveEzg3QZX7+vY/QfccP6hoGhsRZit5/noQlRj1ROg0DTSQZXnhs8vMDALX8tMI6O9XsQwbyp5JY6rUI1RO+vNR/Vq6VMv4ak1iMupd4WvMgaEeTrz8/lfG6BxBcCGx8Wpvxsd9VvcAyPqXwI2C5y0JSnOJIHRxvLr2NmQeFdFppcVlucQV1SCzxnbMRcI5i0RKuOo/9yJJForwFJcFzkAxWgeUnJglNw/C/20s0aKwM+z+mBAFK/uZPjvEVcbTXj3KM1iuWs5l6FW03YyI5xO5EC2fRUmUKWcCgYEA+Yx2t6GSQ6YJ/v4DhaZv2rzEtn5G9Nk4J98w2GiQAjW4SBRp+vaWrmWhvkvrpX6lp741k9jK6CViayEE05orEHmVjHM885AiddQc88zxtCpGijMR1FuVdklswd0LnyB/PLWgr3f7Z1qAYyFCbxnpcg76jpQCpo36h40KEOO80BcCgYEA8dwZ1/Vwq6UDJBvM9QBvjOnW7Jeoico2m2Hsd74v9G1si+yywyvIMphVnlqoHsXqdxi79ZHJXiyfB/zmQWO9dEhcxq2OubaemDzeJLwXRcLW+Cg4VNIirDdZEWo/nhD7P+00IP/y8Z5neguWYVc01sLkCGB46z8Q/8R59xuZ++cCgYEA0/F9framD/h8MuqwORnDlEaQ1+HmB9xZOlvwE0yzSn0vl2BnJnO6REIjHglDCVrH/PCqdnhA1OuzbAMuIz2j56kr346cLMy0x9gwAsyEWB0zrfpz4SUrirwPt5MyZKLoDbrAz2aaygvuUMMVtmCOiYW5PdDtc2HQbsHV08RoP18CgYB09fi1fCcxioobUypprP1FCux529mQUO7Zc6CUQ7ATJzuf6yaDxc950DtPag31W8bIM3jqB8d2uGNrzHxZUO+UpU3gcpwb6VmGy6Ct6RvkC5ZDycd8FWbZG6cCCfyb5yBpyL812jDVccIevi3KAw81cGgwON8g/I2u8of83Sc5LwKBgQCtVDV1OHE24Y2DxRAakZ5fMp3NYIE8Y+5XrhwHdWRa72AaAz7DrU12b7yZJH5y/upV4Gv12Ia1o1pAx9mXSnVb0WSHp1A8UNtCVlivtnr66BGb5X5ucw53Wz+ciL3KgoHm5pU7Q/6UxttWyikvF2uBp6xMBWOcv3b1f6erxcw2aQ==" + ], + "keyUse": [ + "SIG" + ], + "certificate": [ + "MIICoTCCAYkCBgGGVKQ/mjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlkZW1vcmVhbG0wHhcNMjMwMjE1MTAzMzE1WhcNMzMwMjE1MTAzNDU1WjAUMRIwEAYDVQQDDAlkZW1vcmVhbG0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDrw8mqILpwxMhoW/GyeGTIdeI67AzFrdR2+7eVgmCCX7eEwr0+fXHuiMClQRyHneoIgMskAOav3dqh93KVmUst0r/y8NWKrxdaeh0N9UOLoW20uEaFaI7t7fsqzBDbBuixtAVtYh1TxutuwfWybuyeTAZNS5Ne3dEOZY6VqaiaVt66bE2j1Job5JIx2rDMHUoC7ohDABG9sDwu/N8yGVzqOHXqjsI7VBM0WVtn/xEOa7RP+k4X1NRxyzDSE+IVsbNXLv6UA0RUVdj1SfSxlO1vfGEnqX3OyfTtlkRnoPgI5jTIZE5YPlu81S6xm8O7OSDvmbIQc0NviJljT4NqJVHBAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHTSxIteVa2a1V4pqCHweJTRxoXOGE6048ZtEzOsO/5xElynszVZ1iMXlRq8uNTlc80Icx4hw17attP7KCColFIwZ0HCq1IfLQEquoHBaLDhIhVQ7ehCTkto/u8nLgfZL+NDkO+nT+i5eNctMQG74x6hTV3Urc5z4MnBtJNqtPvR+Lw0pbtH07f+K345Z06nxES0Hnb6TLsw8BuTTKPfSqVIAbsi4n3pUgULdeVRd0iYaSQdmwRaFEntWzUJlM4GjvaIPkaMS1CwjEBuutLzskVSiFlQ26W8KwXn7ySIBlec4ox3VD+Q7YynR64bB6Hs1MmFFuooTjn4c02uytLmK2o=" + ], + "priority": [ + "100" + ] + } + }, + { + "id": "d0d08daf-ba76-4e82-8183-37aa8f9a6af1", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEpAIBAAKCAQEA6wG/F8mo6eXBXSuShH9D2ybnMNUfh9dbRJ+Bporl/XNOSxBKaiyo+2W1VIdrjsedwmQuQoFjs2f229qzBS9Fl4ChLxlIjJEW1U+bPilRZUDlSM3q2HE6O4acP+nSIG8ZW9/KhXPgcanM+MixkM9cHzO7bqGKKcnCKD5Y8ia2Vc1PXPNguxmDwuj9XSlpdDOzRZiMUZzcqxzxWgyfUTD839NN16AM6tN4CBIuK779Zwah0ym4wLiVdX2UCJNVzVzuo16YoXYOlXC+skiWG+M4pjj3/IKkLQvzpO9Zh1x6NNMsstGQhPSRUq4KX4fquQVDwxDvIzsIWn2pcTmkOsdWSwIDAQABAoIBADCrTWx7PoimJOwLPI5FHwPxZBrIYH3M+2FUWVDo3iWlrha8mnSvqBVcZHfLjdple8YI4k2ypze99bFlcwLFXfe402jCJzS5TY3CrUdr3igGjxWLU7IcjO9L+ur/nR1LdOiCidome9p+TG1Pfvqa/xyVJaGNQeRSnOuhseEAZG2TMEidK0nWI1NQdm5Q6hsNI2UcPbq3+0ouHz2xBYGoKln76Ibqk5yfNty60jtxeQwg1P0I5PjiyLIiOQUKzaGEX6NENr/WI0qXLr5460ioCqI3Urrgml+lDij/Tg5BXs/Dd1awbup3/vUId/gd7Hy99tXDOK0iXyjZThfgBhsBiYUCgYEA/5zJhrn3g3YyFhPljP4xQBP3k4f/sPBihNwshsa/P8DBIFvxBE3jL+u8QZ/d1PU935gq4183HT8Nk7DFoQ0pntdLyJxfZ5oF07d0qYoHY++R5OIqX2VK2AXBGbwuCUct/XXjEUn83MSBgg1GGbY6qyty1UwBqQhN+qqiOxcUfS0CgYEA61z2IBNOs0T+ns4SsLZERM7uu2mV0IokaLQ60Rbk+7+sV+Mx0eapp4rXuxE33YFRrsvHonyfvbNZtqBnyw7fU8PQm5ozl9T1jeUAdgqm8l5cqpYswASL/NkWLkK3+D+ynLLjKygDZqMj3ZMO8c7nYw799QQaw4T9pzpJZjJLfFcCgYEArOk11kKEsdRJy2+IMBlf3ZXkO1ObXukt6+w43q8hfpH40tf/MUcy8R7JiacIW9/ODCwWjxrA4LLfj1HcTrblucKwTDOjwiSJ3o9ShsGNgEf2bFumCEQwHfO+jZcjmTkiXjvZ778aI4l2hjBOhGQmSdYpZyp0URECFxhIiCpzvL0CgYAYrtodCQlS4aR2URRCtgq40J7Wxr7wbNxeorAcZ3NCN5rCaNA7vB4EtRnkw2yBbWN8mmBoWPuDsIBzF6Vq9TdUmI+TEfvhK3NJG0AOIRXbCyxas38j8BYiQT4DQfn7Ler0ZgpO51Zb+DX1sct6boFzsQnPHUwVPyg+1m0GK7Yg5wKBgQDCIYvL6JhJidgP9ve3U0d0NUDAndRE5fuGqMcKvBiiRAE2D8RxHIWDre/NzrSLAst4UzmuPLG1LB1mIIfUHoPlHX5yEHbBRWtKeJUZ8tEU2z6uNZxP+ntf/K8fdkBS0jbowfG2G/nqqlyJuIrQduNvtT6aoZK0UjRtZ7iAdMjs5Q==" + ], + "keyUse": [ + "ENC" + ], + "certificate": [ + "MIICoTCCAYkCBgGGVKRAEDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlkZW1vcmVhbG0wHhcNMjMwMjE1MTAzMzE1WhcNMzMwMjE1MTAzNDU1WjAUMRIwEAYDVQQDDAlkZW1vcmVhbG0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDrAb8Xyajp5cFdK5KEf0PbJucw1R+H11tEn4GmiuX9c05LEEpqLKj7ZbVUh2uOx53CZC5CgWOzZ/bb2rMFL0WXgKEvGUiMkRbVT5s+KVFlQOVIzerYcTo7hpw/6dIgbxlb38qFc+Bxqcz4yLGQz1wfM7tuoYopycIoPljyJrZVzU9c82C7GYPC6P1dKWl0M7NFmIxRnNyrHPFaDJ9RMPzf003XoAzq03gIEi4rvv1nBqHTKbjAuJV1fZQIk1XNXO6jXpihdg6VcL6ySJYb4zimOPf8gqQtC/Ok71mHXHo00yyy0ZCE9JFSrgpfh+q5BUPDEO8jOwhafalxOaQ6x1ZLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEkOE5zWi8x3NfPWsdZkLckRw9vQFDYhQuaRZfD1i3lKvYW9xERFjOICuSHUX9yxX/CpGWVDAomXXXYxvlPpkENO/GjUGcL5UH6AopPkOzUZwsVWEylR6nczlK9sXjfjqgDOFwV19q5bn6hiV6qLG4Ty747lCATaxCUDPwJvZE8ZLFTHrccFtAH8VXCZbJJMLXO42LgMeG5nF4Tf1K0iI+4sSDWshl+QNugmaRQSrG0IklH4+U9J9JTwAiibkspgVsc2ubhbA+Xvzndg0vIQ2cEVWLqK9tRAIuaoANTmJneBZdBoVL5gtY3jCyXj6ecrKr+4JzeXCwmrxQUk9061eDU=" + ], + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "0d0237f6-29a9-4e1d-8de0-0490ab1c9118", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "kid": [ + "1523f166-af8b-4970-94ff-eabb0f391532" + ], + "secret": [ + "ByJpObMtnXpqZwygKnj_cgP-SkhVJPH_D-vgOS8yRSH9Km9idi4Ryh5w__zKGk4OeIJWQWRSz5VG_6kzINchKw" + ], + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "53a26567-105e-4b11-bbd1-1516fe76104a", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "kid": [ + "8d1e845c-f3bc-4b6c-86c5-acbc49442386" + ], + "secret": [ + "EWEleDkIM15zP8tyIr6nHw" + ], + "priority": [ + "100" + ] + } } - }, { - "id" : "3c8cc563-5b4e-410e-9318-cf6019a62fb3", - "name" : "rsa-generated", - "providerId" : "rsa-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEpAIBAAKCAQEAr7zgzlIYcj6ufYyypCuvfNPmw7h3278AGZ8yYRs0OLbFULILUbVZCv4itBVHtl/1u5IxnsCNBbIKoycLtXtK74gOa/DcmZbeOuW+INIMNbHFTqUwIiFvXt18koPz1Kptp2aZKI/w1IrOvgdHkdVvH0COB++gkTK5sej4FwC3L+eBItD+TgW4qQY7GRN/erq+GiwxhjhabecxOGZpo/XpeHKuD7wygKwkdjkUtFQeJ9MJHDYjSZvVaqkFNJusGdmWUP5N390kc6AbyTbQ3LD1Ck0YPCytYHhS4PJ0ztdVZ+pI/yQ0+AHxObB0xfXxvCMSJfF0od6mYOoa1y5KRqwyNQIDAQABAoIBADOsh7PTKvII4Vj0cqDYYZkEW/IH/2Jjlr1x9KfeDmxwvbM9LxvfBag4Uu795ODI9aIqWiHSf4UBtaQEuqop7z5glNEDeXJGDOFnpZDUApgVRi2aX/1hNHTCDrusRhmt5WFYR0fIj2mHyUPSMya8Chk366uEuAOJ1VVtaemlobfZT3quB2hh+QBDEz4dg9DwNE5NCeABCpU3x2yiIqwSMf2oTxjaQmTN7Bt5vg/N482QBk60TGgMrP4ewU8P7iDEayqCmyVnxGDhhocSl0n2zQwZsd2rdRYLFBJ8D+MRSoPUI82FR94IXBBMUaSIUd+/g2AXYVnWEFcY4PKooPY/YY8CgYEA2BtZuSe2+Iq8TFG0JXQMVDFvkl5tgiXNw03r3VaPbqhoe9LPzx75KS7Y5YaA9LONYN9wX7txZxVq+zYEvR/JNJfZAKAET7VvoV7FkoRau3AGHl4aQR8mCivKSu8dl7pGORBdzFwM8Q/FGP3ijj6zmWMohSurrERu+M5+MydLNZMCgYEA0C3KrsNVJ7iwASl+0tzvL5poECxX60S6FY/ARJpVOpqNXwZTZmcswW69ZsmbWG+GcOd4kpBc/c8SWo3wpfE+D8IrQEv7zIEpUau25XNjvey850pzH9enZYjHLm95sDgtQbiZeaETQUM6XoN0cmNAycTJl//+il9rQCthI3xJVhcCgYEAyvF8Y+XI7UUUKbO8skN15Ib8WP82ZbdssRF3A8frMKcUX9wMbVyP7j3hUELszV/kz+llFXHowD6b1cfX0WeNQ0eqcOzUv6oKaAehEMQclNNR26jRjQBcb8bJ1W8SYk967NHS1DxJpwQBO0QJQz3c2ZoGDnGBaWhmsl7woscu2OMCgYAayzoCAkwMf73PeHrFQwBZ/SKVqk9qjX7x1X2mlsKPd2LorxS+4lqlTs5700j2Oexwzan8Gy6M0EEJfKlTglg9iP0uGll/OcSKqXIx2AcnY7fwgOmOWKBH9jHErT5ZSvGwOsUuHWgwByoVDhBHxLSLA5P6x00InD1elfa7rXx24wKBgQCYefkhORVweEYZJM1g7rR8L6kpTHqrDR3X0V15p6dLxxP4ZcdcdnGeD6BqqxQ6J41k15jU7ZpqWpVgsZxbHQbRlmZV8RbsOAWZyRLP7iVTNTaAHC2JD4tocXoj9dhT65ohFz2r0u7lmis/kUbJIwsBHzwqYcNKG5SAqg3OFYOBMQ==" ], - "keyUse" : [ "SIG" ], - "certificate" : [ "MIICoTCCAYkCBgGGNmK7HDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wHhcNMjMwMjA5MTMzMzA1WhcNMzMwMjA5MTMzNDQ1WjAUMRIwEAYDVQQDDAl0ZXN0cmVhbG0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvvODOUhhyPq59jLKkK6980+bDuHfbvwAZnzJhGzQ4tsVQsgtRtVkK/iK0FUe2X/W7kjGewI0FsgqjJwu1e0rviA5r8NyZlt465b4g0gw1scVOpTAiIW9e3XySg/PUqm2nZpkoj/DUis6+B0eR1W8fQI4H76CRMrmx6PgXALcv54Ei0P5OBbipBjsZE396ur4aLDGGOFpt5zE4Zmmj9el4cq4PvDKArCR2ORS0VB4n0wkcNiNJm9VqqQU0m6wZ2ZZQ/k3f3SRzoBvJNtDcsPUKTRg8LK1geFLg8nTO11Vn6kj/JDT4AfE5sHTF9fG8IxIl8XSh3qZg6hrXLkpGrDI1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBADWKt3hBcClaQB4ws16+uUFC7tijyz1d7y1dpgP12nxjsfcKDnoEySl3Epb7LWXJfsynOdvMepH4Od3PBh2YLUmTFx5Yf+6+ATEuvrXesuUojmFf8hklEZXborkfRBpkDqfW82ctCxEi9W9beD5m9ZEHbz0bs25oR5zroDj7OZrnHeVGAXXKoeraNmSdOmN3cMLJ7AKRuDuOmfT7XbxH96027FeWBT5sR44wPscFU9I8XQ6JaH5+v2XVUVMuKAN+fjEkqCzLX2Iti8MSMNJQ5t8dC59gMamlrujTd+jH0G5nDz2LritJ+VkHU0kGLDwh1qGROVBVb2FJIuzRgEZMagY=" ], - "priority" : [ "100" ] + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "c3820085-85d3-4ff8-bd42-15464fa668b6", + "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": "815709b3-7421-44b3-bf18-4838cf354852", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "6b97ee19-cc94-435d-92cd-c16cec1ce04e", + "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": "8ce4d49e-4ef9-4f99-ab46-3693b7b4a093", + "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": "2d7a3dd7-cfa2-427c-b1c6-aa91a4cd6c63", + "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": "bf73abb4-36f2-4338-8578-86ab06bb1131", + "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": "65caf860-42ee-4bbe-aafe-468c838cf602", + "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": "bf7960c0-0247-442b-b381-bea5646a4912", + "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": "1f2b94c0-0f10-4484-a224-0699141f7c02", + "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": "a34173a7-4ad7-4817-ad11-9ddc459854aa", + "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": "a69e205f-6ae1-4aa1-9ed1-806dbc5f7168", + "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": "18b4d1ed-9fcd-41b2-8db0-cdc74d9ad155", + "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": "7aeeddfa-01fa-43c2-80ad-3d7089b8d952", + "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": "4e0d5e38-0c28-4ad3-b904-e1dcde038e7f", + "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": "c14d77a8-4c9f-429d-9d75-fd5d5046e140", + "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": "e58d48bb-d404-40d9-9cfb-5fa5d450fef1", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "287e7e9a-1b9a-40b8-9d96-2718f336c12a", + "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": "bad61d64-c8fb-4dad-b8fe-ce25da2de253", + "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-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "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 + } + ] + }, + { + "id": "68449dd8-7be7-4150-82fe-fb75b67c896e", + "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": "baded0d1-49db-4723-8924-19f1c6e4b0d3", + "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": "c8e1e585-b8bb-417c-943d-079afbae72c5", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" } - }, { - "id" : "1a2b0841-81a8-4a34-9725-865207b3eea9", - "name" : "aes-generated", - "providerId" : "aes-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "28988101-aab0-4a6d-ac5a-8c9354921a8f" ], - "secret" : [ "Ukfu-vGx0yhCe2vDWh2eTQ" ], - "priority" : [ "100" ] + }, + { + "id": "07e5d37a-e1ed-4957-9802-f02b8565c715", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" } - } ] - }, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "id" : "2f656614-b183-4d5d-ac49-77ec1ce255dd", - "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" : "2320409f-2ea6-4829-9f79-e20f96ee0c0d", - "alias" : "Authentication Options", - "description" : "Authentication options.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "basic-auth", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "basic-auth-otp", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "9600922d-2b69-48d5-9bdd-7301ac7e2d86", - "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" : "b7a6599c-c0ed-4528-b5f6-bd532f13d1fb", - "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" : "a3cb92d3-7aec-4714-acf9-d875a14e54cd", - "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" : "faee7139-7369-4794-823c-4aa00db351b9", - "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" : "1b987aac-f443-4d3d-a731-c67af88cc83c", - "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" : "e11a18f3-e58b-4a45-85f2-de69cc48f19c", - "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" : "6c057f26-b8ed-40df-a27b-bef2d2ec0416", - "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" : "3b77214d-0f25-43d8-8429-3427d50a153f", - "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" : "19faf3b5-9c26-421a-9ba1-94721f442a37", - "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" : "1cf28966-e26e-48d2-b6d9-887e591524ff", - "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" : "e4698ed9-1fa3-4e30-a07c-dc7876a6b7c9", - "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" : "c9bda4af-29a5-4298-9882-306c22be2ecf", - "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" : "74883789-91ed-47b7-83b9-7738179b8a2e", - "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" : "ec833733-3e09-41aa-816d-c6c68ccf59bf", - "alias" : "http challenge", - "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "no-cookie-redirect", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Authentication Options", - "userSetupAllowed" : false - } ] - }, { - "id" : "5fce633c-3e98-406c-9d9f-4fa9a60aa1f7", - "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" : "3d2443de-dbb3-4c32-aaa7-3e7e607b0848", - "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-profile-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 40, - "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 - } ] - }, { - "id" : "97bcd182-ed8e-451e-af92-f1c2a9d2381a", - "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" : "59340546-1da0-42e4-bd4b-934a84ee1098", - "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" : "4b9bb466-54be-4b63-b97a-8556986bd192", - "alias" : "create unique user config", - "config" : { - "require.password.update.after.registration" : "false" } - }, { - "id" : "b0488377-25d8-4518-a9b8-672e4c9ddd18", - "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": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} } - } ], - "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" : "update_user_locale", - "name" : "Update User Locale", - "providerId" : "update_user_locale", - "enabled" : true, - "defaultAction" : false, - "priority" : 1000, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients", - "dockerAuthenticationFlow" : "docker auth", - "attributes" : { - "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaExpiresIn" : "120", - "cibaAuthRequestedUserHint" : "login_hint", - "oauth2DeviceCodeLifespan" : "600", - "oauth2DevicePollingInterval" : "5", - "parRequestUriLifespan" : "60", - "cibaInterval" : "5", - "realmReusableOtpCode" : "false" + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" }, - "keycloakVersion" : "20.0.3", - "userManagedAccessAllowed" : false, - "clientProfiles" : { - "profiles" : [ ] + "keycloakVersion": "20.0.3", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] }, - "clientPolicies" : { - "policies" : [ ] + "clientPolicies": { + "policies": [] } } From 7a96074b70213d3c6d246be7541c3fbad0437f0f Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 8 Mar 2023 09:49:12 +0100 Subject: [PATCH 29/49] Better handling for roles --- .../NormalizationConfiguration.java | 7 +- .../normalize/GroupNormalizationService.java | 1 + .../normalize/RoleNormalizationService.java | 122 +++++++++++------- 3 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 4bdf72048..3de29fc23 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import org.javers.core.Javers; import org.javers.core.JaversBuilder; @@ -66,6 +68,8 @@ public Javers unOrderedJavers() { public YAMLMapper yamlMapper() { var ym = new YAMLMapper(); ym.setSerializationInclusion(JsonInclude.Include.NON_NULL); + ym.enable(SerializationFeature.INDENT_OUTPUT); + ym.enable(YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR); return ym; } @@ -73,6 +77,7 @@ public YAMLMapper yamlMapper() { public ObjectMapper objectMapper() { var om = new ObjectMapper(); om.setSerializationInclusion(JsonInclude.Include.NON_NULL); + om.enable(SerializationFeature.INDENT_OUTPUT); return om; } @@ -111,7 +116,7 @@ private JaversBuilder commonJavers() { List.of("id", "authorizationSettings", "protocolMappers"))) .registerEntity(new EntityDefinition(ProtocolMapperRepresentation.class, "name", List.of("id"))) .registerEntity(new EntityDefinition(ClientScopeRepresentation.class, "name", List.of("id", "protocolMappers"))) - .registerEntity(new EntityDefinition(RoleRepresentation.class, "name", List.of("id", "containerId", "composites"))) + .registerEntity(new EntityDefinition(RoleRepresentation.class, "name", List.of("id", "containerId", "composites", "attributes"))) .registerEntity(new EntityDefinition(GroupRepresentation.class, "path", List.of("id", "subGroups", "attributes", "clientRoles"))) .registerEntity(new EntityDefinition(AuthenticationFlowRepresentation.class, "alias", List.of("id", "authenticationExecutions"))) .registerEntity(new EntityDefinition(IdentityProviderRepresentation.class, "alias", List.of("internalId"))) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java index 520006c1b..0e08894a6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java @@ -145,6 +145,7 @@ public void normalizeGroupList(List groups) { normalizeGroupList(group.getSubGroups()); } } + group.setId(null); } } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java index 49e302cd8..79d35db92 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java @@ -20,9 +20,7 @@ package de.adorsys.keycloak.config.service.normalize; -import de.adorsys.keycloak.config.util.JaversUtil; import org.javers.core.Javers; -import org.javers.core.diff.changetype.PropertyChange; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RolesRepresentation; import org.slf4j.Logger; @@ -45,14 +43,11 @@ public class RoleNormalizationService { private static final Logger logger = LoggerFactory.getLogger(RoleNormalizationService.class); private final Javers unOrderedJavers; - private final JaversUtil javersUtil; - private final AttributeNormalizationService attributeNormalizationService; @Autowired - public RoleNormalizationService(Javers unOrderedJavers, JaversUtil javersUtil, AttributeNormalizationService attributeNormalizationService) { + public RoleNormalizationService(Javers unOrderedJavers, AttributeNormalizationService attributeNormalizationService) { this.unOrderedJavers = unOrderedJavers; - this.javersUtil = javersUtil; this.attributeNormalizationService = attributeNormalizationService; } @@ -61,69 +56,98 @@ public RolesRepresentation normalizeRoles(RolesRepresentation exportedRoles, Rol var baselineOrEmpty = baselineRoles == null ? new RolesRepresentation() : baselineRoles; var clientRoles = normalizeClientRoles(exportedOrEmpty.getClient(), baselineOrEmpty.getClient()); var realmRoles = normalizeRealmRoles(exportedOrEmpty.getRealm(), baselineOrEmpty.getRealm()); - if (clientRoles == null && realmRoles == null) { - return null; - } var normalizedRoles = new RolesRepresentation(); - normalizedRoles.setClient(clientRoles); - normalizedRoles.setRealm(realmRoles); + if (!clientRoles.isEmpty()) { + normalizedRoles.setClient(clientRoles); + } + if (!realmRoles.isEmpty()) { + normalizedRoles.setRealm(realmRoles); + } return normalizedRoles; } - public List normalizeRealmRoles(List exportedRealmRoles, List baselineRealmRoles) { - List exportedOrEmpty = exportedRealmRoles == null ? List.of() : exportedRealmRoles; - List baselineOrEmpty = baselineRealmRoles == null ? List.of() : baselineRealmRoles; + public List normalizeRealmRoles(List exportedRoles, List baselineRoles) { + return normalizeRoleList(exportedRoles, baselineRoles, null); + } + + public Map> normalizeClientRoles(Map> exportedRoles, + Map> baselineRoles) { + Map> exportedOrEmpty = exportedRoles == null ? Map.of() : exportedRoles; + Map> baselineOrEmpty = baselineRoles == null ? Map.of() : baselineRoles; + + var normalizedRoles = new HashMap>(); + for (var entry : baselineOrEmpty.entrySet()) { + var clientId = entry.getKey(); + var baselineClientRoles = entry.getValue(); + var exportedClientRoles = exportedOrEmpty.remove(clientId); + exportedClientRoles = exportedClientRoles == null ? List.of() : exportedClientRoles; + + var normalizedClientRoles = normalizeRoleList(exportedClientRoles, baselineClientRoles, clientId); + if (!normalizedClientRoles.isEmpty()) { + normalizedRoles.put(clientId, normalizedClientRoles); + } + } + + for (var entry : exportedOrEmpty.entrySet()) { + var clientId = entry.getKey(); + var roles = entry.getValue(); + + if (!roles.isEmpty()) { + normalizedRoles.put(clientId, normalizeList(roles)); + } + } + return normalizedRoles; + } - var exportedMap = exportedOrEmpty.stream().collect(Collectors.toMap(RoleRepresentation::getName, Function.identity())); - var baselineMap = baselineOrEmpty.stream().collect(Collectors.toMap(RoleRepresentation::getName, Function.identity())); + public List normalizeRoleList(List exportedRoles, + List baselineRoles, String clientId) { + List exportedOrEmpty = exportedRoles == null ? List.of() : exportedRoles; + List baselineOrEmpty = baselineRoles == null ? List.of() : baselineRoles; + var exportedMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(RoleRepresentation::getName, Function.identity())); + var baselineMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(RoleRepresentation::getName, Function.identity())); var normalizedRoles = new ArrayList(); for (var entry : baselineMap.entrySet()) { var roleName = entry.getKey(); - var baselineRole = entry.getValue(); var exportedRole = exportedMap.remove(roleName); if (exportedRole == null) { - logger.warn("Default realm role '{}' was deleted in exported realm. It may be reintroduced during import", roleName); + if (clientId == null) { + logger.warn("Default realm role '{}' was deleted in exported realm. It may be reintroduced during import!", roleName); + } else { + logger.warn("Default realm client-role '{}' for client '{}' was deleted in the exported realm. " + + "It may be reintroduced during import!", roleName, clientId); + } continue; } + + var baselineRole = entry.getValue(); + var diff = unOrderedJavers.compare(baselineRole, exportedRole); + if (diff.hasChanges() - || attributeNormalizationService.listAttributesChanged(exportedRole.getAttributes(), baselineRole.getAttributes()) - || compositesChanged(exportedRole.getComposites(), baselineRole.getComposites())) { - var normalizedRole = new RoleRepresentation(); - normalizedRole.setName(roleName); - for (var change : diff.getChangesByType(PropertyChange.class)) { - javersUtil.applyChange(normalizedRole, change); - } - normalizedRole.setAttributes(attributeNormalizationService.normalizeListAttributes(exportedRole.getAttributes(), - baselineRole.getAttributes())); - normalizedRoles.add(normalizedRole); - normalizedRole.setComposites(exportedRole.getComposites()); + || compositesChanged(exportedRole.getComposites(), baselineRole.getComposites()) + || attributeNormalizationService.listAttributesChanged(exportedRole.getAttributes(), baselineRole.getAttributes())) { + normalizedRoles.add(exportedRole); } } - return normalizedRoles.isEmpty() ? null : normalizedRoles; + normalizedRoles.addAll(exportedMap.values()); + return normalizeList(normalizedRoles); } - private boolean compositesChanged(RoleRepresentation.Composites exportedComposites, RoleRepresentation.Composites baselineComposites) { - return unOrderedJavers.compare(baselineComposites, exportedComposites).hasChanges(); - } - - public Map> normalizeClientRoles(Map> exportedClientRoles, - Map> baselineClientRoles) { - Map> exportedOrEmpty = exportedClientRoles == null ? Map.of() : exportedClientRoles; - Map> baselineOrEmpty = baselineClientRoles == null ? Map.of() : baselineClientRoles; - - Map> normalizedClientRoles = new HashMap<>(); - for (var entry : baselineOrEmpty.entrySet()) { - var clientName = entry.getKey(); - var baselineRoles = entry.getValue(); - var exportedRoles = exportedOrEmpty.remove(clientName); - - var normalizedRoles = normalizeRealmRoles(exportedRoles, baselineRoles); - if (normalizedRoles != null) { - normalizedClientRoles.put(clientName, normalizedRoles); + public List normalizeList(List roles) { + for (var role : roles) { + role.setId(null); + if (role.getAttributes().isEmpty()) { + role.setAttributes(null); } } - return normalizedClientRoles.isEmpty() ? null : normalizedClientRoles; + return roles; + } + + public boolean compositesChanged(RoleRepresentation.Composites exportedComposites, RoleRepresentation.Composites baselineComposites) { + return unOrderedJavers.compare(baselineComposites, exportedComposites) + .hasChanges(); } } From 93c5fbc16151fac9c1d2c984707f403eb83804cd Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 9 Mar 2023 14:46:33 +0100 Subject: [PATCH 30/49] Fix incomplete required actions --- .../RequiredActionNormalizationService.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java index 410ed9359..c2a9325de 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java @@ -20,9 +20,7 @@ package de.adorsys.keycloak.config.service.normalize; -import de.adorsys.keycloak.config.util.JaversUtil; import org.javers.core.Javers; -import org.javers.core.diff.changetype.PropertyChange; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,11 +39,9 @@ public class RequiredActionNormalizationService { private static final Logger logger = LoggerFactory.getLogger(RequiredActionNormalizationService.class); private final Javers javers; - private final JaversUtil javersUtil; - public RequiredActionNormalizationService(Javers javers, JaversUtil javersUtil) { + public RequiredActionNormalizationService(Javers javers) { this.javers = javers; - this.javersUtil = javersUtil; } public List normalizeRequiredActions(List exportedActions, @@ -70,14 +66,7 @@ public List normalizeRequiredActions(List< var diff = javers.compare(baselineAction, exportedAction); if (diff.hasChanges()) { - var normalizedAction = new RequiredActionProviderRepresentation(); - normalizedAction.setAlias(alias); - for (var change : diff.getChangesByType(PropertyChange.class)) { - javersUtil.applyChange(normalizedAction, change); - } - normalizedAction.setEnabled(exportedAction.isEnabled()); - normalizedAction.setDefaultAction(exportedAction.isDefaultAction()); - normalizedActions.add(normalizedAction); + normalizedActions.add(exportedAction); } } normalizedActions.addAll(exportedMap.values()); From f3282bf86fc4444c923c2f939287a673b54b230d Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Fri, 10 Mar 2023 11:16:26 +0100 Subject: [PATCH 31/49] Fix normalization bug with missing properties --- .../AuthFlowNormalizationService.java | 18 +++----- .../ClientScopeNormalizationService.java | 40 ++++++++---------- .../normalize/GroupNormalizationService.java | 14 +------ .../IdentityProviderNormalizationService.java | 31 ++++++-------- .../ProtocolMapperNormalizationService.java | 41 ++++++++++++++----- 5 files changed, 66 insertions(+), 78 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index ecafca602..f9c0aecd3 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -20,9 +20,7 @@ package de.adorsys.keycloak.config.service.normalize; -import de.adorsys.keycloak.config.util.JaversUtil; import org.javers.core.Javers; -import org.javers.core.diff.changetype.PropertyChange; import org.keycloak.representations.idm.AbstractAuthenticationExecutionRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; @@ -51,11 +49,8 @@ public class AuthFlowNormalizationService { private final Javers unOrderedJavers; - private final JaversUtil javersUtil; - - public AuthFlowNormalizationService(Javers unOrderedJavers, JaversUtil javersUtil) { + public AuthFlowNormalizationService(Javers unOrderedJavers) { this.unOrderedJavers = unOrderedJavers; - this.javersUtil = javersUtil; } public List normalizeAuthFlows(List exportedAuthFlows, @@ -80,16 +75,13 @@ public List normalizeAuthFlows(List normalizeClientScopes(List exportedScopes, @@ -71,29 +67,27 @@ public List normalizeClientScopes(List normalizedScopes) { + for (var scope : normalizedScopes) { scope.setId(null); - normalizedScopes.add(scope); + if (scope.getProtocolMappers() != null) { + for (var mapper : scope.getProtocolMappers()) { + mapper.setId(null); + } + } + if (scope.getProtocolMappers().isEmpty()) { + scope.setProtocolMappers(null); + } } - return normalizedScopes.isEmpty() ? null : normalizedScopes; } public boolean clientScopeChanged(ClientScopeRepresentation exportedScope, ClientScopeRepresentation baselineScope) { diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java index 0e08894a6..2731b099f 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java @@ -20,9 +20,7 @@ package de.adorsys.keycloak.config.service.normalize; -import de.adorsys.keycloak.config.util.JaversUtil; import org.javers.core.Javers; -import org.javers.core.diff.changetype.PropertyChange; import org.keycloak.representations.idm.GroupRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,14 +40,11 @@ public class GroupNormalizationService { private static final Logger logger = LoggerFactory.getLogger(GroupNormalizationService.class); private final Javers unOrderedJavers; - private final JaversUtil javersUtil; private final AttributeNormalizationService attributeNormalizationService; public GroupNormalizationService(Javers unOrderedJavers, - JaversUtil javersUtil, AttributeNormalizationService attributeNormalizationService) { this.unOrderedJavers = unOrderedJavers; - this.javersUtil = javersUtil; this.attributeNormalizationService = attributeNormalizationService; } @@ -75,14 +70,7 @@ public List normalizeGroups(List expor if (diff.hasChanges() || subGroupsChanged(exportedGroup, baselineGroup) || attributeNormalizationService.listAttributesChanged(exportedGroup.getAttributes(), baselineGroup.getAttributes()) || attributeNormalizationService.listAttributesChanged(exportedGroup.getClientRoles(), baselineGroup.getClientRoles())) { - var normalizedGroup = new GroupRepresentation(); - for (var change : diff.getChangesByType(PropertyChange.class)) { - javersUtil.applyChange(normalizedGroup, change); - } - normalizedGroup.setAttributes(exportedGroup.getAttributes()); - normalizedGroup.setClientRoles(exportedGroup.getClientRoles()); - normalizedGroup.setPath(exportedGroup.getPath()); - normalizedGroups.add(normalizedGroup); + normalizedGroups.add(exportedGroup); } } normalizedGroups.addAll(exportedGroupsMap.values()); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java index ba9053744..c491ba7c2 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java @@ -20,9 +20,7 @@ package de.adorsys.keycloak.config.service.normalize; -import de.adorsys.keycloak.config.util.JaversUtil; import org.javers.core.Javers; -import org.javers.core.diff.changetype.PropertyChange; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.slf4j.Logger; @@ -42,11 +40,9 @@ public class IdentityProviderNormalizationService { private static final Logger logger = LoggerFactory.getLogger(IdentityProviderNormalizationService.class); private final Javers unOrderedJavers; - private final JaversUtil javersUtil; - public IdentityProviderNormalizationService(Javers unOrderedJavers, JaversUtil javersUtil) { + public IdentityProviderNormalizationService(Javers unOrderedJavers) { this.unOrderedJavers = unOrderedJavers; - this.javersUtil = javersUtil; } public List normalizeProviders(List exportedProviders, @@ -71,17 +67,16 @@ public List normalizeProviders(List normalizeMappers(List normalizeProtocolMappers(List exportedOrEmpty = exportedMappers == null ? List.of() : exportedMappers; List baselineOrEmpty = baselineMappers == null ? List.of() : baselineMappers; - List normalizedMappers = null; - if (unOrderedJavers.compareCollections(baselineOrEmpty, exportedOrEmpty, ProtocolMapperRepresentation.class).hasChanges()) { - /* - * If the mapper lists differ, add all the mappers from the exported list. Otherwise, just return null - */ - normalizedMappers = new ArrayList<>(); - for (var mapper : exportedOrEmpty) { - mapper.setId(null); - normalizedMappers.add(mapper); + var exportedMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(ProtocolMapperRepresentation::getName, Function.identity())); + var baselineMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(ProtocolMapperRepresentation::getName, Function.identity())); + var normalizedMappers = new ArrayList(); + + for (var entry : baselineMap.entrySet()) { + var name = entry.getKey(); + var exportedMapper = exportedMap.remove(name); + if (exportedMapper == null) { + logger.warn("Default realm protocolMapper '{}' was deleted in exported realm. It may be reintroduced during import!", name); + continue; + } + + var baselineMapper = entry.getValue(); + if (unOrderedJavers.compare(baselineMapper, exportedMapper).hasChanges()) { + normalizedMappers.add(exportedMapper); + } + } + normalizedMappers.addAll(exportedMappers); + for (var mapper : normalizedMappers) { + mapper.setId(null); + if (mapper.getConfig().isEmpty()) { + mapper.setConfig(null); } } - return normalizedMappers; + return normalizedMappers.isEmpty() ? null : normalizedMappers; } } From 8e4925b28e1ddc1820cecae578a82494c4759f2a Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Fri, 10 Mar 2023 13:11:45 +0100 Subject: [PATCH 32/49] Add normalization for User Federations --- .../NormalizationConfiguration.java | 6 +- .../ProtocolMapperNormalizationService.java | 2 +- .../normalize/RealmNormalizationService.java | 7 ++ .../UserFederationNormalizationService.java | 119 ++++++++++++++++++ 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 3de29fc23..e6ddfb661 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -39,6 +39,8 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserFederationMapperRepresentation; +import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -121,6 +123,8 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(AuthenticationFlowRepresentation.class, "alias", List.of("id", "authenticationExecutions"))) .registerEntity(new EntityDefinition(IdentityProviderRepresentation.class, "alias", List.of("internalId"))) .registerEntity(new EntityDefinition(IdentityProviderMapperRepresentation.class, "name", List.of("id"))) - .registerEntity(new EntityDefinition(RequiredActionProviderRepresentation.class, "alias")); + .registerEntity(new EntityDefinition(RequiredActionProviderRepresentation.class, "alias")) + .registerEntity(new EntityDefinition(UserFederationProviderRepresentation.class, "displayName", List.of("id"))) + .registerEntity(new EntityDefinition(UserFederationMapperRepresentation.class, "name", List.of("id"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java index 0ba686417..57602af32 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java @@ -68,7 +68,7 @@ public List normalizeProtocolMappers(List normalizeProviders(List exportedProviders, + List baselineProviders) { + List exportedOrEmpty = exportedProviders == null ? List.of() : exportedProviders; + List baselineOrEmpty = baselineProviders == null ? List.of() : baselineProviders; + + var exportedMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(UserFederationProviderRepresentation::getDisplayName, Function.identity())); + var baselineMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(UserFederationProviderRepresentation::getDisplayName, Function.identity())); + + var normalizedProviders = new ArrayList(); + for (var entry : baselineMap.entrySet()) { + var displayName = entry.getKey(); + var exportedProvider = exportedMap.remove(displayName); + + if (exportedProvider == null) { + logger.warn("Default realm UserFederationProvider '{}' was deleted in exported realm. " + + "It may be reintroduced during import!", displayName); + continue; + } + + var baselineProvider = entry.getValue(); + if (unOrderedJavers.compare(baselineProvider, exportedProvider).hasChanges()) { + normalizedProviders.add(exportedProvider); + } + } + normalizedProviders.addAll(exportedMap.values()); + for (var provider : normalizedProviders) { + provider.setId(null); + if (provider.getConfig().isEmpty()) { + provider.setConfig(null); + } + } + return normalizedProviders.isEmpty() ? null : normalizedProviders; + } + + public List normalizeMappers(List exportedMappers, + List baselineMappers) { + List exportedOrEmpty = exportedMappers == null ? List.of() : exportedMappers; + List baselineOrEmpty = baselineMappers == null ? List.of() : baselineMappers; + + var exportedMap = exportedOrEmpty.stream() + .collect(Collectors.toMap(UserFederationMapperRepresentation::getName, Function.identity())); + var baselineMap = baselineOrEmpty.stream() + .collect(Collectors.toMap(UserFederationMapperRepresentation::getName, Function.identity())); + + var normalizedMappers = new ArrayList(); + for (var entry : baselineMap.entrySet()) { + var name = entry.getKey(); + var exportedMapper = exportedMap.remove(name); + if (exportedMapper == null) { + logger.warn("Default realm UserFederationMapper '{}' was deleted in exported realm. " + + "It may be reintroduced during import!", name); + } + + var baselineMapper = entry.getValue(); + if (unOrderedJavers.compare(baselineMapper, exportedMapper).hasChanges()) { + normalizedMappers.add(exportedMapper); + } + } + normalizedMappers.addAll(exportedMap.values()); + for (var mapper : normalizedMappers) { + mapper.setId(null); + if (mapper.getConfig().isEmpty()) { + mapper.setConfig(null); + } + } + return normalizedMappers.isEmpty() ? null : normalizedMappers; + } +} From 92325e5a2b6efe45d769e42c3b5e3285763f9745 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 13 Mar 2023 12:05:37 +0100 Subject: [PATCH 33/49] Fix possible NPEs --- .../service/normalize/ClientScopeNormalizationService.java | 6 +++--- .../normalize/IdentityProviderNormalizationService.java | 2 +- .../normalize/ProtocolMapperNormalizationService.java | 2 +- .../config/service/normalize/RoleNormalizationService.java | 2 +- .../normalize/UserFederationNormalizationService.java | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java index 511c65803..d8130e73f 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java @@ -83,9 +83,9 @@ private static void normalizeList(ArrayList normalize for (var mapper : scope.getProtocolMappers()) { mapper.setId(null); } - } - if (scope.getProtocolMappers().isEmpty()) { - scope.setProtocolMappers(null); + if (scope.getProtocolMappers().isEmpty()) { + scope.setProtocolMappers(null); + } } } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java index c491ba7c2..b6054ac4a 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java @@ -73,7 +73,7 @@ public List normalizeProviders(List normalizeProtocolMappers(List normalizeRoleList(List expor public List normalizeList(List roles) { for (var role : roles) { role.setId(null); - if (role.getAttributes().isEmpty()) { + if (role.getAttributes() != null && role.getAttributes().isEmpty()) { role.setAttributes(null); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java index c34fc70a4..e83157242 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java @@ -76,7 +76,7 @@ public List normalizeProviders(List normalizeMappers(List Date: Mon, 3 Apr 2023 14:54:25 +0200 Subject: [PATCH 34/49] Fix UOE when using partial keycloak export --- .../AttributeNormalizationService.java | 15 ++++++----- .../AuthFlowNormalizationService.java | 2 +- .../normalize/ClientNormalizationService.java | 26 +++++++++---------- .../ClientScopeNormalizationService.java | 10 ++++--- .../normalize/GroupNormalizationService.java | 6 +++-- .../IdentityProviderNormalizationService.java | 10 ++++--- .../ProtocolMapperNormalizationService.java | 6 +++-- .../normalize/RealmNormalizationService.java | 13 ++++++++++ .../RequiredActionNormalizationService.java | 6 +++-- .../normalize/RoleNormalizationService.java | 12 +++++---- .../ScopeMappingNormalizationService.java | 8 +++--- .../UserFederationNormalizationService.java | 10 ++++--- 12 files changed, 76 insertions(+), 48 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java index ef1237126..d8de9e680 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java @@ -24,11 +24,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.*; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class AttributeNormalizationService { @@ -40,8 +43,8 @@ public AttributeNormalizationService(Javers unOrderedJavers) { } public Map normalizeStringAttributes(Map exportedAttributes, Map baselineAttributes) { - Map exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; - Map baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + var exportedOrEmpty = getNonNull(exportedAttributes); + var baselineOrEmpty = getNonNull(baselineAttributes); var normalizedAttributes = new HashMap(); for (var entry : baselineOrEmpty.entrySet()) { var attributeName = entry.getKey(); @@ -58,8 +61,8 @@ public Map normalizeStringAttributes(Map exporte public Map> normalizeListAttributes(Map> exportedAttributes, Map> baselineAttributes) { - Map> exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; - Map> baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + var exportedOrEmpty = getNonNull(exportedAttributes); + var baselineOrEmpty = getNonNull(baselineAttributes); var normalizedAttributes = new HashMap>(); for (var entry : baselineOrEmpty.entrySet()) { var attributeName = entry.getKey(); @@ -75,8 +78,8 @@ public Map> normalizeListAttributes(Map> exportedAttributes, Map> baselineAttributes) { - Map> exportedOrEmpty = exportedAttributes == null ? Map.of() : exportedAttributes; - Map> baselineOrEmpty = baselineAttributes == null ? Map.of() : baselineAttributes; + var exportedOrEmpty = getNonNull(exportedAttributes); + var baselineOrEmpty = getNonNull(baselineAttributes); if (!Objects.equals(exportedOrEmpty.keySet(), baselineOrEmpty.keySet())) { return true; diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index f9c0aecd3..89e5f3ead 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -105,7 +105,7 @@ public List normalizeAuthConfig(List filterBuiltIn(List flows) { if (flows == null) { - return List.of(); + return new ArrayList<>(); } return flows.stream().filter(not(AuthenticationFlowRepresentation::isBuiltIn)).collect(Collectors.toList()); } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index 1132b2255..23d559bdb 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -38,29 +38,28 @@ import java.util.List; import java.util.Map; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class ClientNormalizationService { private static final Logger logger = LoggerFactory.getLogger(ClientNormalizationService.class); - private final Javers javers; + private final Javers unOrderedJavers; private final BaselineProvider baselineProvider; private final JaversUtil javersUtil; - private final ProtocolMapperNormalizationService protocolMapperNormalizationService; - public ClientNormalizationService(Javers javers, + public ClientNormalizationService(Javers unOrderedJavers, BaselineProvider baselineProvider, - JaversUtil javersUtil, - ProtocolMapperNormalizationService protocolMapperNormalizationService) { - this.javers = javers; + JaversUtil javersUtil) { + this.unOrderedJavers = unOrderedJavers; this.baselineProvider = baselineProvider; this.javersUtil = javersUtil; - this.protocolMapperNormalizationService = protocolMapperNormalizationService; } public List normalizeClients(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { - List exportedOrEmpty = exportedRealm.getClients() == null ? List.of() : exportedRealm.getClients(); - List baselineOrEmpty = baselineRealm.getClients() == null ? List.of() : baselineRealm.getClients(); + var exportedOrEmpty = getNonNull(exportedRealm.getClients()); + var baselineOrEmpty = getNonNull(baselineRealm.getClients()); var exportedClientMap = new HashMap(); for (var exportedClient : exportedOrEmpty) { exportedClientMap.put(exportedClient.getClientId(), exportedClient); @@ -99,7 +98,7 @@ public List normalizeClients(RealmRepresentation exportedR public ClientRepresentation normalizeClient(ClientRepresentation client, String keycloakVersion) { var clientId = client.getClientId(); var baselineClient = baselineProvider.getClient(keycloakVersion, clientId); - var diff = javers.compare(baselineClient, client); + var diff = unOrderedJavers.compare(baselineClient, client); var normalizedClient = new ClientRepresentation(); for (var change : diff.getChangesByType(PropertyChange.class)) { javersUtil.applyChange(normalizedClient, change); @@ -117,7 +116,7 @@ public ClientRepresentation normalizeClient(ClientRepresentation client, String } public boolean clientChanged(ClientRepresentation exportedClient, ClientRepresentation baselineClient) { - var diff = javers.compare(baselineClient, exportedClient); + var diff = unOrderedJavers.compare(baselineClient, exportedClient); if (diff.hasChanges()) { return true; } @@ -129,12 +128,11 @@ public boolean clientChanged(ClientRepresentation exportedClient, ClientRepresen public boolean protocolMappersChanged(List exportedMappers, List baselineMappers) { // CompareCollections doesn't handle nulls gracefully - return javers.compareCollections(baselineMappers == null ? List.of() : baselineMappers, - exportedMappers == null ? List.of() : exportedMappers, ProtocolMapperRepresentation.class).hasChanges(); + return unOrderedJavers.compareCollections(getNonNull(baselineMappers), getNonNull(exportedMappers), ProtocolMapperRepresentation.class).hasChanges(); } public boolean authorizationSettingsChanged(ResourceServerRepresentation exportedSettings, ResourceServerRepresentation baselineSettings) { - return javers.compare(baselineSettings, exportedSettings).hasChanges(); + return unOrderedJavers.compare(baselineSettings, exportedSettings).hasChanges(); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java index d8130e73f..3825beee2 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientScopeNormalizationService.java @@ -33,6 +33,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class ClientScopeNormalizationService { @@ -47,8 +49,8 @@ public ClientScopeNormalizationService(Javers unOrderedJavers) { public List normalizeClientScopes(List exportedScopes, List baselineScopes) { - List exportedOrEmpty = exportedScopes == null ? List.of() : exportedScopes; - List baselineOrEmpty = baselineScopes == null ? List.of() : baselineScopes; + var exportedOrEmpty = getNonNull(exportedScopes); + var baselineOrEmpty = getNonNull(baselineScopes); var exportedMap = exportedOrEmpty.stream().collect(Collectors.toMap(ClientScopeRepresentation::getName, Function.identity())); @@ -99,7 +101,7 @@ public boolean clientScopeChanged(ClientScopeRepresentation exportedScope, Clien } public boolean protocolMappersChanged(List exportedMappers, List baselineMappers) { - return unOrderedJavers.compareCollections(baselineMappers == null ? List.of() : baselineMappers, - exportedMappers == null ? List.of() : exportedMappers, ProtocolMapperRepresentation.class).hasChanges(); + return unOrderedJavers.compareCollections(getNonNull(baselineMappers), getNonNull(exportedMappers), + ProtocolMapperRepresentation.class).hasChanges(); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java index 2731b099f..10cf28c6b 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/GroupNormalizationService.java @@ -33,6 +33,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class GroupNormalizationService { @@ -49,8 +51,8 @@ public GroupNormalizationService(Javers unOrderedJavers, } public List normalizeGroups(List exportedGroups, List baselineGroups) { - List exportedOrEmpty = exportedGroups == null ? List.of() : exportedGroups; - List baselineOrEmpty = baselineGroups == null ? List.of() : baselineGroups; + var exportedOrEmpty = getNonNull(exportedGroups); + var baselineOrEmpty = getNonNull(baselineGroups); var exportedGroupsMap = exportedOrEmpty.stream() .collect(Collectors.toMap(GroupRepresentation::getPath, Function.identity())); var baselineGroupsMap = baselineOrEmpty.stream() diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java index b6054ac4a..2fb13a5ad 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java @@ -33,6 +33,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class IdentityProviderNormalizationService { @@ -47,8 +49,8 @@ public IdentityProviderNormalizationService(Javers unOrderedJavers) { public List normalizeProviders(List exportedProviders, List baselineProviders) { - List exportedOrEmpty = exportedProviders == null ? List.of() : exportedProviders; - List baselineOrEmpty = baselineProviders == null ? List.of() : baselineProviders; + var exportedOrEmpty = getNonNull(exportedProviders); + var baselineOrEmpty = getNonNull(baselineProviders); var exportedMap = exportedOrEmpty.stream() .collect(Collectors.toMap(IdentityProviderRepresentation::getAlias, Function.identity())); @@ -82,8 +84,8 @@ public List normalizeProviders(List normalizeMappers(List exportedMappers, List baselineMappers) { - List exportedOrEmpty = exportedMappers == null ? List.of() : exportedMappers; - List baselineOrEmpty = baselineMappers == null ? List.of() : baselineMappers; + var exportedOrEmpty = getNonNull(exportedMappers); + var baselineOrEmpty = getNonNull(baselineMappers); var exportedMap = exportedOrEmpty.stream() .collect(Collectors.toMap(IdentityProviderMapperRepresentation::getName, Function.identity())); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java index 09890934d..1a65f85b6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ProtocolMapperNormalizationService.java @@ -32,6 +32,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class ProtocolMapperNormalizationService { @@ -46,8 +48,8 @@ public ProtocolMapperNormalizationService(Javers unOrderedJavers) { public List normalizeProtocolMappers(List exportedMappers, List baselineMappers) { - List exportedOrEmpty = exportedMappers == null ? List.of() : exportedMappers; - List baselineOrEmpty = baselineMappers == null ? List.of() : baselineMappers; + var exportedOrEmpty = getNonNull(exportedMappers); + var baselineOrEmpty = getNonNull(baselineMappers); var exportedMap = exportedOrEmpty.stream() .collect(Collectors.toMap(ProtocolMapperRepresentation::getName, Function.identity())); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index b591d926e..789022133 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -32,6 +32,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; @Service @@ -187,4 +191,13 @@ private void handleBaseRealm(RealmRepresentation exportedRealm, RealmRepresentat minimizedRealm.setId(exportedRealm.getId()); } } + + + public static Map getNonNull(Map in) { + return in == null ? new HashMap<>() : in; + } + + public static List getNonNull(List in) { + return in == null ? new ArrayList<>() : in; + } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java index c2a9325de..361357d0d 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RequiredActionNormalizationService.java @@ -32,6 +32,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class RequiredActionNormalizationService { @@ -46,8 +48,8 @@ public RequiredActionNormalizationService(Javers javers) { public List normalizeRequiredActions(List exportedActions, List baselineActions) { - List exportedOrEmpty = exportedActions == null ? List.of() : exportedActions; - List baselineOrEmpty = baselineActions == null ? List.of() : baselineActions; + var exportedOrEmpty = getNonNull(exportedActions); + var baselineOrEmpty = getNonNull(baselineActions); var exportedMap = exportedOrEmpty.stream() .collect(Collectors.toMap(RequiredActionProviderRepresentation::getAlias, Function.identity())); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java index 5a6c00e9d..0952ca36d 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RoleNormalizationService.java @@ -36,6 +36,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class RoleNormalizationService { @@ -72,15 +74,15 @@ public List normalizeRealmRoles(List exp public Map> normalizeClientRoles(Map> exportedRoles, Map> baselineRoles) { - Map> exportedOrEmpty = exportedRoles == null ? Map.of() : exportedRoles; - Map> baselineOrEmpty = baselineRoles == null ? Map.of() : baselineRoles; + var exportedOrEmpty = getNonNull(exportedRoles); + var baselineOrEmpty = getNonNull(baselineRoles); var normalizedRoles = new HashMap>(); for (var entry : baselineOrEmpty.entrySet()) { var clientId = entry.getKey(); var baselineClientRoles = entry.getValue(); var exportedClientRoles = exportedOrEmpty.remove(clientId); - exportedClientRoles = exportedClientRoles == null ? List.of() : exportedClientRoles; + exportedClientRoles = getNonNull(exportedClientRoles); var normalizedClientRoles = normalizeRoleList(exportedClientRoles, baselineClientRoles, clientId); if (!normalizedClientRoles.isEmpty()) { @@ -101,8 +103,8 @@ public Map> normalizeClientRoles(Map normalizeRoleList(List exportedRoles, List baselineRoles, String clientId) { - List exportedOrEmpty = exportedRoles == null ? List.of() : exportedRoles; - List baselineOrEmpty = baselineRoles == null ? List.of() : baselineRoles; + var exportedOrEmpty = getNonNull(exportedRoles); + var baselineOrEmpty = getNonNull(baselineRoles); var exportedMap = exportedOrEmpty.stream() .collect(Collectors.toMap(RoleRepresentation::getName, Function.identity())); diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java index 43f587cbe..f713a2351 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ScopeMappingNormalizationService.java @@ -33,6 +33,8 @@ import java.util.List; import java.util.Map; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class ScopeMappingNormalizationService { @@ -83,10 +85,8 @@ public List normalizeScopeMappings(RealmRepresentati public Map> normalizeClientScopeMappings(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) { - Map> baselineOrEmpty = baselineRealm.getClientScopeMappings() == null - ? Map.of() : baselineRealm.getClientScopeMappings(); - Map> exportedOrEmpty = exportedRealm.getClientScopeMappings() == null - ? Map.of() : exportedRealm.getClientScopeMappings(); + var baselineOrEmpty = getNonNull(baselineRealm.getClientScopeMappings()); + var exportedOrEmpty = getNonNull(exportedRealm.getClientScopeMappings()); var mappings = new HashMap>(); for (var e : baselineOrEmpty.entrySet()) { diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java index e83157242..0783c9916 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/UserFederationNormalizationService.java @@ -34,6 +34,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + @Service @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class UserFederationNormalizationService { @@ -49,8 +51,8 @@ public UserFederationNormalizationService(Javers unOrderedJavers) { public List normalizeProviders(List exportedProviders, List baselineProviders) { - List exportedOrEmpty = exportedProviders == null ? List.of() : exportedProviders; - List baselineOrEmpty = baselineProviders == null ? List.of() : baselineProviders; + var exportedOrEmpty = getNonNull(exportedProviders); + var baselineOrEmpty = getNonNull(baselineProviders); var exportedMap = exportedOrEmpty.stream() .collect(Collectors.toMap(UserFederationProviderRepresentation::getDisplayName, Function.identity())); @@ -85,8 +87,8 @@ public List normalizeProviders(List normalizeMappers(List exportedMappers, List baselineMappers) { - List exportedOrEmpty = exportedMappers == null ? List.of() : exportedMappers; - List baselineOrEmpty = baselineMappers == null ? List.of() : baselineMappers; + var exportedOrEmpty = getNonNull(exportedMappers); + var baselineOrEmpty = getNonNull(baselineMappers); var exportedMap = exportedOrEmpty.stream() .collect(Collectors.toMap(UserFederationMapperRepresentation::getName, Function.identity())); From 55710e7e6884aaf992343fa717ef7dd91eeab905 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 3 Apr 2023 17:33:33 +0200 Subject: [PATCH 35/49] Improved client normalization Always include the protocol attribute for clarity. Remove SAML-attributes if the client is OIDC. --- .../normalize/ClientNormalizationService.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index 23d559bdb..b661cdea6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; @@ -44,6 +45,13 @@ @ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") public class ClientNormalizationService { + private static final Set SAML_ATTRIBUTES = Set.of("saml_signature_canonicalization_method", "saml.onetimeuse.condition", + "saml_name_id_format", "saml.authnstatement", "saml.server.signature.keyinfo$xmlSigKeyInfoKeyNameTransformer", + "saml_force_name_id_format", "saml.artifact.binding", "saml.artifact.binding.identifier", "saml.server.signature", "saml.encrypt", + "saml.assertion.signature", "saml.allow.ecp.flow", "saml.signing.private.key", "saml.force.name.id.format", "saml.client.signature", + "saml.signature.algorithm", "saml.signing.certificate", "saml.server.signature.keyinfo.ext", "saml.multivalued.roles", + "saml.force.post.binding"); + private static final Logger logger = LoggerFactory.getLogger(ClientNormalizationService.class); private final Javers unOrderedJavers; private final BaselineProvider baselineProvider; @@ -103,6 +111,9 @@ public ClientRepresentation normalizeClient(ClientRepresentation client, String for (var change : diff.getChangesByType(PropertyChange.class)) { javersUtil.applyChange(normalizedClient, change); } + + // Always include protocol, even if it's the default "openid-connect" + normalizedClient.setProtocol(client.getProtocol()); var mappers = client.getProtocolMappers(); normalizedClient.setProtocolMappers(mappers); if (mappers != null) { @@ -112,6 +123,11 @@ public ClientRepresentation normalizeClient(ClientRepresentation client, String } normalizedClient.setAuthorizationSettings(client.getAuthorizationSettings()); normalizedClient.setClientId(clientId); + + // Older versions of keycloak include SAML attributes even in OIDC clients. Ignore these. + if (normalizedClient.getProtocol().equals("openid-connect")) { + normalizedClient.getAttributes().keySet().removeIf(SAML_ATTRIBUTES::contains); + } return normalizedClient; } From 5a28f89e7fb917cf784ed434b404e42f026844a2 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Mon, 3 Apr 2023 17:39:00 +0200 Subject: [PATCH 36/49] Start work on ComponentNormalization --- .../NormalizationConfiguration.java | 4 +- .../AttributeNormalizationService.java | 1 - .../normalize/ClientNormalizationService.java | 3 +- .../ComponentNormalizationService.java | 91 +++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/ComponentNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index e6ddfb661..3bab5e45f 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -32,6 +32,7 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.keycloak.representations.idm.ComponentExportRepresentation; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; @@ -125,6 +126,7 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(IdentityProviderMapperRepresentation.class, "name", List.of("id"))) .registerEntity(new EntityDefinition(RequiredActionProviderRepresentation.class, "alias")) .registerEntity(new EntityDefinition(UserFederationProviderRepresentation.class, "displayName", List.of("id"))) - .registerEntity(new EntityDefinition(UserFederationMapperRepresentation.class, "name", List.of("id"))); + .registerEntity(new EntityDefinition(UserFederationMapperRepresentation.class, "name", List.of("id"))) + .registerEntity(new EntityDefinition(ComponentExportRepresentation.class, "name", List.of("id", "subComponents", "config"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java index d8de9e680..267faab0d 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AttributeNormalizationService.java @@ -24,7 +24,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index b661cdea6..c408ee1a0 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -144,7 +144,8 @@ public boolean clientChanged(ClientRepresentation exportedClient, ClientRepresen public boolean protocolMappersChanged(List exportedMappers, List baselineMappers) { // CompareCollections doesn't handle nulls gracefully - return unOrderedJavers.compareCollections(getNonNull(baselineMappers), getNonNull(exportedMappers), ProtocolMapperRepresentation.class).hasChanges(); + return unOrderedJavers.compareCollections(getNonNull(baselineMappers), getNonNull(exportedMappers), ProtocolMapperRepresentation.class) + .hasChanges(); } public boolean authorizationSettingsChanged(ResourceServerRepresentation exportedSettings, ResourceServerRepresentation baselineSettings) { diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ComponentNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ComponentNormalizationService.java new file mode 100644 index 000000000..1ce2c3dce --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ComponentNormalizationService.java @@ -0,0 +1,91 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import org.javers.core.Javers; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.representations.idm.ComponentExportRepresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; + +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class ComponentNormalizationService { + + private static final Logger logger = LoggerFactory.getLogger(ComponentNormalizationService.class); + + private final Javers unOrderedJavers; + + public ComponentNormalizationService(Javers unOrderedJavers) { + this.unOrderedJavers = unOrderedJavers; + } + + public MultivaluedHashMap + normalizeComponents(MultivaluedHashMap exportedComponents, + MultivaluedHashMap baselineComponents) { + var exportedOrEmpty = getNonNull(exportedComponents); + var baselineOrEmpty = getNonNull(baselineComponents); + + var normalizedMap = new MultivaluedHashMap(); + for (var entry : baselineOrEmpty.entrySet()) { + var componentClass = entry.getKey(); + + var exportedList = exportedOrEmpty.remove(componentClass); + + if (exportedList == null) { + logger.warn("Default realm component '{}' was deleted in exported realm. It may be reintroduced during import!", componentClass); + continue; + } + var baselineList = entry.getValue(); + var normalizedList = normalizeList(exportedList, baselineList, componentClass); + normalizedMap.put(componentClass, normalizedList); + } + normalizedMap.putAll(exportedOrEmpty); + var toRemove = new HashSet(); + for (var entry : normalizedMap.entrySet()) { + var componentList = entry.getValue(); + for (var component : componentList) { + normalizeEntry(component); + } + } + return new MultivaluedHashMap<>(); + } + + public List normalizeList(List exportedComponents, + List baselineComponents, + String componentClass) { + return List.of(); + } + + public void normalizeEntry(ComponentExportRepresentation component) { + component.setId(null); + if (component.getConfig() != null && component.getConfig().isEmpty()) { + component.setConfig(null); + } + } +} From e40e3fb0909bf61488901b684cfcdc4b790c9b0b Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 4 Apr 2023 08:53:04 +0200 Subject: [PATCH 37/49] Fix NPE when client attributes are not set --- .../config/service/normalize/ClientNormalizationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index c408ee1a0..47b0ee2c6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -125,7 +125,7 @@ public ClientRepresentation normalizeClient(ClientRepresentation client, String normalizedClient.setClientId(clientId); // Older versions of keycloak include SAML attributes even in OIDC clients. Ignore these. - if (normalizedClient.getProtocol().equals("openid-connect")) { + if (normalizedClient.getProtocol().equals("openid-connect") && normalizedClient.getAttributes() != null) { normalizedClient.getAttributes().keySet().removeIf(SAML_ATTRIBUTES::contains); } return normalizedClient; From eb166603bba55da6abc39c895caa98050d71cc7b Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Tue, 4 Apr 2023 10:17:06 +0200 Subject: [PATCH 38/49] Fix handling of invalid executions --- .../normalize/AuthFlowNormalizationService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index 89e5f3ead..c6a3c8990 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -81,6 +81,17 @@ public List normalizeAuthFlows(List Date: Tue, 4 Apr 2023 10:40:39 +0200 Subject: [PATCH 39/49] Remove technical IDs from authenticatorconfig --- .../service/normalize/AuthFlowNormalizationService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index c6a3c8990..9b264a6fb 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -39,6 +39,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; import static java.util.function.Predicate.not; @Service @@ -96,7 +97,7 @@ public List normalizeAuthFlows(List normalizeAuthConfig(List config, + public List normalizeAuthConfig(List configs, List flows) { List flowsOrEmpty = flows == null ? List.of() : flows; // Find out which configs are actually used by the normalized flows @@ -107,10 +108,14 @@ public List normalizeAuthConfig(List configOrEmpty = config == null ? List.of() : config; + var configOrEmpty = getNonNull(configs); // Only return configs that are used var filteredConfigs = configOrEmpty.stream() .filter(acr -> usedConfigs.contains(acr.getAlias())).collect(Collectors.toList()); + + for (var config : filteredConfigs) { + config.setId(null); + } return filteredConfigs.isEmpty() ? null : filteredConfigs; } From e8e889a1b1f7ea5a8919be2420b52db23a016aa1 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 5 Apr 2023 15:47:31 +0200 Subject: [PATCH 40/49] Map client authflow bindings to aliases --- .../normalize/ClientNormalizationService.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index 47b0ee2c6..828122de6 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -24,6 +24,7 @@ import de.adorsys.keycloak.config.util.JaversUtil; import org.javers.core.Javers; import org.javers.core.diff.changetype.PropertyChange; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -38,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; @@ -90,20 +92,20 @@ public List normalizeClients(RealmRepresentation exportedR } if (clientChanged(exportedClient, baselineRealmClient)) { // We know the client has changed in some way. Now, compare it to a default client to minimize it - clients.add(normalizeClient(exportedClient, exportedRealm.getKeycloakVersion())); + clients.add(normalizeClient(exportedClient, exportedRealm.getKeycloakVersion(), exportedRealm)); } } // Now iterate over all the clients that are *not* default clients for (Map.Entry e : exportedClientMap.entrySet()) { if (!baselineClientMap.containsKey(e.getKey())) { - clients.add(normalizeClient(e.getValue(), exportedRealm.getKeycloakVersion())); + clients.add(normalizeClient(e.getValue(), exportedRealm.getKeycloakVersion(), exportedRealm)); } } return clients; } - public ClientRepresentation normalizeClient(ClientRepresentation client, String keycloakVersion) { + public ClientRepresentation normalizeClient(ClientRepresentation client, String keycloakVersion, RealmRepresentation exportedRealm) { var clientId = client.getClientId(); var baselineClient = baselineProvider.getClient(keycloakVersion, clientId); var diff = unOrderedJavers.compare(baselineClient, client); @@ -128,6 +130,17 @@ public ClientRepresentation normalizeClient(ClientRepresentation client, String if (normalizedClient.getProtocol().equals("openid-connect") && normalizedClient.getAttributes() != null) { normalizedClient.getAttributes().keySet().removeIf(SAML_ATTRIBUTES::contains); } + + if (normalizedClient.getAuthenticationFlowBindingOverrides() != null) { + var overrides = new HashMap(); + var flows = exportedRealm.getAuthenticationFlows().stream() + .collect(Collectors.toMap(AuthenticationFlowRepresentation::getId, AuthenticationFlowRepresentation::getAlias)); + for (var entry : normalizedClient.getAuthenticationFlowBindingOverrides().entrySet()) { + var id = entry.getValue(); + overrides.put(entry.getKey(), flows.get(id)); + } + normalizedClient.setAuthenticationFlowBindingOverrides(overrides); + } return normalizedClient; } From 53a7e036acaf537c04984bbdd0057ea6268bc21c Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 5 Apr 2023 16:31:18 +0200 Subject: [PATCH 41/49] Tentative fix for unused authentication flows --- .../AuthFlowNormalizationService.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index 9b264a6fb..3d4e56baa 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -37,6 +37,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; @@ -94,7 +95,7 @@ public List normalizeAuthFlows(List normalizeAuthConfig(List configs, @@ -126,6 +127,36 @@ private List filterBuiltIn(List filterUnusedNonTopLevel(List flows) { + if (flows == null) { + return new ArrayList<>(); + } + + // Assume all top level flows are used + var usedFlows = flows.stream().filter(AuthenticationFlowRepresentation::isTopLevel).collect(Collectors.toList()); + var toCheck = flows.stream().filter(Predicate.not(AuthenticationFlowRepresentation::isTopLevel)).collect(Collectors.toMap(AuthenticationFlowRepresentation::getAlias, Function.identity())); + var removedEntry = false; + do { + removedEntry = false; + var toRemove = new ArrayList(); + for (var flow : usedFlows) { + for (var execution : flow.getAuthenticationExecutions()) { + var alias = execution.getFlowAlias(); + if (alias != null) { + if (toCheck.containsKey(alias)) { + toRemove.add(alias); + removedEntry = true; + } + } + } + } + for (var alias : toRemove) { + usedFlows.add(toCheck.remove(alias)); + } + } while (removedEntry); + return usedFlows; + } + public boolean executionsChanged(List exportedExecutions, List baselineExecutions) { if (exportedExecutions == null && baselineExecutions != null) { From 38c5f2374e17fda2a1e23f235f89011c87779143 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 5 Apr 2023 16:34:24 +0200 Subject: [PATCH 42/49] Fix checkstyle line length error --- .../config/service/normalize/AuthFlowNormalizationService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index 3d4e56baa..75b3fcc8d 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -134,7 +134,8 @@ private List filterUnusedNonTopLevel(List Date: Wed, 19 Apr 2023 14:31:04 +0200 Subject: [PATCH 43/49] Documentation for detection of broken configs --- docs/FEATURES.md | 2 +- docs/NORMALIZE.md | 117 ++++++++++++++++++ .../config/provider/BaselineProvider.java | 2 +- .../AuthFlowNormalizationService.java | 72 +++++++---- 4 files changed, 169 insertions(+), 24 deletions(-) diff --git a/docs/FEATURES.md b/docs/FEATURES.md index ede3aa835..7e1d30284 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -55,7 +55,7 @@ | Remove clientScopeMappings | 2.5.0 | Remove existing clientScopeMappings while creating or updating realms | | Synchronize user federation | 3.5.0 | Synchronize the user federation defined on the realm configuration | | Synchronize user profile | 5.4.0 | Synchronize the user profile configuration defined on the realm configuration | -| Normalize realm exports | 5.5.0 | Normalize a full realm export to be more minimal | +| Normalize realm exports | x.x.x | Normalize a full realm export to be more minimal | # Specificities diff --git a/docs/NORMALIZE.md b/docs/NORMALIZE.md index b34aa925e..0df106179 100644 --- a/docs/NORMALIZE.md +++ b/docs/NORMALIZE.md @@ -1,6 +1,123 @@ # Realm normalization +Realm normalization is a feature that is supposed to aid users in migrating from an "unmanaged" Keycloak installation, +to an installation managed by keycloak-config-cli. +To achieve this, it uses a full [realm export](https://www.keycloak.org/server/importExport#_exporting_a_specific_realm) +as an input, and only retains things that deviate from the default +## Usage To run the normalization, run keycloak-config-cli with the CLI option `--run.operation=NORMALIZE`. The default value for this option is `IMPORT`, which will run the regular keycloak-config-cli import. + +### Configuration options + +| Configuration key | Purpose | Example | +|--------------------------------------|-----------------------------------------------------------------------------------------------------------------|---------------| +| run.operation | Tell keycloak-config-cli to normalize, rather than import | NORMALIZE | +| normalization.files.input-locations | Which realm files to import | See IMPORT.md | +| normalization.files.output-directory | Where to save output realm files | ./exports/out | +| normalization.output-format | Whether to output JSON or YAML. Default value is YAML | YAML | +| normalization.fallback-version | Use this version as a baseline of keycloak version in realm is not available as baseline in keycloak-config-cli | 19.0.3 | + +### Unimplemented Features +- Components: + - Currently, keycloak-config-cli will not yet look at the `components` section of the exported JSON + - Therefore, some things (like LDAP federation configs and Key providers) are missing from the normalized YAML +- Users + - Users are not currently considered by normalization. + +## Missing entries +keycloak-config-cli will WARN if components that are present in a realm by default are missing from an exported realm. +An example of such a message is: +``` +Default realm requiredAction 'webauthn-register-passwordless' was deleted in exported realm. It may be reintroduced during import +``` +Messages like these will often show up when using keycloak-config-cli to normalize an import from an older version of Keycloak, and compared to a newer baseline. +In the above case, the Keycloak version is 18.0.3, and the baseline for comparison was 19.0.3. +Since the webauthn feature was not present (or enabled by default) in the older version, this message is mostly informative. +If a message like this appears on a component that was *not* deleted or should generally be present, this may indicate a bug in keycloak-config-cli. + +## Cleaning of invalid data +Sometimes, realms of existing installations may contain invalid data, due to faulty migrations, or due to direct interaction with the database, +rather than the Keycloak API. +When such problems are found, we attempt to handle them in keycloak-config-cli, or at least notify the user about the existence of these problems. + +### SAML Attributes on clients +While not necessarily invalid, openid-connect clients that were created on older Keycloak versions, will sometimes contain +SAML-related attributes. These are filtered out by keycloak-config-cli. + +### Unused non-top-level Authentication Flows +Authentication flows in Keycloak are marked as top-level if they are supposed to be available for binding or overrides. +Authentication flows that are not marked as top-level are used as sub-flows in other authentication flows. +The normalization process recognizes recursively whether there are authentication flows that are not top level and not used +by any top level flow, and does not include them in the final result. + +A warning message is logged, and you can use the following SQL query to find any authentication flows that are not referenced. +Note that this query, unlike keycloak-config-cli, is not recursive. +That means that after deleting an unused flow, additional unused flows may appear after the query is performed again. + +```sql +select flow.alias +from authentication_flow flow + join realm r on flow.realm_id = r.id + left join authentication_execution execution on flow.id = execution.auth_flow_id +where r.name = 'mytest' + and execution.id is null + and not flow.top_level +``` + +### Unused and duplicate Authenticator Configs +Authenticator Configs are not useful if they are not referenced by at least one authentication execution. +Therefore, keycloak-config-cli detects unused configurations and does not include them in the resulting output. +Note that the check for unused configs runs *after* the check for unused flows. +That means a config will be detected as unused if it is referenced by an execution that is part of a flow that is unused. + +A warning message is logged on duplicate or unused configs, and you can use the following SQL query to find any configs +that are unused: + +```sql +select ac.alias, ac.id +from authenticator_config ac + left join authentication_execution ae on ac.id = ae.auth_config + left join authentication_flow af on ae.flow_id = af.id + join realm r on ac.realm_id = r.id +where r.name = 'master' and af.alias is null +order by ac.alias +``` + +And the following query to find duplicates: + +```sql +select alias, count(alias), r.name as realm_name +from authenticator_config + join realm r on realm_id = r.id +group by alias, r.name +having count(alias) > 1 +``` + +If the `af.id` and `af.alias` fields are `null`, the config in question is not in use. +Note that configs used by unused flows are not marked as unused in the SQL result, as these need to be deleted first +to become unused. +After the unused flows (and executions) are deleted, the configs will be marked as unused and can also be deleted. + +### Authentication Executions with invalid subflows +Some keycloak exports have invalid authentication executions that reference a subflow, while also setting an authenticator. +This is only a valid configuration if the subflow's type is `form-flow`. +If it is not, then keycloak-config-cli will not import the configuration. +This will be marked by an ERROR severity message in the log output. +You can use this SQL query to find offending entries and remediate the configuration errors before continuing. + +```sql +select parent.alias, + subflow.alias, + execution.alias +from authentication_execution execution + join realm r on execution.realm_id = r.id + join authentication_flow parent on execution.flow_id = parent.id + join authentication_flow subflow on execution.auth_flow_id = subflow.id +where execution.auth_flow_id is not null + and execution.authenticator is not null + and subflow.provider_id <> 'form-flow' + and r.name = 'REALMNAME'; +``` diff --git a/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java index f9493f4e2..f2722a904 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/BaselineProvider.java @@ -96,7 +96,7 @@ public InputStream getClientInputStream(String version) { var inputStream = getClass().getResourceAsStream(String.format("/baseline/%s/client/client.json", version)); if (inputStream == null) { if (fallbackVersion != null) { - logger.warn("Reference client not found for version {}. Using fallback version {}!", version, fallbackVersion); + logger.debug("Reference client not found for version {}. Using fallback version {}!", version, fallbackVersion); inputStream = getClass().getResourceAsStream(String.format("/baseline/%s/client/client.json", fallbackVersion)); if (inputStream == null) { throw new NormalizationException(String.format("Reference client for version %s does not exist, " diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index 75b3fcc8d..5e8eb71ae 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -83,24 +84,36 @@ public List normalizeAuthFlows(List flows) { + var flowsByAlias = flows.stream().collect(Collectors.toMap(AuthenticationFlowRepresentation::getAlias, Function.identity())); + for (var flow : flows) { for (var execution : flow.getAuthenticationExecutions()) { - if (execution.getFlowAlias() != null) { - execution.setAuthenticator(null); + var flowAlias = execution.getFlowAlias(); + var authenticator = execution.getAuthenticator(); + + if (flowAlias != null && authenticator != null) { + var referencedFlow = flowsByAlias.get(flowAlias); + if (!"form-flow".equals(referencedFlow.getProviderId())) { + logger.error("An execution of flow '{}' defines an authenticator and references the sub-flow '{}'." + + " This is only possible if the sub-flow is of type 'form-flow', but it is of type '{}'." + + " keycloak-config-cli will refuse to import this flow. See NORMALIZE.md for more information.", + flow.getAlias(), flowAlias, referencedFlow.getProviderId()); + } } } } - return normalizedFlows.isEmpty() ? null : filterUnusedNonTopLevel(normalizedFlows); + } public List normalizeAuthConfig(List configs, List flows) { - List flowsOrEmpty = flows == null ? List.of() : flows; + var flowsOrEmpty = getNonNull(flows); // Find out which configs are actually used by the normalized flows var usedConfigs = flowsOrEmpty.stream() .map(AuthenticationFlowRepresentation::getAuthenticationExecutions) @@ -114,8 +127,24 @@ public List normalizeAuthConfig(List usedConfigs.contains(acr.getAlias())).collect(Collectors.toList()); + var duplicates = new HashSet(); + var seen = new HashSet(); for (var config : filteredConfigs) { config.setId(null); + if (seen.contains(config.getAlias())) { + duplicates.add(config.getAlias()); + } else { + seen.add(config.getAlias()); + } + } + + if (!duplicates.isEmpty()) { + logger.warn("The following authenticator configs are duplicates: {}. " + + "Check NORMALIZE.md for an SQL query to find the offending entries in your database!", duplicates); + } + + if (configs.size() != filteredConfigs.size()) { + logger.warn("Some authenticator configs are unused. Check NORMALIZE.md for an SQL query to find the offending entries in your database!"); } return filteredConfigs.isEmpty() ? null : filteredConfigs; } @@ -128,33 +157,32 @@ private List filterBuiltIn(List filterUnusedNonTopLevel(List flows) { - if (flows == null) { - return new ArrayList<>(); - } - // Assume all top level flows are used var usedFlows = flows.stream().filter(AuthenticationFlowRepresentation::isTopLevel).collect(Collectors.toList()); - var toCheck = flows.stream().filter(Predicate.not(AuthenticationFlowRepresentation::isTopLevel)) + var unchecked = flows.stream().filter(Predicate.not(AuthenticationFlowRepresentation::isTopLevel)) .collect(Collectors.toMap(AuthenticationFlowRepresentation::getAlias, Function.identity())); - var removedEntry = false; - do { - removedEntry = false; + var toCheck = new ArrayList<>(usedFlows); + while (!toCheck.isEmpty()) { var toRemove = new ArrayList(); - for (var flow : usedFlows) { + for (var flow : toCheck) { for (var execution : flow.getAuthenticationExecutions()) { var alias = execution.getFlowAlias(); if (alias != null) { - if (toCheck.containsKey(alias)) { + if (unchecked.containsKey(alias)) { toRemove.add(alias); - removedEntry = true; } } } } + toCheck.clear(); for (var alias : toRemove) { - usedFlows.add(toCheck.remove(alias)); + toCheck.add(unchecked.remove(alias)); } - } while (removedEntry); + usedFlows.addAll(toCheck); + } + if (usedFlows.size() != flows.size()) { + logger.warn("Some authentication flows are unused. Check NORMALIZE.md for an SQL query to find the offending entries in your database!"); + } return usedFlows; } From debe1f042e9e964af2735f1657a5bef2b1337f55 Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 19 Apr 2023 15:39:51 +0200 Subject: [PATCH 44/49] Fix PMD warnings --- .../service/normalize/AuthFlowNormalizationService.java | 9 +++------ .../service/normalize/ComponentNormalizationService.java | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index 5e8eb71ae..40ba3b676 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -38,7 +38,6 @@ import java.util.Map; import java.util.Objects; import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Collectors; import static de.adorsys.keycloak.config.service.normalize.RealmNormalizationService.getNonNull; @@ -159,7 +158,7 @@ private List filterBuiltIn(List filterUnusedNonTopLevel(List flows) { // Assume all top level flows are used var usedFlows = flows.stream().filter(AuthenticationFlowRepresentation::isTopLevel).collect(Collectors.toList()); - var unchecked = flows.stream().filter(Predicate.not(AuthenticationFlowRepresentation::isTopLevel)) + var unchecked = flows.stream().filter(not(AuthenticationFlowRepresentation::isTopLevel)) .collect(Collectors.toMap(AuthenticationFlowRepresentation::getAlias, Function.identity())); var toCheck = new ArrayList<>(usedFlows); while (!toCheck.isEmpty()) { @@ -167,10 +166,8 @@ private List filterUnusedNonTopLevel(List(); + //var toRemove = new HashSet(); for (var entry : normalizedMap.entrySet()) { var componentList = entry.getValue(); for (var component : componentList) { From 5572c1e21c51fbf128bad6f5b0c52bd331554d4a Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Thu, 20 Apr 2023 13:51:54 +0200 Subject: [PATCH 45/49] Fix errors with duplicate mappers --- .../NormalizationConfiguration.java | 9 +++- .../IdentityProviderNormalizationService.java | 43 +++++++++++++++--- .../UserFederationNormalizationService.java | 45 ++++++++++++++++--- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java index 3bab5e45f..21a43888a 100644 --- a/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java +++ b/src/main/java/de/adorsys/keycloak/config/configuration/NormalizationConfiguration.java @@ -29,6 +29,7 @@ import org.javers.core.JaversBuilder; import org.javers.core.diff.ListCompareAlgorithm; import org.javers.core.metamodel.clazz.EntityDefinition; +import org.javers.core.metamodel.clazz.EntityDefinitionBuilder; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; @@ -123,10 +124,14 @@ private JaversBuilder commonJavers() { .registerEntity(new EntityDefinition(GroupRepresentation.class, "path", List.of("id", "subGroups", "attributes", "clientRoles"))) .registerEntity(new EntityDefinition(AuthenticationFlowRepresentation.class, "alias", List.of("id", "authenticationExecutions"))) .registerEntity(new EntityDefinition(IdentityProviderRepresentation.class, "alias", List.of("internalId"))) - .registerEntity(new EntityDefinition(IdentityProviderMapperRepresentation.class, "name", List.of("id"))) + .registerEntity(EntityDefinitionBuilder.entityDefinition(IdentityProviderMapperRepresentation.class) + .withIdPropertyNames("name", "identityProviderAlias") + .withIgnoredProperties("id").build()) .registerEntity(new EntityDefinition(RequiredActionProviderRepresentation.class, "alias")) .registerEntity(new EntityDefinition(UserFederationProviderRepresentation.class, "displayName", List.of("id"))) - .registerEntity(new EntityDefinition(UserFederationMapperRepresentation.class, "name", List.of("id"))) + .registerEntity(EntityDefinitionBuilder.entityDefinition(UserFederationMapperRepresentation.class) + .withIdPropertyNames("name", "federationProviderDisplayName") + .withIgnoredProperties("id").build()) .registerEntity(new EntityDefinition(ComponentExportRepresentation.class, "name", List.of("id", "subComponents", "config"))); } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java index 2fb13a5ad..a5d87cdcf 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/IdentityProviderNormalizationService.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @@ -88,16 +89,17 @@ public List normalizeMappers(List new MapperKey(m.getName(), m.getIdentityProviderAlias()), Function.identity())); var baselineMap = baselineOrEmpty.stream() - .collect(Collectors.toMap(IdentityProviderMapperRepresentation::getName, Function.identity())); + .collect(Collectors.toMap(m -> new MapperKey(m.getName(), m.getIdentityProviderAlias()), Function.identity())); var normalizedMappers = new ArrayList(); for (var entry : baselineMap.entrySet()) { - var name = entry.getKey(); - var exportedMapper = exportedMap.remove(name); + var key = entry.getKey(); + var exportedMapper = exportedMap.remove(key); if (exportedMapper == null) { - logger.warn("Default realm identityProviderMapper '{}' was deleted in exported realm. It may be reintroduced during import!", name); + logger.warn("Default realm identityProviderMapper '{}' for idp '{}' was deleted in exported realm." + + "It may be reintroduced during import!", key.getName(), key.getIdentityProviderAlias()); continue; } var baselineMapper = entry.getValue(); @@ -113,4 +115,35 @@ public List normalizeMappers(List normalizeMappers(List new MapperKey(m.getName(), m.getFederationProviderDisplayName()), Function.identity())); var baselineMap = baselineOrEmpty.stream() - .collect(Collectors.toMap(UserFederationMapperRepresentation::getName, Function.identity())); + .collect(Collectors.toMap(m -> new MapperKey(m.getName(), m.getFederationProviderDisplayName()), Function.identity())); var normalizedMappers = new ArrayList(); for (var entry : baselineMap.entrySet()) { - var name = entry.getKey(); - var exportedMapper = exportedMap.remove(name); + var key = entry.getKey(); + var exportedMapper = exportedMap.remove(key); if (exportedMapper == null) { - logger.warn("Default realm UserFederationMapper '{}' was deleted in exported realm. " - + "It may be reintroduced during import!", name); + logger.warn("Default realm UserFederationMapper '{}' for federation '{}' was deleted in exported realm. " + + "It may be reintroduced during import!", key.getName(), key.getFederationDisplayName()); } var baselineMapper = entry.getValue(); @@ -118,4 +119,36 @@ public List normalizeMappers(List Date: Thu, 20 Apr 2023 14:31:13 +0200 Subject: [PATCH 46/49] Log names of unused flows --- .../service/normalize/AuthFlowNormalizationService.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java index 40ba3b676..6f00051a4 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationService.java @@ -158,7 +158,7 @@ private List filterBuiltIn(List filterUnusedNonTopLevel(List flows) { // Assume all top level flows are used var usedFlows = flows.stream().filter(AuthenticationFlowRepresentation::isTopLevel).collect(Collectors.toList()); - var unchecked = flows.stream().filter(not(AuthenticationFlowRepresentation::isTopLevel)) + var potentialUnused = flows.stream().filter(not(AuthenticationFlowRepresentation::isTopLevel)) .collect(Collectors.toMap(AuthenticationFlowRepresentation::getAlias, Function.identity())); var toCheck = new ArrayList<>(usedFlows); while (!toCheck.isEmpty()) { @@ -166,19 +166,20 @@ private List filterUnusedNonTopLevel(List Date: Thu, 20 Apr 2023 15:26:49 +0200 Subject: [PATCH 47/49] Support clientpolicy and profile --- .../ClientPolicyNormalizationService.java | 49 +++++++++++++++++++ .../normalize/RealmNormalizationService.java | 8 +++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/de/adorsys/keycloak/config/service/normalize/ClientPolicyNormalizationService.java diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientPolicyNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientPolicyNormalizationService.java new file mode 100644 index 000000000..823e13257 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientPolicyNormalizationService.java @@ -0,0 +1,49 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2023 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package de.adorsys.keycloak.config.service.normalize; + +import org.keycloak.representations.idm.ClientPoliciesRepresentation; +import org.keycloak.representations.idm.ClientProfilesRepresentation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +@Service +@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE") +public class ClientPolicyNormalizationService { + + public ClientPoliciesRepresentation normalizePolicies(ClientPoliciesRepresentation exportedPolicies, + ClientPoliciesRepresentation baselinePolicies) { + var policies = exportedPolicies.getPolicies(); + if (policies == null || policies.isEmpty()) { + return null; + } + return exportedPolicies; + } + + public ClientProfilesRepresentation normalizeProfiles(ClientProfilesRepresentation exportedProfiles, + ClientProfilesRepresentation baselineProfiles) { + var profiles = exportedProfiles.getProfiles(); + if (profiles == null || profiles.isEmpty()) { + return null; + } + return exportedProfiles; + } +} diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java index 789022133..cb9e1b16b 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/RealmNormalizationService.java @@ -58,6 +58,7 @@ public class RealmNormalizationService { private final IdentityProviderNormalizationService identityProviderNormalizationService; private final RequiredActionNormalizationService requiredActionNormalizationService; private final UserFederationNormalizationService userFederationNormalizationService; + private final ClientPolicyNormalizationService clientPolicyNormalizationService; private final JaversUtil javersUtil; @Autowired @@ -75,6 +76,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC IdentityProviderNormalizationService identityProviderNormalizationService, RequiredActionNormalizationService requiredActionNormalizationService, UserFederationNormalizationService userFederationNormalizationService, + ClientPolicyNormalizationService clientPolicyNormalizationService, JaversUtil javersUtil) { this.keycloakConfigProperties = keycloakConfigProperties; this.javers = javers; @@ -90,6 +92,7 @@ public RealmNormalizationService(NormalizationKeycloakConfigProperties keycloakC this.identityProviderNormalizationService = identityProviderNormalizationService; this.requiredActionNormalizationService = requiredActionNormalizationService; this.userFederationNormalizationService = userFederationNormalizationService; + this.clientPolicyNormalizationService = clientPolicyNormalizationService; this.javersUtil = javersUtil; // TODO allow extra "default" values to be ignored? @@ -171,6 +174,11 @@ public RealmRepresentation normalizeRealm(RealmRepresentation exportedRealm) { baselineRealm.getUserFederationProviders())); minimizedRealm.setUserFederationMappers(userFederationNormalizationService.normalizeMappers(exportedRealm.getUserFederationMappers(), baselineRealm.getUserFederationMappers())); + + minimizedRealm.setParsedClientPolicies(clientPolicyNormalizationService.normalizePolicies(exportedRealm.getParsedClientPolicies(), + baselineRealm.getParsedClientPolicies())); + minimizedRealm.setParsedClientProfiles(clientPolicyNormalizationService.normalizeProfiles(exportedRealm.getParsedClientProfiles(), + baselineRealm.getParsedClientProfiles())); return minimizedRealm; } From 87e968e7acdd2220f6a21e9e42a11dc9efcda9fb Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Fri, 26 May 2023 12:43:14 +0200 Subject: [PATCH 48/49] Always set publicClient attribute --- .../config/service/normalize/ClientNormalizationService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java index 828122de6..e36b40063 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/normalize/ClientNormalizationService.java @@ -141,6 +141,7 @@ public ClientRepresentation normalizeClient(ClientRepresentation client, String } normalizedClient.setAuthenticationFlowBindingOverrides(overrides); } + normalizedClient.setPublicClient(client.isPublicClient()); return normalizedClient; } From fe9de52fec56058734792c060257cec6cbca19b9 Mon Sep 17 00:00:00 2001 From: Sebastian Rose Date: Mon, 5 Jun 2023 16:31:10 +0200 Subject: [PATCH 49/49] Added some test for authFlows --- pom.xml | 2 +- .../service/ExecutionFlowsImportService.java | 8 -- .../normalize/AbstractNormalizeTest.java | 2 + .../AuthFlowNormalizationServiceConfigIT.java | 81 +++++++++++++++++++ .../AuthFlowNormalizationServiceFlowIT.java | 53 ++++++++++++ 5 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceConfigIT.java create mode 100644 src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceFlowIT.java diff --git a/pom.xml b/pom.xml index e129d6246..050d835de 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 - 21.0.1 + 18.0.2 3.2.0 10.0 diff --git a/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java index 26ba23a3a..6184f99f7 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ExecutionFlowsImportService.java @@ -21,7 +21,6 @@ package de.adorsys.keycloak.config.service; import de.adorsys.keycloak.config.exception.ImportProcessingException; -import de.adorsys.keycloak.config.exception.InvalidImportException; import de.adorsys.keycloak.config.model.RealmImport; import de.adorsys.keycloak.config.repository.AuthenticatorConfigRepository; import de.adorsys.keycloak.config.repository.ExecutionFlowRepository; @@ -148,13 +147,6 @@ private void createSubFlowByExecutionFlow( executionToImport.getFlowAlias(), realmImport.getRealm() ); - if (!Objects.equals(executionToImport.getAuthenticator(), null) && !Objects.equals(subFlow.getProviderId(), "form-flow")) { - throw new InvalidImportException(String.format( - "Execution property authenticator '%s' can be only set if the sub-flow '%s' type is 'form-flow'.", - executionToImport.getAuthenticator(), subFlow.getAlias() - )); - } - HashMap executionFlow = new HashMap<>(); executionFlow.put("alias", executionToImport.getFlowAlias()); executionFlow.put("provider", executionToImport.getAuthenticator()); diff --git a/src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java b/src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java index 5a92dbe8d..be0412dee 100644 --- a/src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java +++ b/src/test/java/de/adorsys/keycloak/config/normalize/AbstractNormalizeTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -33,6 +34,7 @@ @ExtendWith(SpringExtension.class) @ExtendWith(GithubActionsExtension.class) +@ExtendWith(OutputCaptureExtension.class) @ContextConfiguration( classes = {NormalizeTestConfiguration.class}, initializers = {ConfigDataApplicationContextInitializer.class} diff --git a/src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceConfigIT.java b/src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceConfigIT.java new file mode 100644 index 000000000..d9a2ef250 --- /dev/null +++ b/src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceConfigIT.java @@ -0,0 +1,81 @@ +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.normalize.AbstractNormalizeTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.system.CapturedOutput; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +class AuthFlowNormalizationServiceConfigIT extends AbstractNormalizeTest { + + @Autowired + AuthFlowNormalizationService service; + + @Test + public void testNormalizeAuthConfigsWithEmptyListsIsNull() { + var resultingAuthConfig = service.normalizeAuthConfig(new ArrayList<>(), new ArrayList<>()); + Assertions.assertThat(resultingAuthConfig).isNull(); + } + + @Test + public void testNormalizeAuthConfigsAreRemovedWithoutAliasReference(CapturedOutput output) { + AuthenticatorConfigRepresentation authenticatorConfigRepresentation = new AuthenticatorConfigRepresentation(); + authenticatorConfigRepresentation.setAlias("config2"); + + AuthenticationFlowRepresentation authenticationFlowRepresentation = new AuthenticationFlowRepresentation(); + + AuthenticationExecutionExportRepresentation authenticationExecutionExportRepresentation = new AuthenticationExecutionExportRepresentation(); + authenticationExecutionExportRepresentation.setAuthenticatorConfig("config1"); + authenticationFlowRepresentation.setAuthenticationExecutions(List.of(authenticationExecutionExportRepresentation)); + + var resultingAuthConfig = service.normalizeAuthConfig(List.of(authenticatorConfigRepresentation), List.of(authenticationFlowRepresentation)); + + Assertions.assertThat(resultingAuthConfig).isNull(); + Assertions.assertThat(output.getOut()).contains("Some authenticator configs are unused."); + } + + @Test + public void testNormalizeAuthConfigsRemainWithAliasReference() { + AuthenticatorConfigRepresentation authenticatorConfigRepresentation = new AuthenticatorConfigRepresentation(); + authenticatorConfigRepresentation.setAlias("config1"); + + AuthenticationFlowRepresentation authenticationFlowRepresentation = new AuthenticationFlowRepresentation(); + + AuthenticationExecutionExportRepresentation authenticationExecutionExportRepresentation = new AuthenticationExecutionExportRepresentation(); + authenticationExecutionExportRepresentation.setAuthenticatorConfig("config1"); + authenticationFlowRepresentation.setAuthenticationExecutions(List.of(authenticationExecutionExportRepresentation)); + + var resultingAuthConfig = service.normalizeAuthConfig(List.of(authenticatorConfigRepresentation), List.of(authenticationFlowRepresentation)); + + Assertions.assertThat(resultingAuthConfig).containsExactlyInAnyOrder(authenticatorConfigRepresentation); + } + + @Test + public void testNormalizeAuthConfigsCheckedForDuplicates(CapturedOutput output) { + AuthenticatorConfigRepresentation authenticatorConfigRepresentation1 = new AuthenticatorConfigRepresentation(); + authenticatorConfigRepresentation1.setId(UUID.randomUUID().toString()); + authenticatorConfigRepresentation1.setAlias("config1"); + + AuthenticatorConfigRepresentation authenticatorConfigRepresentation2 = new AuthenticatorConfigRepresentation(); + authenticatorConfigRepresentation2.setId(UUID.randomUUID().toString()); + authenticatorConfigRepresentation2.setAlias("config1"); + + AuthenticationFlowRepresentation authenticationFlowRepresentation = new AuthenticationFlowRepresentation(); + + AuthenticationExecutionExportRepresentation authenticationExecutionExportRepresentation = new AuthenticationExecutionExportRepresentation(); + authenticationExecutionExportRepresentation.setAuthenticatorConfig("config1"); + authenticationFlowRepresentation.setAuthenticationExecutions(List.of(authenticationExecutionExportRepresentation)); + + var resultingAuthConfig = service.normalizeAuthConfig(List.of(authenticatorConfigRepresentation1, authenticatorConfigRepresentation2), List.of(authenticationFlowRepresentation)); + + Assertions.assertThat(resultingAuthConfig).containsExactlyInAnyOrder(authenticatorConfigRepresentation1, authenticatorConfigRepresentation2); + Assertions.assertThat(output.getOut()).contains("The following authenticator configs are duplicates: [config1]"); + } +} diff --git a/src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceFlowIT.java b/src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceFlowIT.java new file mode 100644 index 000000000..a06f6a621 --- /dev/null +++ b/src/test/java/de/adorsys/keycloak/config/service/normalize/AuthFlowNormalizationServiceFlowIT.java @@ -0,0 +1,53 @@ +package de.adorsys.keycloak.config.service.normalize; + +import de.adorsys.keycloak.config.normalize.AbstractNormalizeTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.system.CapturedOutput; + +import java.util.ArrayList; +import java.util.List; + +class AuthFlowNormalizationServiceFlowIT extends AbstractNormalizeTest { + + @Autowired + AuthFlowNormalizationService service; + + + @Test + public void testNormalizeAuthFlows() { + var resultingAuthFlows = service.normalizeAuthFlows(new ArrayList<>(), new ArrayList<>()); + Assertions.assertThat(resultingAuthFlows).isNull(); + } + + @Test + public void testNormalizeAuthFlowsIgnoreBuiltInTrue() { + AuthenticationFlowRepresentation authenticationFlowRepresentation = new AuthenticationFlowRepresentation(); + authenticationFlowRepresentation.setBuiltIn(true); + + AuthenticationFlowRepresentation authenticationFlowRepresentationBaseline = new AuthenticationFlowRepresentation(); + authenticationFlowRepresentationBaseline.setBuiltIn(true); + + var resultingAuthFlows = service.normalizeAuthFlows(List.of(authenticationFlowRepresentation), List.of(authenticationFlowRepresentationBaseline)); + + Assertions.assertThat(resultingAuthFlows).isNull(); + } + + @Test + public void testNormalizeAuthFlowsIgnoreBuiltInTrueButBaselineHasEntryCreatesRecreationWarning(CapturedOutput output) { + AuthenticationFlowRepresentation authenticationFlowRepresentation = new AuthenticationFlowRepresentation(); + authenticationFlowRepresentation.setBuiltIn(true); + + AuthenticationFlowRepresentation authenticationFlowRepresentationBaseline = new AuthenticationFlowRepresentation(); + authenticationFlowRepresentationBaseline.setBuiltIn(false); + authenticationFlowRepresentationBaseline.setAlias("flow1"); + + var resultingAuthFlows = service.normalizeAuthFlows(List.of(authenticationFlowRepresentation), List.of(authenticationFlowRepresentationBaseline)); + + Assertions.assertThat(resultingAuthFlows).isNull(); + Assertions.assertThat(output.getOut()).contains("Default realm authentication flow 'flow1' was deleted in exported realm. It may be reintroduced during import"); + + } +}