From 06d4eac6d239a7d6dcad948a37004ee52f03feb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Krau=C3=9F?= Date: Fri, 18 Oct 2024 14:30:26 +0200 Subject: [PATCH 1/5] Adds support for default first broker login flow on realm level # Conflicts: # .github/workflows/ci.yaml # CHANGELOG.md # pom.xml --- .github/workflows/ci.yaml | 18 +- CHANGELOG.md | 4 + pom.xml | 80 ++ ...edAuthenticationFlowWorkaroundFactory.java | 28 +- ...nticationFlowWorkaroundFactory.java.legacy | 457 ++++++ .../AuthenticationFlowsImportService.java | 1 + ...thenticationFlowsImportService.java.legacy | 347 +++++ .../config/service/RealmImportService.java | 1 + .../service/ImportAuthenticationFlowsIT.java | 31 + .../ImportAuthenticationFlowsIT.java.legacy | 1237 +++++++++++++++++ ...ustom_default_first-broker-login-flow.json | 24 + ...ustom_default_first-broker-login-flow.json | 24 + 12 files changed, 2250 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy create mode 100644 src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy create mode 100644 src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy create mode 100644 src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json create mode 100644 src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d652a45f2..4070024fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -68,6 +68,11 @@ jobs: run: | echo "COMPATIBILITY_PROFILE=-Ppre-keycloak26" >> $GITHUB_ENV + - name: Adapt sources for Keycloak versions < 24.0.0 + if: ${{ matrix.env.KEYCLOAK_VERSION < '24.0.0' }} + run: | + echo "COMPATIBILITY_PROFILE=-Ppre-keycloak24" >> $GITHUB_ENV + - name: Adapt sources for Keycloak versions < 23.0.0 if: ${{ matrix.env.KEYCLOAK_VERSION < '23.0.0' }} run: | @@ -85,7 +90,9 @@ jobs: echo "COMPATIBILITY_PROFILE=-Ppre-keycloak19" >> $GITHUB_ENV - name: Build & Test - run: ./mvnw ${MAVEN_CLI_OPTS} -Dkeycloak.version=${{ matrix.env.KEYCLOAK_VERSION }} -Dkeycloak.client.version=${{ matrix.env.KEYCLOAK_CLIENT_VERSION }} ${ADJUSTED_RESTEASY_VERSION} clean verify -Pcoverage ${COMPATIBILITY_PROFILE} + run: | + echo "using COMPATIBILITY_PROFILE: ${COMPATIBILITY_PROFILE}" + ./mvnw ${MAVEN_CLI_OPTS} -Dkeycloak.version=${{ matrix.env.KEYCLOAK_VERSION }} -Dkeycloak.client.version=${{ matrix.env.KEYCLOAK_CLIENT_VERSION }} ${ADJUSTED_RESTEASY_VERSION} clean verify -Pcoverage ${COMPATIBILITY_PROFILE} - name: Upload coverage to Codecov uses: codecov/codecov-action@v4.6.0 @@ -199,6 +206,11 @@ jobs: key: ${{ runner.os }}-${{ matrix.java }}-maven-build-pom-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-${{ matrix.java }}-maven-build-pom + - name: Adapt sources for Keycloak versions < 24.0.0 + if: ${{ matrix.env.KEYCLOAK_VERSION < '24.0.0' }} + run: | + echo "COMPATIBILITY_PROFILE=-Ppre-keycloak24" >> $GITHUB_ENV + - name: Adapt sources for Keycloak versions < 23.0.0 if: ${{ matrix.env.KEYCLOAK_VERSION < '23.0.0' }} run: | @@ -237,6 +249,10 @@ jobs: key: ${{ runner.os }}-maven-keycloak-legacy-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven-keycloak-legacy + - name: Adapt sources for Keycloak versions < 24.0.0 + if: ${{ matrix.env.KEYCLOAK_VERSION < '24.0.0' }} + run: | + echo "COMPATIBILITY_PROFILE=-Ppre-keycloak24" >> $GITHUB_ENV - name: Adapt sources for Keycloak versions < 23.0.0 if: ${{ matrix.env.KEYCLOAK_VERSION < '23.0.0' }} run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 63dbb72f8..47d46ee0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Updated CI to use Keycloak 26.0.5 +### Added + +- Support for first broker login flows defined on realm level + ### Fixed - Allow executions of same provider with different configurations in Sub-Auth-Flows diff --git a/pom.xml b/pom.xml index 53261031a..db5b9667b 100644 --- a/pom.xml +++ b/pom.xml @@ -910,6 +910,39 @@ import org.keycloak.representations.userprofile.config.UPConfig; ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/SubGroupUtil.java + + replace-used-authentication-flow-workaround-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java + + + + replace-authentication-flow-import-service-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java + + + + replace-authentication-flow-import-service-test-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java + + replace-keycloakmock-with-legacy generate-sources @@ -990,6 +1023,53 @@ import org.keycloak.representations.userprofile.config.UPConfig; + + pre-keycloak24 + + + + com.coderplus.maven.plugins + copy-rename-maven-plugin + 1.0.1 + + + replace-used-authentication-flow-workaround-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java + + + + replace-authentication-flow-import-service-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java + + + + replace-authentication-flow-import-service-test-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java + + + + + + + coverage 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 c1aa21641..405bdb879 100644 --- a/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java +++ b/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java @@ -82,6 +82,7 @@ public class UsedAuthenticationFlowWorkaround { private String dockerAuthenticationFlow; private String registrationFlow; private String resetCredentialsFlow; + private String firstBrokerLoginFlow; private UsedAuthenticationFlowWorkaround(RealmImport realmImport) { this.realmImport = realmImport; @@ -239,6 +240,13 @@ private void disableFirstBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, Real } } } + if (Objects.equals(existingRealm.getFirstBrokerLoginFlow(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable first-broker-login-flow for in realm '{}' which is '{}'", + realmImport.getRealm(), topLevelFlowAlias + ); + disableFirstBrokerLoginFlow(existingRealm); + } } private void disablePostBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { @@ -312,6 +320,15 @@ private void disableResetCredentialsFlow(RealmRepresentation existingRealm) { realmRepository.update(existingRealm); } + private void disableFirstBrokerLoginFlow(RealmRepresentation existingRealm) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + firstBrokerLoginFlow = existingRealm.getFirstBrokerLoginFlow(); + + existingRealm.setFirstBrokerLoginFlow(otherFlowAlias); + realmRepository.update(existingRealm); + } + private void disableFirstBrokerLoginFlow(String realmName, IdentityProviderRepresentation identityProvider) { String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); @@ -403,7 +420,8 @@ private boolean hasToResetFlows() { || Strings.isNotBlank(registrationFlow) || Strings.isNotBlank(resetCredentialsFlow) || !resetFirstBrokerLoginFlow.isEmpty() - || !resetPostBrokerLoginFlow.isEmpty(); + || !resetPostBrokerLoginFlow.isEmpty() + || Strings.isNotBlank(firstBrokerLoginFlow); } private void resetFlows(RealmRepresentation existingRealm) { @@ -496,6 +514,14 @@ private void resetFirstBrokerLoginFlowsIfNeeded(RealmRepresentation existingReal identityProviderRepresentation.setFirstBrokerLoginFlowAlias(entry.getValue()); identityProviderRepository.update(existingRealm.getRealm(), identityProviderRepresentation); } + if (Strings.isNotBlank(firstBrokerLoginFlow)) { + logger.debug( + "Reset first-broker-login-flow in realm '{}' to '{}'", + realmImport.getRealm(), firstBrokerLoginFlow + ); + + existingRealm.setFirstBrokerLoginFlow(firstBrokerLoginFlow); + } } private void resetPostBrokerLoginFlowsIfNeeded(RealmRepresentation existingRealm) { diff --git a/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy b/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy new file mode 100644 index 000000000..4b9252936 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy @@ -0,0 +1,457 @@ +/*- + * ---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.factory; + +import de.adorsys.keycloak.config.model.RealmImport; +import de.adorsys.keycloak.config.repository.AuthenticationFlowRepository; +import de.adorsys.keycloak.config.repository.IdentityProviderRepository; +import de.adorsys.keycloak.config.repository.RealmRepository; +import org.apache.logging.log4j.util.Strings; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +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.util.*; + +@Service +public class UsedAuthenticationFlowWorkaroundFactory { + + private final RealmRepository realmRepository; + private final IdentityProviderRepository identityProviderRepository; + private final AuthenticationFlowRepository authenticationFlowRepository; + + @Autowired + public UsedAuthenticationFlowWorkaroundFactory( + RealmRepository realmRepository, + IdentityProviderRepository identityProviderRepository, + AuthenticationFlowRepository authenticationFlowRepository + ) { + this.realmRepository = realmRepository; + this.identityProviderRepository = identityProviderRepository; + this.authenticationFlowRepository = authenticationFlowRepository; + } + + public UsedAuthenticationFlowWorkaround buildFor(RealmImport realmImport) { + return new UsedAuthenticationFlowWorkaround(realmImport); + } + + /** + * There is no chance to update a top-level-flow, and it's not possible to recreate a top-level-flow + * which is currently in use. + * So we have to disable our top-level-flow by use a temporary created flow as long as updating the considered flow. + * This code could be maybe replace by a better update-algorithm of top-level-flows + */ + public class UsedAuthenticationFlowWorkaround { + private static final String TEMPORARY_CREATED_AUTH_FLOW = "TEMPORARY_CREATED_AUTH_FLOW"; + private final Logger logger = LoggerFactory.getLogger(UsedAuthenticationFlowWorkaround.class); + private final RealmImport realmImport; + private final Map resetFirstBrokerLoginFlow = new HashMap<>(); + private final Map resetPostBrokerLoginFlow = new HashMap<>(); + private String browserFlow; + private String directGrantFlow; + private String clientAuthenticationFlow; + private String dockerAuthenticationFlow; + private String registrationFlow; + private String resetCredentialsFlow; + + private UsedAuthenticationFlowWorkaround(RealmImport realmImport) { + this.realmImport = realmImport; + } + + public void disableTopLevelFlowIfNeeded(String topLevelFlowAlias) { + RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); + + disableBrowserFlowIfNeeded(topLevelFlowAlias, existingRealm); + disableDirectGrantFlowIfNeeded(topLevelFlowAlias, existingRealm); + disableClientAuthenticationFlowIfNeeded(topLevelFlowAlias, existingRealm); + disableDockerAuthenticationFlowIfNeeded(topLevelFlowAlias, existingRealm); + disableRegistrationFlowIfNeeded(topLevelFlowAlias, existingRealm); + disableResetCredentialsFlowIfNeeded(topLevelFlowAlias, existingRealm); + disableFirstBrokerLoginFlowsIfNeeded(topLevelFlowAlias, existingRealm); + disablePostBrokerLoginFlowsIfNeeded(topLevelFlowAlias, existingRealm); + } + + private void disableBrowserFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + if (Objects.equals(existingRealm.getBrowserFlow(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable browser-flow in realm '{}' which is '{}'", + realmImport.getRealm(), topLevelFlowAlias + ); + disableBrowserFlow(existingRealm); + } + } + + private void disableDirectGrantFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + if (Objects.equals(existingRealm.getDirectGrantFlow(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable direct-grant-flow in realm '{}' which is '{}'", + realmImport.getRealm(), topLevelFlowAlias + ); + disableDirectGrantFlow(existingRealm); + } + } + + private void disableClientAuthenticationFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + if (Objects.equals(existingRealm.getClientAuthenticationFlow(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable client-authentication-flow in realm '{}' which is '{}'", + realmImport.getRealm(), topLevelFlowAlias + ); + disableClientAuthenticationFlow(existingRealm); + } + } + + private void disableDockerAuthenticationFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + if (Objects.equals(existingRealm.getDockerAuthenticationFlow(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable docker-authentication-flow in realm '{}' which is '{}'", + realmImport.getRealm(), topLevelFlowAlias + ); + disableDockerAuthenticationFlow(existingRealm); + } + } + + private void disableRegistrationFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + if (Objects.equals(existingRealm.getRegistrationFlow(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable registration-flow in realm '{}' which is '{}'", + realmImport.getRealm(), topLevelFlowAlias + ); + disableRegistrationFlow(existingRealm); + } + } + + private void disableResetCredentialsFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + if (Objects.equals(existingRealm.getResetCredentialsFlow(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable reset-credentials-flow in realm '{}' which is '{}'", + realmImport.getRealm(), topLevelFlowAlias + ); + disableResetCredentialsFlow(existingRealm); + } + } + + private void disableFirstBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + List identityProviders = existingRealm.getIdentityProviders(); + if (identityProviders != null) { + for (IdentityProviderRepresentation identityProvider : identityProviders) { + if (Objects.equals(identityProvider.getFirstBrokerLoginFlowAlias(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable first-broker-login-flow for " + + "identity-provider '{}' in realm '{}' which is '{}'", + identityProvider.getAlias(), realmImport.getRealm(), topLevelFlowAlias + ); + + disableFirstBrokerLoginFlow(existingRealm.getRealm(), identityProvider); + } + } + } + } + + private void disablePostBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { + List identityProviders = existingRealm.getIdentityProviders(); + if (identityProviders != null) { + for (IdentityProviderRepresentation identityProvider : identityProviders) { + if (Objects.equals(identityProvider.getPostBrokerLoginFlowAlias(), topLevelFlowAlias)) { + logger.debug( + "Temporary disable post-broker-login-flow for " + + "identity-provider '{}' in realm '{}' which is '{}'", + identityProvider.getAlias(), realmImport.getRealm(), topLevelFlowAlias + ); + + disablePostBrokerLoginFlow(existingRealm.getRealm(), identityProvider); + } + } + } + } + + private void disableBrowserFlow(RealmRepresentation existingRealm) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + browserFlow = existingRealm.getBrowserFlow(); + + existingRealm.setBrowserFlow(otherFlowAlias); + realmRepository.update(existingRealm); + } + + private void disableDirectGrantFlow(RealmRepresentation existingRealm) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + directGrantFlow = existingRealm.getDirectGrantFlow(); + + existingRealm.setDirectGrantFlow(otherFlowAlias); + realmRepository.update(existingRealm); + } + + private void disableClientAuthenticationFlow(RealmRepresentation existingRealm) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + clientAuthenticationFlow = existingRealm.getClientAuthenticationFlow(); + + existingRealm.setClientAuthenticationFlow(otherFlowAlias); + realmRepository.update(existingRealm); + } + + private void disableDockerAuthenticationFlow(RealmRepresentation existingRealm) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + dockerAuthenticationFlow = existingRealm.getDockerAuthenticationFlow(); + + existingRealm.setDockerAuthenticationFlow(otherFlowAlias); + realmRepository.update(existingRealm); + } + + private void disableRegistrationFlow(RealmRepresentation existingRealm) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + registrationFlow = existingRealm.getRegistrationFlow(); + + existingRealm.setRegistrationFlow(otherFlowAlias); + realmRepository.update(existingRealm); + } + + private void disableResetCredentialsFlow(RealmRepresentation existingRealm) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + resetCredentialsFlow = existingRealm.getResetCredentialsFlow(); + + existingRealm.setResetCredentialsFlow(otherFlowAlias); + realmRepository.update(existingRealm); + } + + private void disableFirstBrokerLoginFlow(String realmName, IdentityProviderRepresentation identityProvider) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + resetFirstBrokerLoginFlow.put(identityProvider.getAlias(), identityProvider + .getFirstBrokerLoginFlowAlias()); + + identityProvider.setFirstBrokerLoginFlowAlias(otherFlowAlias); + identityProviderRepository.update(realmName, identityProvider); + } + + private void disablePostBrokerLoginFlow(String realmName, IdentityProviderRepresentation identityProvider) { + String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); + + resetPostBrokerLoginFlow.put(identityProvider.getAlias(), identityProvider + .getPostBrokerLoginFlowAlias()); + + identityProvider.setPostBrokerLoginFlowAlias(otherFlowAlias); + identityProviderRepository.update(realmName, identityProvider); + } + + private String searchTemporaryCreatedTopLevelFlowForReplacement() { + AuthenticationFlowRepresentation otherFlow; + + Optional maybeTemporaryCreatedFlow = searchForTemporaryCreatedFlow(); + + if (maybeTemporaryCreatedFlow.isPresent()) { + otherFlow = maybeTemporaryCreatedFlow.get(); + } else { + logger.debug( + "Create top-level-flow '{}' in realm '{}' to be used temporarily", + realmImport.getRealm(), TEMPORARY_CREATED_AUTH_FLOW + ); + + AuthenticationFlowRepresentation temporaryCreatedFlow = setupTemporaryCreatedFlow(); + authenticationFlowRepository.createTopLevel(realmImport.getRealm(), temporaryCreatedFlow); + + otherFlow = temporaryCreatedFlow; + } + + return otherFlow.getAlias(); + } + + private Optional searchForTemporaryCreatedFlow() { + List existingTopLevelFlows = authenticationFlowRepository + .getTopLevelFlows(realmImport.getRealm()); + + return existingTopLevelFlows.stream() + .filter(f -> Objects.equals(f.getAlias(), TEMPORARY_CREATED_AUTH_FLOW)) + .findFirst(); + } + + public void resetFlowIfNeeded() { + if (hasToResetFlows()) { + RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); + + resetFlows(existingRealm); + realmRepository.update(existingRealm); + + if (!flowInUse()) { + deleteTemporaryCreatedFlow(); + } + } + } + + private boolean flowInUse() { + RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); + return existingRealm.getBrowserFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) + || existingRealm.getDirectGrantFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) + || existingRealm.getClientAuthenticationFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) + || existingRealm.getDockerAuthenticationFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) + || existingRealm.getRegistrationFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) + || existingRealm.getResetCredentialsFlow().equals(TEMPORARY_CREATED_AUTH_FLOW); + } + + private boolean hasToResetFlows() { + return Strings.isNotBlank(browserFlow) + || Strings.isNotBlank(directGrantFlow) + || Strings.isNotBlank(clientAuthenticationFlow) + || Strings.isNotBlank(dockerAuthenticationFlow) + || Strings.isNotBlank(registrationFlow) + || Strings.isNotBlank(resetCredentialsFlow) + || !resetFirstBrokerLoginFlow.isEmpty() + || !resetPostBrokerLoginFlow.isEmpty(); + } + + private void resetFlows(RealmRepresentation existingRealm) { + resetBrowserFlowIfNeeded(existingRealm); + resetDirectGrantFlowIfNeeded(existingRealm); + resetClientAuthenticationFlowIfNeeded(existingRealm); + resetDockerAuthenticationFlowIfNeeded(existingRealm); + resetRegistrationFlowIfNeeded(existingRealm); + resetCredentialsFlowIfNeeded(existingRealm); + resetFirstBrokerLoginFlowsIfNeeded(existingRealm); + resetPostBrokerLoginFlowsIfNeeded(existingRealm); + } + + private void resetBrowserFlowIfNeeded(RealmRepresentation existingRealm) { + if (Strings.isNotBlank(browserFlow)) { + logger.debug( + "Reset browser-flow in realm '{}' to '{}'", + realmImport.getRealm(), browserFlow + ); + + existingRealm.setBrowserFlow(browserFlow); + } + } + + private void resetDirectGrantFlowIfNeeded(RealmRepresentation existingRealm) { + if (Strings.isNotBlank(directGrantFlow)) { + logger.debug( + "Reset direct-grant-flow in realm '{}' to '{}'", + realmImport.getRealm(), directGrantFlow + ); + + existingRealm.setDirectGrantFlow(directGrantFlow); + } + } + + private void resetClientAuthenticationFlowIfNeeded(RealmRepresentation existingRealm) { + if (Strings.isNotBlank(clientAuthenticationFlow)) { + logger.debug( + "Reset client-authentication-flow in realm '{}' to '{}'", + realmImport.getRealm(), clientAuthenticationFlow + ); + + existingRealm.setClientAuthenticationFlow(clientAuthenticationFlow); + } + } + + private void resetDockerAuthenticationFlowIfNeeded(RealmRepresentation existingRealm) { + if (Strings.isNotBlank(dockerAuthenticationFlow)) { + logger.debug( + "Reset docker-authentication-flow in realm '{}' to '{}'", + realmImport.getRealm(), dockerAuthenticationFlow + ); + + existingRealm.setDockerAuthenticationFlow(dockerAuthenticationFlow); + } + } + + private void resetRegistrationFlowIfNeeded(RealmRepresentation existingRealm) { + if (Strings.isNotBlank(registrationFlow)) { + logger.debug( + "Reset registration-flow in realm '{}' to '{}'", + realmImport.getRealm(), registrationFlow + ); + + existingRealm.setRegistrationFlow(registrationFlow); + } + } + + private void resetCredentialsFlowIfNeeded(RealmRepresentation existingRealm) { + if (Strings.isNotBlank(resetCredentialsFlow)) { + logger.debug( + "Reset reset-credentials-flow in realm '{}' to '{}'", + realmImport.getRealm(), resetCredentialsFlow + ); + + existingRealm.setResetCredentialsFlow(resetCredentialsFlow); + } + } + + private void resetFirstBrokerLoginFlowsIfNeeded(RealmRepresentation existingRealm) { + for (Map.Entry entry : resetFirstBrokerLoginFlow.entrySet()) { + logger.debug( + "Reset first-broker-login-flow for identity-provider '{}' in realm '{}' to '{}'", + entry.getKey(), realmImport.getRealm(), resetCredentialsFlow + ); + + IdentityProviderRepresentation identityProviderRepresentation = identityProviderRepository + .getByAlias(existingRealm.getRealm(), entry.getKey()); + + identityProviderRepresentation.setFirstBrokerLoginFlowAlias(entry.getValue()); + identityProviderRepository.update(existingRealm.getRealm(), identityProviderRepresentation); + } + } + + private void resetPostBrokerLoginFlowsIfNeeded(RealmRepresentation existingRealm) { + for (Map.Entry entry : resetPostBrokerLoginFlow.entrySet()) { + logger.debug( + "Reset post-broker-login-flow for identity-provider '{}' in realm '{}' to '{}'", + entry.getKey(), realmImport.getRealm(), resetCredentialsFlow + ); + + IdentityProviderRepresentation identityProviderRepresentation = identityProviderRepository + .getByAlias(existingRealm.getRealm(), entry.getKey()); + + identityProviderRepresentation.setPostBrokerLoginFlowAlias(entry.getValue()); + identityProviderRepository.update(existingRealm.getRealm(), identityProviderRepresentation); + } + } + + private void deleteTemporaryCreatedFlow() { + logger.debug("Delete temporary created top-level-flow '{}' in realm '{}'", + TEMPORARY_CREATED_AUTH_FLOW, realmImport.getRealm()); + + AuthenticationFlowRepresentation existingTemporaryCreatedFlow = authenticationFlowRepository + .getByAlias(realmImport.getRealm(), TEMPORARY_CREATED_AUTH_FLOW); + + authenticationFlowRepository.delete(realmImport.getRealm(), existingTemporaryCreatedFlow.getId()); + } + + private AuthenticationFlowRepresentation setupTemporaryCreatedFlow() { + AuthenticationFlowRepresentation tempFlow = new AuthenticationFlowRepresentation(); + + tempFlow.setAlias(TEMPORARY_CREATED_AUTH_FLOW); + tempFlow.setTopLevel(true); + tempFlow.setBuiltIn(false); + tempFlow.setProviderId(TEMPORARY_CREATED_AUTH_FLOW); + + return tempFlow; + } + } +} 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 9de2e9cef..808cd12b0 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java @@ -111,6 +111,7 @@ private void setupFlowsInRealm(RealmImport realmImport) { realm.setDockerAuthenticationFlow(realmImport.getDockerAuthenticationFlow()); realm.setRegistrationFlow(realmImport.getRegistrationFlow()); realm.setResetCredentialsFlow(realmImport.getResetCredentialsFlow()); + realm.setFirstBrokerLoginFlow(realmImport.getFirstBrokerLoginFlow()); realmRepository.update(realm); } diff --git a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy new file mode 100644 index 000000000..52fabee73 --- /dev/null +++ b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy @@ -0,0 +1,347 @@ +/*- + * ---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; + +import de.adorsys.keycloak.config.exception.InvalidImportException; +import de.adorsys.keycloak.config.factory.UsedAuthenticationFlowWorkaroundFactory; +import de.adorsys.keycloak.config.model.RealmImport; +import de.adorsys.keycloak.config.properties.ImportConfigProperties; +import de.adorsys.keycloak.config.properties.ImportConfigProperties.ImportManagedProperties.ImportManagedPropertiesValues; +import de.adorsys.keycloak.config.repository.AuthenticationFlowRepository; +import de.adorsys.keycloak.config.repository.RealmRepository; +import de.adorsys.keycloak.config.util.AuthenticationFlowUtil; +import de.adorsys.keycloak.config.util.CloneUtil; +import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +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.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * We have to import authentication-flows separately because in case of an existing realmName, keycloak is ignoring or + * not supporting embedded objects in realm-import's property called "authenticationFlows" + *

+ * Glossar: + * topLevel-flow: any flow which has the property 'topLevel' set to 'true'. Can contain execution-flows and executions + * sub-flow: any flow which has the property 'topLevel' set to 'false' and which are related to execution-flows within topLevel-flows + */ +@Service +public class AuthenticationFlowsImportService { + private static final Logger logger = LoggerFactory.getLogger(AuthenticationFlowsImportService.class); + + private final RealmRepository realmRepository; + private final AuthenticationFlowRepository authenticationFlowRepository; + private final ExecutionFlowsImportService executionFlowsImportService; + private final AuthenticatorConfigImportService authenticatorConfigImportService; + private final UsedAuthenticationFlowWorkaroundFactory workaroundFactory; + + private final ImportConfigProperties importConfigProperties; + + @Autowired + public AuthenticationFlowsImportService( + RealmRepository realmRepository, + AuthenticationFlowRepository authenticationFlowRepository, + ExecutionFlowsImportService executionFlowsImportService, + AuthenticatorConfigImportService authenticatorConfigImportService, UsedAuthenticationFlowWorkaroundFactory workaroundFactory, + ImportConfigProperties importConfigProperties + ) { + this.realmRepository = realmRepository; + this.authenticationFlowRepository = authenticationFlowRepository; + this.executionFlowsImportService = executionFlowsImportService; + this.authenticatorConfigImportService = authenticatorConfigImportService; + this.workaroundFactory = workaroundFactory; + this.importConfigProperties = importConfigProperties; + } + + /** + * How the import works: + * - check the authentication flows: + * -- if the flow is not present: create the authentication flow + * -- if the flow is present, check: + * --- if the flow contains any changes: update the authentication flow, which means: delete and recreate the authentication flow + * --- if nothing of above: do nothing + */ + public void doImport(RealmImport realmImport) { + List authenticationFlows = realmImport.getAuthenticationFlows(); + if (authenticationFlows == null) return; + + List topLevelFlowsToImport = AuthenticationFlowUtil.getTopLevelFlows(realmImport); + createOrUpdateTopLevelFlows(realmImport, topLevelFlowsToImport); + updateBuiltInFlows(realmImport, authenticationFlows); + setupFlowsInRealm(realmImport); + + if (importConfigProperties.getManaged().getAuthenticationFlow() == ImportManagedPropertiesValues.FULL) { + deleteTopLevelFlowsMissingInImport(realmImport, topLevelFlowsToImport); + } + } + + private void setupFlowsInRealm(RealmImport realmImport) { + RealmRepresentation realm = realmRepository.get(realmImport.getRealm()); + + realm.setBrowserFlow(realmImport.getBrowserFlow()); + realm.setDirectGrantFlow(realmImport.getDirectGrantFlow()); + realm.setClientAuthenticationFlow(realmImport.getClientAuthenticationFlow()); + realm.setDockerAuthenticationFlow(realmImport.getDockerAuthenticationFlow()); + realm.setRegistrationFlow(realmImport.getRegistrationFlow()); + realm.setResetCredentialsFlow(realmImport.getResetCredentialsFlow()); + + realmRepository.update(realm); + } + + /** + * creates or updates only the top-level flows and its executions or execution-flows + */ + private void createOrUpdateTopLevelFlows(RealmImport realmImport, List topLevelFlowsToImport) { + for (AuthenticationFlowRepresentation topLevelFlowToImport : topLevelFlowsToImport) { + if (!topLevelFlowToImport.isBuiltIn()) { + createOrUpdateTopLevelFlow(realmImport, topLevelFlowToImport); + } + } + } + + /** + * creates or updates only the top-level flow and its executions or execution-flows + */ + private void createOrUpdateTopLevelFlow( + RealmImport realmImport, + AuthenticationFlowRepresentation topLevelFlowToImport + ) { + String alias = topLevelFlowToImport.getAlias(); + + Optional maybeTopLevelFlow = authenticationFlowRepository.searchByAlias(realmImport.getRealm(), alias); + + if (maybeTopLevelFlow.isPresent()) { + AuthenticationFlowRepresentation existingTopLevelFlow = maybeTopLevelFlow.get(); + updateTopLevelFlowIfNeeded(realmImport, topLevelFlowToImport, existingTopLevelFlow); + } else { + createTopLevelFlow(realmImport, topLevelFlowToImport); + } + } + + private void createTopLevelFlow(RealmImport realmImport, AuthenticationFlowRepresentation topLevelFlowToImport) { + logger.debug("Creating top-level flow: {}", topLevelFlowToImport.getAlias()); + authenticationFlowRepository.createTopLevel(realmImport.getRealm(), topLevelFlowToImport); + + AuthenticationFlowRepresentation createdTopLevelFlow = authenticationFlowRepository.getByAlias( + realmImport.getRealm(), topLevelFlowToImport.getAlias() + ); + executionFlowsImportService.createExecutionsAndExecutionFlows(realmImport, topLevelFlowToImport, createdTopLevelFlow); + } + + private void updateTopLevelFlowIfNeeded( + RealmImport realmName, + AuthenticationFlowRepresentation topLevelFlowToImport, + AuthenticationFlowRepresentation existingAuthenticationFlow + ) { + boolean hasToBeUpdated = hasAuthenticationFlowToBeUpdated(topLevelFlowToImport, existingAuthenticationFlow) + || hasAnySubFlowToBeUpdated(realmName, topLevelFlowToImport); + + if (hasToBeUpdated) { + logger.debug("Recreate top-level flow: {}", topLevelFlowToImport.getAlias()); + recreateTopLevelFlow(realmName, topLevelFlowToImport, existingAuthenticationFlow); + } else { + logger.debug("No need to update flow: {}", topLevelFlowToImport.getAlias()); + } + } + + private boolean hasAnySubFlowToBeUpdated( + RealmImport realmImport, + AuthenticationFlowRepresentation topLevelFlowToImport + ) { + List subFlows = getAllSubFlows(realmImport, topLevelFlowToImport); + + for (AuthenticationFlowRepresentation subFlowToImport : subFlows) { + if (isSubFlowNotExistingOrHasToBeUpdated(realmImport, topLevelFlowToImport, subFlowToImport)) { + return true; + } + } + + return false; + } + + private List getAllSubFlows(RealmImport realmImport, + AuthenticationFlowRepresentation topLevelFlowToImport) { + + final List subFlows = AuthenticationFlowUtil.getSubFlowsForTopLevelFlow( + realmImport, topLevelFlowToImport); + final List allSubFlows = new ArrayList<>(subFlows); + + for (AuthenticationFlowRepresentation subflow : subFlows) { + allSubFlows.addAll(getAllSubFlows(realmImport, subflow)); + } + + return allSubFlows; + } + + private boolean isSubFlowNotExistingOrHasToBeUpdated( + RealmImport realmImport, + AuthenticationFlowRepresentation topLevelFlowToImport, + AuthenticationFlowRepresentation subFlowToImport + ) { + Optional maybeSubFlow = authenticationFlowRepository.searchSubFlow( + realmImport.getRealm(), topLevelFlowToImport.getAlias(), subFlowToImport.getAlias() + ); + + return maybeSubFlow + .map(authenticationExecutionInfoRepresentation -> hasExistingSubFlowToBeUpdated( + realmImport, subFlowToImport, authenticationExecutionInfoRepresentation + )) + .orElse(true); + } + + private boolean hasExistingSubFlowToBeUpdated( + RealmImport realmImport, + AuthenticationFlowRepresentation subFlowToImport, + AuthenticationExecutionInfoRepresentation existingSubExecutionFlow + ) { + AuthenticationFlowRepresentation existingSubFlow = authenticationFlowRepository.getFlowById( + realmImport.getRealm(), existingSubExecutionFlow.getFlowId() + ); + + return hasAuthenticationFlowToBeUpdated(subFlowToImport, existingSubFlow); + } + + /** + * Checks if the authentication flow to import and the existing representation differs in any property except "id" and: + * + * @param authenticationFlowToImport the top-level or non-top-level flow coming from import file + * @param existingAuthenticationFlow the existing top-level or non-top-level flow in keycloak + * @return true if there is any change, false if not + */ + private boolean hasAuthenticationFlowToBeUpdated( + AuthenticationFlowRepresentation authenticationFlowToImport, + AuthenticationFlowRepresentation existingAuthenticationFlow + ) { + return !CloneUtil.deepEquals( + authenticationFlowToImport, + existingAuthenticationFlow, + "id" + ); + } + + private void updateBuiltInFlows( + RealmImport realmImport, + List flowsToImport + ) { + for (AuthenticationFlowRepresentation flowToImport : flowsToImport) { + if (!flowToImport.isBuiltIn()) continue; + + String flowAlias = flowToImport.getAlias(); + Optional maybeFlow = authenticationFlowRepository + .searchByAlias(realmImport.getRealm(), flowAlias); + + if (maybeFlow.isEmpty()) { + throw new InvalidImportException(String.format( + "Cannot create flow '%s' in realm '%s': Unable to create built-in flows.", + flowToImport.getAlias(), realmImport.getRealm() + )); + } + + AuthenticationFlowRepresentation existingFlow = maybeFlow.get(); + if (hasAuthenticationFlowToBeUpdated(flowToImport, existingFlow)) { + logger.debug("Updating builtin flow: {}", flowToImport.getAlias()); + updateBuiltInFlow(realmImport, flowToImport, existingFlow); + } + } + } + + private void updateBuiltInFlow( + RealmImport realmImport, + AuthenticationFlowRepresentation topLevelFlowToImport, + AuthenticationFlowRepresentation existingAuthenticationFlow + ) { + if (!existingAuthenticationFlow.isBuiltIn()) { + throw new InvalidImportException(String.format( + "Unable to update flow '%s' in realm '%s': Change built-in flag is not possible", + topLevelFlowToImport.getAlias(), realmImport.getRealm() + )); + } + AuthenticationFlowRepresentation patchedAuthenticationFlow = CloneUtil.patch( + existingAuthenticationFlow, topLevelFlowToImport, "id" + ); + + authenticationFlowRepository.update(realmImport.getRealm(), patchedAuthenticationFlow); + + executionFlowsImportService.updateExecutionFlows(realmImport, topLevelFlowToImport); + } + + /** + * Deletes the top-level flow and all its executions and recreates them. + */ + private void recreateTopLevelFlow( + RealmImport realmImport, + AuthenticationFlowRepresentation topLevelFlowToImport, + AuthenticationFlowRepresentation existingAuthenticationFlow + ) { + AuthenticationFlowRepresentation patchedAuthenticationFlow = CloneUtil.patch( + existingAuthenticationFlow, topLevelFlowToImport, "id" + ); + + if (existingAuthenticationFlow.isBuiltIn()) { + throw new InvalidImportException(String.format( + "Unable to recreate flow '%s' in realm '%s': Deletion or creation of built-in flows is not possible", + patchedAuthenticationFlow.getAlias(), realmImport.getRealm() + )); + } + + UsedAuthenticationFlowWorkaroundFactory.UsedAuthenticationFlowWorkaround workaround = workaroundFactory.buildFor(realmImport); + workaround.disableTopLevelFlowIfNeeded(topLevelFlowToImport.getAlias()); + + authenticatorConfigImportService.deleteAuthenticationConfigs(realmImport, patchedAuthenticationFlow); + authenticationFlowRepository.delete(realmImport.getRealm(), patchedAuthenticationFlow.getId()); + authenticationFlowRepository.createTopLevel(realmImport.getRealm(), patchedAuthenticationFlow); + + AuthenticationFlowRepresentation createdTopLevelFlow = authenticationFlowRepository.getByAlias( + realmImport.getRealm(), topLevelFlowToImport.getAlias() + ); + executionFlowsImportService.createExecutionsAndExecutionFlows(realmImport, topLevelFlowToImport, createdTopLevelFlow); + + workaround.resetFlowIfNeeded(); + } + + private void deleteTopLevelFlowsMissingInImport( + RealmImport realmImport, + List importedTopLevelFlows + ) { + String realmName = realmImport.getRealm(); + List existingTopLevelFlows = authenticationFlowRepository.getTopLevelFlows(realmName) + .stream().filter(flow -> !flow.isBuiltIn()).toList(); + + Set topLevelFlowsToImportAliases = importedTopLevelFlows.stream() + .map(AuthenticationFlowRepresentation::getAlias) + .collect(Collectors.toSet()); + + for (AuthenticationFlowRepresentation existingTopLevelFlow : existingTopLevelFlows) { + if (topLevelFlowsToImportAliases.contains(existingTopLevelFlow.getAlias())) continue; + + logger.debug("Delete authentication flow: {}", existingTopLevelFlow.getAlias()); + authenticationFlowRepository.delete(realmName, existingTopLevelFlow.getId()); + } + } +} 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 c1af0878e..75372363e 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java @@ -59,6 +59,7 @@ public class RealmImportService { "defaultOptionalClientScopes", "clientProfiles", "clientPolicies", + "firstBrokerLoginFlow", }; private static final Logger logger = LoggerFactory.getLogger(RealmImportService.class); diff --git a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java index fb8e808f2..208f3c337 100644 --- a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java +++ b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java @@ -43,10 +43,12 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings({"java:S5961", "java:S5976", "deprecation"}) class ImportAuthenticationFlowsIT extends AbstractImportIT { private static final String REALM_NAME = "realmWithFlow"; + private static final String DEFAULT_FLOW_REALM_NAME = "realmWithDefaultFlow"; @Autowired private IdentityProviderRepository identityProviderRepository; @@ -1215,6 +1217,35 @@ void shouldChangeSubFlowOfFirstBrokerLoginFlow() throws IOException { assertThat(flow.getAuthenticationExecutions().get(1).getRequirement(), is("DISABLED")); } + @Test + void shouldSetCustomFirstBrokerLoginFlowAsDefaultFlow() throws IOException { + assumeTrue(VersionUtil.ge(KEYCLOAK_VERSION,"24")); // was introduced with KC 24 + + doImport("init_custom_default_first-broker-login-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(DEFAULT_FLOW_REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(DEFAULT_FLOW_REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + assertThat(realm.getFirstBrokerLoginFlow(), is("my auth flow")); + } + + @Test + void shouldUpdateCustomFirstBrokerLoginFlowWhenSetAsDefault() throws IOException { + assumeTrue(VersionUtil.ge(KEYCLOAK_VERSION,"24")); // was introduced with KC 24 + + doImport("init_custom_default_first-broker-login-flow.json"); + doImport("updated_custom_default_first-broker-login-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(DEFAULT_FLOW_REALM_NAME).partialExport(true, true); + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); + + assertThat(realm.getRealm(), is(DEFAULT_FLOW_REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + assertThat(realm.getFirstBrokerLoginFlow(), is("my auth flow")); + assertThat(flow.getAuthenticationExecutions().getFirst().getAuthenticator(), is("idp-auto-link")); + } + private List getExecutionFromFlow(AuthenticationFlowRepresentation flow, String executionAuthenticator) { List executions = flow.getAuthenticationExecutions(); diff --git a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy new file mode 100644 index 000000000..2450d513f --- /dev/null +++ b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy @@ -0,0 +1,1237 @@ +/*- + * ---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; + +import de.adorsys.keycloak.config.AbstractImportIT; +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.util.VersionUtil; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.keycloak.representations.idm.*; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static de.adorsys.keycloak.config.test.util.KeycloakRepository.getAuthenticatorConfig; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SuppressWarnings({"java:S5961", "java:S5976", "deprecation"}) +class ImportAuthenticationFlowsIT extends AbstractImportIT { + private static final String REALM_NAME = "realmWithFlow"; + + ImportAuthenticationFlowsIT() { + this.resourcePath = "import-files/auth-flows"; + } + + @Test + @Order(0) + void shouldCreateRealmWithFlows() throws IOException { + doImport("00_create_realm_with_flows.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executions = flow.getAuthenticationExecutions(); + assertThat(executions, hasSize(1)); + + List execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(1) + void shouldAddExecutionToFlow() throws IOException { + doImport("01_update_realm__add_execution_to_flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executions = flow.getAuthenticationExecutions(); + assertThat(executions, hasSize(2)); + + List execution; + execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(flow, "http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(2) + void shouldChangeExecutionRequirement() throws IOException { + doImport("02_update_realm__change_execution_requirement.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executions = flow.getAuthenticationExecutions(); + assertThat(executions, hasSize(2)); + + List execution; + execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(flow, "http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(3) + void shouldChangeExecutionPriorities() throws IOException { + doImport("03_update_realm__change_execution_priorities.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executions = flow.getAuthenticationExecutions(); + assertThat(executions, hasSize(2)); + + List execution; + execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(flow, "http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(4) + void shouldAddFlowWithExecutionFlow() throws IOException { + doImport("04_update_realm__add_flow_with_execution_flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); + assertThat(flow.getDescription(), is("My registration flow")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executionFlows = flow.getAuthenticationExecutions(); + assertThat(executionFlows, hasSize(1)); + + List execution; + execution = getExecutionFromFlow(flow, "registration-page-form"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(true)); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); + List subFlowExecutions = subFlow.getAuthenticationExecutions(); + assertThat(subFlowExecutions, hasSize(2)); + + execution = getExecutionFromFlow(subFlow, "registration-user-creation"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(subFlow, "registration-password-action"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(5) + void shouldFailWhenTryAddFlowWithDefectiveExecutionFlow() throws IOException { + RealmImport foundImport = getFirstImport("05_try_to_update_realm__add_flow_with_defective_execution_flow.json"); + + InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Execution property authenticator 'registration-page-form' can be only set if the sub-flow 'my registration form' type is 'form-flow'.")); + } + + @Test + @Order(6) + void shouldChangeFlowRequirementWithExecutionFlow() throws IOException { + doImport("10_update_realm__change_requirement_flow_with_execution_flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); + assertThat(flow.getDescription(), is("My registration flow")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executionFlows = flow.getAuthenticationExecutions(); + assertThat(executionFlows, hasSize(1)); + + List execution; + execution = getExecutionFromFlow(flow, "registration-page-form"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(true)); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); + + List subFlowExecutions = subFlow.getAuthenticationExecutions(); + assertThat(subFlowExecutions, hasSize(2)); + + execution = getExecutionFromFlow(subFlow, "registration-user-creation"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(subFlow, "registration-password-action"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(7) + void shouldFailWhenTryToUpdateDefectiveFlowRequirementWithExecutionFlow() throws IOException { + RealmImport foundImport = getFirstImport("06_try_to_update_realm__change_requirement_in_defective_flow_with_execution_flow.json"); + + InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), matchesPattern("Execution property authenticator 'registration-page-form' can be only set if the sub-flow 'my registration form' type is 'form-flow'.")); + } + + @Test + @Order(8) + void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithNotExistingExecution() throws IOException { + RealmImport foundImport = getFirstImport("07_try_to_update_realm__change_requirement_flow_with_execution_flow_with_not_existing_execution.json"); + + ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), matchesPattern("Cannot create execution 'not-existing-registration-user-creation' for non-top-level-flow 'my registration form' in realm 'realmWithFlow': .*")); + } + + @Test + @Order(9) + void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithDefectiveExecution() throws IOException { + RealmImport foundImport = getFirstImport("08_try_to_update_realm__change_requirement_flow_with_execution_flow_with_defective_execution.json"); + + ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), matchesPattern("Cannot update execution-flow 'registration-user-creation' for flow 'my registration form' in realm 'realmWithFlow': .*")); + } + + @Test + @Order(10) + void shouldFailWhenTryToUpdateFlowRequirementWithDefectiveExecutionFlow() throws IOException { + RealmImport foundImport = getFirstImport("09_try_to_update_realm__change_requirement_flow_with_defective_execution_flow.json"); + + ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Cannot create execution-flow 'docker-http-basic-authenticator' for top-level-flow 'my auth flow' in realm 'realmWithFlow'")); + } + + @Test + @Order(11) + void shouldChangeFlowPriorityWithExecutionFlow() throws IOException { + doImport("11_update_realm__change_priority_flow_with_execution_flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); + assertThat(flow.getDescription(), is("My registration flow")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executionFlows = flow.getAuthenticationExecutions(); + assertThat(executionFlows, hasSize(1)); + + List execution; + execution = getExecutionFromFlow(flow, "registration-page-form"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(true)); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); + + List subFlowExecutions = subFlow.getAuthenticationExecutions(); + assertThat(subFlowExecutions, hasSize(2)); + + execution = getExecutionFromFlow(subFlow, "registration-user-creation"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(subFlow, "registration-password-action"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(12) + void shouldSetRegistrationFlow() throws IOException { + doImport("12_update_realm__set_registration_flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getRegistrationFlow(), is("my registration")); + } + + @Test + @Order(13) + void shouldChangeRegistrationFlow() throws IOException { + doImport("13_update_realm__change_registration_flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getRegistrationFlow(), is("my registration")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); + assertThat(flow.getDescription(), is("My changed registration flow")); + } + + @Test + @Order(14) + void shouldAddAndSetResetCredentialsFlow() throws IOException { + doImport("14_update_realm__add_and_set_custom_reset-credentials-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getResetCredentialsFlow(), is("my reset credentials")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my reset credentials"); + assertThat(flow.getDescription(), is("My reset credentials for a user if they forgot their password or something")); + } + + @Test + @Order(15) + void shouldChangeResetCredentialsFlow() throws IOException { + doImport("15_update_realm__change_custom_reset-credentials-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getResetCredentialsFlow(), is("my reset credentials")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my reset credentials"); + assertThat(flow.getDescription(), is("My changed reset credentials for a user if they forgot their password or something")); + } + + @Test + @Order(16) + void shouldAddAndSetBrowserFlow() throws IOException { + doImport("16_update_realm__add_and_set_custom_browser-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getBrowserFlow(), is("my browser")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my browser"); + assertThat(flow.getDescription(), is("My browser based authentication")); + } + + @Test + @Order(17) + void shouldChangeBrowserFlow() throws IOException { + doImport("17.1_update_realm__change_custom_browser-flow.json"); + + assertThatBrowserFlowIsUpdated(4); + + doImport("17.2_update_realm__change_custom_browser-flow_with_multiple_subflow.json"); + + AuthenticationFlowRepresentation flow = assertThatBrowserFlowIsUpdated(5); + + AuthenticationExecutionExportRepresentation myForms2 = getExecutionFlowFromFlow(flow, "my forms 2"); + assertThat(myForms2, notNullValue()); + assertThat(myForms2.getRequirement(), is("ALTERNATIVE")); + assertThat(myForms2.isUserSetupAllowed(), is(false)); + assertThat(myForms2.isAutheticatorFlow(), is(true)); + + if (VersionUtil.ge(KEYCLOAK_VERSION, "25")) { + assertThat(myForms2.getPriority(), is(27)); + } else { + assertThat(myForms2.getPriority(), is(4)); + } + } + + AuthenticationFlowRepresentation assertThatBrowserFlowIsUpdated(int expectedNumberOfExecutionsInFlow) { + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getBrowserFlow(), is("my browser")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my browser"); + assertThat(flow.getDescription(), is("My changed browser based authentication")); + + assertThat(flow.getAuthenticationExecutions().size(), is(expectedNumberOfExecutionsInFlow)); + + AuthenticationExecutionExportRepresentation myForms = getExecutionFlowFromFlow(flow, "my forms"); + assertThat(myForms, notNullValue()); + assertThat(myForms.getRequirement(), is("ALTERNATIVE")); + assertThat(myForms.isUserSetupAllowed(), is(false)); + assertThat(myForms.isAutheticatorFlow(), is(true)); + + if (VersionUtil.ge(KEYCLOAK_VERSION, "25")) { + assertThat(myForms.getPriority(), is(26)); + } else { + assertThat(myForms.getPriority(), is(3)); + } + + return flow; + } + + @Test + @Order(18) + void shouldAddAndSetDirectGrantFlow() throws IOException { + doImport("18_update_realm__add_and_set_custom_direct-grant-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getDirectGrantFlow(), is("my direct grant")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my direct grant"); + assertThat(flow.getDescription(), is("My OpenID Connect Resource Owner Grant")); + } + + @Test + @Order(19) + void shouldChangeDirectGrantFlow() throws IOException { + doImport("19_update_realm__change_custom_direct-grant-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getDirectGrantFlow(), is("my direct grant")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my direct grant"); + assertThat(flow.getDescription(), is("My changed OpenID Connect Resource Owner Grant")); + } + + @Test + @Order(20) + void shouldAddAndSetClientAuthenticationFlow() throws IOException { + doImport("20_update_realm__add_and_set_custom_client-authentication-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getClientAuthenticationFlow(), is("my clients")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my clients"); + assertThat(flow.getDescription(), is("My Base authentication for clients")); + } + + @Test + @Order(21) + void shouldChangeClientAuthenticationFlow() throws IOException { + doImport("21_update_realm__change_custom_client-authentication-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getClientAuthenticationFlow(), is("my clients")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my clients"); + assertThat(flow.getDescription(), is("My changed Base authentication for clients")); + } + + @Test + @Order(22) + void shouldAddAndSetDockerAuthenticationFlow() throws IOException { + doImport("22_update_realm__add_and_set_custom_docker-authentication-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getDockerAuthenticationFlow(), is("my docker auth")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my docker auth"); + assertThat(flow.getDescription(), is("My Used by Docker clients to authenticate against the IDP")); + } + + @Test + @Order(23) + void shouldChangeDockerAuthenticationFlow() throws IOException { + doImport("23_update_realm__change_custom_docker-authentication-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getDockerAuthenticationFlow(), is("my docker auth")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my docker auth"); + assertThat(flow.getDescription(), is("My changed Used by Docker clients to authenticate against the IDP")); + } + + @Test + @Order(24) + void shouldAddTopLevelFlowWithExecutionFlow() throws IOException { + doImport("24_update_realm__add-top-level-flow-with-execution-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow with execution-flows"); + assertThat(flow.getDescription(), is("My authentication flow with authentication executions")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my execution-flow"); + + List subFlowExecutions = subFlow.getAuthenticationExecutions(); + assertThat(subFlowExecutions, hasSize(2)); + + List execution = getExecutionFromFlow(subFlow, "auth-username-password-form"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("auth-username-password-form")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(subFlow, "auth-otp-form"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("auth-otp-form")); + assertThat(execution.get(0).getRequirement(), is("CONDITIONAL")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(25) + void shouldUpdateTopLevelFlowWithPseudoId() throws IOException { + doImport("25_update_realm__update-top-level-flow-with-pseudo-id.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); + } + + @Test + @Order(26) + void shouldUpdateSubFlowWithPseudoId() throws IOException { + doImport("26_update_realm__update-non-top-level-flow-with-pseudo-id.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); + assertThat(subFlow.getDescription(), is("My registration form with pseudo-id")); + } + + @Test + @Order(27) + @DisabledIfSystemProperty(named = "keycloak.version", matches = "import.files.locations*", disabledReason = "https://github.com/keycloak/keycloak/issues/10176") + void shouldNotUpdateSubFlowWithPseudoId() throws IOException { + RealmImport foundImport = getFirstImport("27_update_realm__try-to-update-non-top-level-flow-with-pseudo-id.json"); + + ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), matchesPattern("Cannot create execution-flow 'my registration form' for top-level-flow 'my registration' in realm 'realmWithFlow': .*")); + } + + @Test + @Order(28) + void shouldUpdateSubFlowWithPseudoIdAndReUseTempFlow() throws IOException { + doImport("28_update_realm__update-non-top-level-flow-with-pseudo-id.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(realm.getRegistrationFlow(), is("my registration")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); + assertThat(flow.getDescription(), is("changed registration flow")); + + AuthenticationFlowRepresentation tempFlow = getAuthenticationFlow(realm, "TEMPORARY_CREATED_AUTH_FLOW"); + assertThat(tempFlow, nullValue()); + } + + @Test + @Order(29) + @DisabledIfSystemProperty(named = "keycloak.version", matches = "import.files.locations*", disabledReason = "https://github.com/keycloak/keycloak/issues/10176") + void shouldNotUpdateInvalidTopLevelFlow() throws IOException { + RealmImport foundImport = getFirstImport("29_update_realm__try-to-update-invalid-top-level-flow.json"); + + ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), matchesPattern("Cannot create top-level-flow 'my auth flow' in realm 'realmWithFlow': .*")); + } + + @Test + @Order(30) + void shouldCreateMultipleExecutionsWithSameAuthenticator() throws IOException { + doImport("30_update_realm__add_multiple_executions_with_same_authenticator.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "with-two-ids"); + assertThat(flow.getDescription(), is("my browser based authentication")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + + List execution; + execution = getExecutionFromFlow(flow, "identity-provider-redirector"); + assertThat(execution, hasSize(2)); + + List executionsId1 = execution.stream() + .filter((config) -> config.getAuthenticatorConfig() != null) + .filter((config) -> config.getAuthenticatorConfig().equals("id1")) + .toList(); + + assertThat(executionsId1, hasSize(1)); + assertThat(executionsId1.get(0).getAuthenticator(), is("identity-provider-redirector")); + assertThat(executionsId1.get(0).getAuthenticatorConfig(), is("id1")); + assertThat(executionsId1.get(0).getRequirement(), is("ALTERNATIVE")); + + List executionsId2 = execution.stream() + .filter((config) -> config.getAuthenticatorConfig() != null) + .filter((config) -> config.getAuthenticatorConfig().equals("id2")) + .toList(); + + assertThat(executionsId2, hasSize(1)); + assertThat(executionsId2.get(0).getAuthenticator(), is("identity-provider-redirector")); + assertThat(executionsId2.get(0).getAuthenticatorConfig(), is("id2")); + assertThat(executionsId2.get(0).getRequirement(), is("ALTERNATIVE")); + + assertThat(executionsId2.get(0).getPriority(), greaterThan(executionsId1.get(0).getPriority())); + + List authConfig; + authConfig = getAuthenticatorConfig(realm, "id1"); + assertThat(authConfig, hasSize(1)); + assertThat(authConfig.get(0).getAlias(), is("id1")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); + + authConfig = getAuthenticatorConfig(realm, "id2"); + assertThat(authConfig, hasSize(1)); + assertThat(authConfig.get(0).getAlias(), is("id2")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); + } + + @Test + @Order(31) + void shouldUpdateMultipleExecutionsWithSameAuthenticator() throws IOException { + doImport("31_update_realm__update_multiple_executions_with_same_authenticator.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "with-two-ids"); + assertThat(flow.getDescription(), is("my browser based authentication")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + + List execution; + execution = getExecutionFromFlow(flow, "identity-provider-redirector"); + assertThat(execution, hasSize(3)); + + List authConfig; + authConfig = getAuthenticatorConfig(realm, "id1"); + assertThat(authConfig, hasSize(2)); + assertThat(authConfig.get(0).getAlias(), is("id1")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); + assertThat(authConfig.get(1).getAlias(), is("id1")); + assertThat(authConfig.get(1).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); + + authConfig = getAuthenticatorConfig(realm, "id2"); + assertThat(authConfig, hasSize(1)); + assertThat(authConfig.get(0).getAlias(), is("id2")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); + } + + @Test + @Order(32) + void shouldUpdateMultipleExecutionsWithSameAuthenticatorWithConfig() throws IOException { + doImport("32_update_realm__update_multiple_executions_with_same_authenticator_with_config.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "with-two-ids"); + assertThat(flow.getDescription(), is("my browser based authentication")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + + List execution; + execution = getExecutionFromFlow(flow, "identity-provider-redirector"); + assertThat(execution, hasSize(3)); + + List authConfig; + authConfig = getAuthenticatorConfig(realm, "id1"); + assertThat(authConfig, hasSize(2)); + assertThat(authConfig.get(0).getAlias(), is("id1")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); + assertThat(authConfig.get(1).getAlias(), is("id1")); + assertThat(authConfig.get(1).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); + + authConfig = getAuthenticatorConfig(realm, "id2"); + assertThat(authConfig, hasSize(1)); + assertThat(authConfig.get(0).getAlias(), is("id2")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id4"))); + } + + @Test + @Order(33) + void shouldCreateMultipleSubFlowExecutionsWithSameAuthenticator() throws IOException { + doImport("33_update_realm__add_multiple_subflow_executions_with_same_authenticator.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation topLevelFlow = getAuthenticationFlow(realm, "my top level auth flow"); + assertThat(topLevelFlow.isBuiltIn(), is(false)); + assertThat(topLevelFlow.isTopLevel(), is(true)); + assertThat(topLevelFlow.getAuthenticationExecutions().size(), is(1)); + assertThat(topLevelFlow.getAuthenticationExecutions().get(0).getFlowAlias(), is("my sub auth flow")); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my sub auth flow"); + assertThat(subFlow.isBuiltIn(), is(false)); + assertThat(subFlow.isTopLevel(), is(false)); + assertThat(subFlow.getAuthenticationExecutions().size(), is(3)); + + List execution; + execution = getExecutionFromFlow(subFlow, "identity-provider-redirector"); + assertThat(execution, hasSize(2)); + + List executionsId1 = execution.stream() + .filter((config) -> config.getAuthenticatorConfig() != null) + .filter((config) -> config.getAuthenticatorConfig().equals("config-1")) + .collect(Collectors.toList()); + + assertThat(executionsId1, hasSize(1)); + assertThat(executionsId1.get(0).getAuthenticator(), is("identity-provider-redirector")); + assertThat(executionsId1.get(0).getAuthenticatorConfig(), is("config-1")); + assertThat(executionsId1.get(0).getRequirement(), is("ALTERNATIVE")); + + List executionsId2 = execution.stream() + .filter((config) -> config.getAuthenticatorConfig() != null) + .filter((config) -> config.getAuthenticatorConfig().equals("config-2")) + .collect(Collectors.toList()); + + assertThat(executionsId2, hasSize(1)); + assertThat(executionsId2.get(0).getAuthenticator(), is("identity-provider-redirector")); + assertThat(executionsId2.get(0).getAuthenticatorConfig(), is("config-2")); + assertThat(executionsId2.get(0).getRequirement(), is("ALTERNATIVE")); + + assertThat(executionsId2.get(0).getPriority(), greaterThan(executionsId1.get(0).getPriority())); + + List authConfig; + authConfig = getAuthenticatorConfig(realm, "config-1"); + assertThat(authConfig, hasSize(1)); + assertThat(authConfig.get(0).getAlias(), is("config-1")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); + + authConfig = getAuthenticatorConfig(realm, "config-2"); + assertThat(authConfig, hasSize(1)); + assertThat(authConfig.get(0).getAlias(), is("config-2")); + assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); + } + + @Test + @Order(40) + void shouldFailWhenTryingToUpdateBuiltInFlow() throws IOException { + RealmImport foundImport = getFirstImport("40_update_realm__try-to-update-built-in-flow.json"); + + InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Unable to update flow 'my auth flow with execution-flows' in realm 'realmWithFlow': Change built-in flag is not possible")); + } + + @Test + @Order(41) + void shouldFailWhenTryingToUpdateWithNonExistingFlow() throws IOException { + RealmImport foundImport = getFirstImport("41_update_realm__try-to-update-with-non-existing-flow.json"); + + ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Non-toplevel flow not found: non existing sub flow")); + } + + @Test + @Order(42) + void shouldUpdateTopLevelBuiltinFLow() throws IOException { + doImport("42_update_realm__update_builtin-top-level-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "saml ecp"); + assertThat(flow.getDescription(), is("SAML ECP Profile Authentication Flow")); + assertThat(flow.isBuiltIn(), is(true)); + assertThat(flow.isTopLevel(), is(true)); + + List execution = getExecutionFromFlow(flow, "http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("CONDITIONAL")); + assertThat(execution.get(0).getPriority(), is(10)); + assertThat(execution.get(0).isUserSetupAllowed(), is(false)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(43) + void shouldUpdateSubBuiltinFLow() throws IOException { + doImport("43_update_realm__update_builtin-non-top-level-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "registration form"); + assertThat(flow.getDescription(), is("updated registration form")); + assertThat(flow.isBuiltIn(), is(true)); + assertThat(flow.isTopLevel(), is(false)); + + List execution = getExecutionFromFlow(flow, "registration-recaptcha-action"); + assertThat(execution.get(0).getAuthenticator(), is("registration-recaptcha-action")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(60)); + assertThat(execution.get(0).isUserSetupAllowed(), is(false)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(44) + void shouldNotUpdateFlowWithBuiltInFalse() throws IOException { + RealmImport foundImport = getFirstImport("44_update_realm__try-to-update-flow-set-builtin-false.json"); + + InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Unable to recreate flow 'saml ecp' in realm 'realmWithFlow': Deletion or creation of built-in flows is not possible")); + } + + @Test + @Order(45) + void shouldNotUpdateFlowWithBuiltInTrue() throws IOException { + RealmImport foundImport = getFirstImport("45_update_realm__try-to-update-flow-set-builtin-true.json"); + + InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Unable to update flow 'my auth flow' in realm 'realmWithFlow': Change built-in flag is not possible")); + } + + @Test + @Order(46) + @DisabledIfSystemProperty(named = "keycloak.version", matches = "17.0.0", disabledReason = "https://github.com/keycloak/keycloak/issues/10176") + void shouldNotCreateBuiltInFlow() throws IOException { + RealmImport foundImport = getFirstImport("46_update_realm__try-to-create-builtin-flow.json"); + + ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Cannot update top-level-flow 'saml ecp' in realm 'realmWithFlow'.")); + } + + @Test + @Order(47) + void shouldUpdateRealmUpdateBuiltInFlowWithPseudoId() throws IOException { + doImport("47_update_realm__update-builtin-flow-with-pseudo-id.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + } + + @Test + @Order(50) + void shouldRemoveSubFlow() throws IOException { + doImport("50_update_realm__update-remove-non-top-level-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow; + flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executions = flow.getAuthenticationExecutions(); + assertThat(executions, hasSize(1)); + + List execution; + execution = getExecutionFromFlow(flow, "http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + flow = getAuthenticationFlow(realm, "my registration"); + assertThat(flow.getDescription(), is("My registration flow")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executionFlows = flow.getAuthenticationExecutions(); + assertThat(executionFlows, hasSize(1)); + + execution = getExecutionFromFlow(flow, "registration-page-form"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(true)); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); + + List subFlowExecutions = subFlow.getAuthenticationExecutions(); + assertThat(subFlowExecutions, hasSize(2)); + + execution = getExecutionFromFlow(subFlow, "registration-password-action"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(subFlow, "registration-user-creation"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(51) + void shouldSkipRemoveTopLevelFlow() throws IOException { + doImport("51_update_realm__skip-remove-top-level-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow; + flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executions = flow.getAuthenticationExecutions(); + assertThat(executions, hasSize(1)); + + List execution = getExecutionFromFlow(flow, "http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + flow = getAuthenticationFlow(realm, "my registration"); + assertThat(flow.getDescription(), is("My registration flow")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executionFlows = flow.getAuthenticationExecutions(); + assertThat(executionFlows, hasSize(1)); + + execution = getExecutionFromFlow(flow, "registration-page-form"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(true)); + + AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); + + List subFlowExecutions = subFlow.getAuthenticationExecutions(); + assertThat(subFlowExecutions, hasSize(2)); + + execution = getExecutionFromFlow(subFlow, "registration-password-action"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + execution = getExecutionFromFlow(subFlow, "registration-user-creation"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); + assertThat(execution.get(0).getRequirement(), is("REQUIRED")); + assertThat(execution.get(0).getPriority(), is(1)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + } + + @Test + @Order(52) + void shouldRemoveTopLevelFlow() throws IOException { + doImport("52_update_realm__update-remove-top-level-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); + assertThat(flow.getProviderId(), is("basic-flow")); + assertThat(flow.isBuiltIn(), is(false)); + assertThat(flow.isTopLevel(), is(true)); + + List executions = flow.getAuthenticationExecutions(); + assertThat(executions, hasSize(1)); + + List execution = getExecutionFromFlow(flow, "http-basic-authenticator"); + assertThat(execution, hasSize(1)); + assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); + assertThat(execution.get(0).getRequirement(), is("DISABLED")); + assertThat(execution.get(0).getPriority(), is(0)); + assertThat(execution.get(0).isAutheticatorFlow(), is(false)); + + AuthenticationFlowRepresentation deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration"); + + assertThat(deletedTopLevelFlow, is(nullValue())); + + deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration from"); + assertThat(deletedTopLevelFlow, is(nullValue())); + } + + @Test + @Order(53) + void shouldRemoveAllTopLevelFlow() throws IOException { + doImport("53_update_realm__update-remove-all-top-level-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + AuthenticationFlowRepresentation deletedTopLevelFlow; + deletedTopLevelFlow = getAuthenticationFlow(realm, "my auth flow"); + assertThat(deletedTopLevelFlow, is(nullValue())); + + deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration"); + assertThat(deletedTopLevelFlow, is(nullValue())); + + deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration from"); + assertThat(deletedTopLevelFlow, is(nullValue())); + + List allTopLevelFlow = realm.getAuthenticationFlows() + .stream().filter(e -> !e.isBuiltIn()) + .toList(); + + assertThat(allTopLevelFlow, is(empty())); + } + + @Test + @Order(61) + void shouldAddAndSetFirstBrokerLoginFlowForIdentityProvider() throws IOException { + doImport("61_update_realm__add_and_set_custom_first-broker-login-flow_for_identity-provider.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + IdentityProviderRepresentation identityProviderRepresentation = realm.getIdentityProviders().stream() + .filter(idp -> Objects.equals(idp.getAlias(), "keycloak-oidc")).findFirst().orElse(null); + + assertThat(identityProviderRepresentation, is(not(nullValue()))); + assertThat(identityProviderRepresentation.getFirstBrokerLoginFlowAlias(), is("my-first-broker-login")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my-first-broker-login"); + assertThat(flow.getDescription(), is("custom first broker login")); + } + + @Test + @Order(62) + void shouldChangeFirstBrokerLoginFlowForIdentityProvider() throws IOException { + doImport("62_update_realm__change_custom_first-broker-login-flow_for_identity-provider.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + IdentityProviderRepresentation identityProviderRepresentation = realm.getIdentityProviders().stream() + .filter(idp -> Objects.equals(idp.getAlias(), "keycloak-oidc")).findFirst().orElse(null); + + assertThat(identityProviderRepresentation, is(not(nullValue()))); + assertThat(identityProviderRepresentation.getFirstBrokerLoginFlowAlias(), is("my-first-broker-login")); + + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my-first-broker-login"); + assertThat(flow.getDescription(), is("custom changed first broker login")); + } + + @Test + @Order(64) + void shouldNotUpdateFlowWithAuthenticatorOnBasicFlow() throws IOException { + RealmImport foundImport = getFirstImport("63_update-realm__try-to-set-authenticator-basic-flow.json"); + + InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); + + assertThat(thrown.getMessage(), is("Execution property authenticator 'registration-page-form' can be only set if the sub-flow 'JToken Conditional' type is 'form-flow'.")); + } + + @Test + void shouldChangeSubFlowOfFirstBrokerLoginFlow() throws IOException { + doImport("init_custom_first-broker-login-flow.json"); + doImport("updated_custom_first-broker-login-flow.json"); + + RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); + AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my-first-broker-login-handle-existing-account"); + + assertThat(realm.getRealm(), is(REALM_NAME)); + assertThat(realm.isEnabled(), is(true)); + + assertThat(flow.getAuthenticationExecutions().get(1).getRequirement(), is("DISABLED")); + } + + private List getExecutionFromFlow(AuthenticationFlowRepresentation flow, String executionAuthenticator) { + List executions = flow.getAuthenticationExecutions(); + + return executions.stream() + .filter(e -> e.getAuthenticator().equals(executionAuthenticator)) + .toList(); + } + + private AuthenticationExecutionExportRepresentation getExecutionFlowFromFlow(AuthenticationFlowRepresentation flow, String subFlow) { + List executions = flow.getAuthenticationExecutions(); + + return executions.stream() + .filter(f -> f.getFlowAlias() != null && f.getFlowAlias().equals(subFlow)) + .findFirst() + .orElse(null); + } + + private AuthenticationFlowRepresentation getAuthenticationFlow(RealmRepresentation realm, String flowAlias) { + List authenticationFlows = realm.getAuthenticationFlows(); + return authenticationFlows.stream() + .filter(f -> f.getAlias().equals(flowAlias)) + .findFirst() + .orElse(null); + } +} diff --git a/src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json b/src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json new file mode 100644 index 000000000..48f02a814 --- /dev/null +++ b/src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json @@ -0,0 +1,24 @@ +{ + "enabled": true, + "realm": "realmWithDefaultFlow", + "firstBrokerLoginFlow": "my auth flow", + "authenticationFlows": [ + { + "alias": "my auth flow", + "description": "My auth flow for testing", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 0, + "userSetupAllowed": true, + "autheticatorFlow": false, + "authenticatorFlow": false + } + ] + } + ] +} diff --git a/src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json b/src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json new file mode 100644 index 000000000..6aa379b6a --- /dev/null +++ b/src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json @@ -0,0 +1,24 @@ +{ + "enabled": true, + "realm": "realmWithDefaultFlow", + "firstBrokerLoginFlow": "my auth flow", + "authenticationFlows": [ + { + "alias": "my auth flow", + "description": "My auth flow for testing", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "idp-auto-link", + "requirement": "REQUIRED", + "priority": 0, + "userSetupAllowed": false, + "autheticatorFlow": false, + "authenticatorFlow": false + } + ] + } + ] +} From 311fc39a67d928583ae816e26f006fcd21646d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Krau=C3=9F?= Date: Tue, 22 Oct 2024 13:34:17 +0200 Subject: [PATCH 2/5] Adds support for default first broker login flow on realm level --- pom.xml | 77 +++++++++++++++++++ .../service/ImportAuthenticationFlowsIT.java | 2 +- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index db5b9667b..68f278f23 100644 --- a/pom.xml +++ b/pom.xml @@ -677,6 +677,39 @@ ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportManagedNoDeleteIT.java + + replace-used-authentication-flow-workaround-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java + + + + replace-authentication-flow-import-service-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java + + + + replace-authentication-flow-import-service-test-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java + + @@ -770,6 +803,39 @@ import org.keycloak.representations.userprofile.config.UPConfig; ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/SubGroupUtil.java + + replace-used-authentication-flow-workaround-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java + + + + replace-authentication-flow-import-service-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy + ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java + + + + replace-authentication-flow-import-service-test-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java + + replace-keycloakmock-with-legacy generate-sources @@ -1065,6 +1131,17 @@ import org.keycloak.representations.userprofile.config.UPConfig; ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java + + replace-keycloakmock-with-legacy + generate-sources + + copy + + + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakMock.java.legacy + ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakMock.java + + diff --git a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java index 208f3c337..62eea8059 100644 --- a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java +++ b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java @@ -1243,7 +1243,7 @@ void shouldUpdateCustomFirstBrokerLoginFlowWhenSetAsDefault() throws IOException assertThat(realm.getRealm(), is(DEFAULT_FLOW_REALM_NAME)); assertThat(realm.isEnabled(), is(true)); assertThat(realm.getFirstBrokerLoginFlow(), is("my auth flow")); - assertThat(flow.getAuthenticationExecutions().getFirst().getAuthenticator(), is("idp-auto-link")); + assertThat(flow.getAuthenticationExecutions().get(0).getAuthenticator(), is("idp-auto-link")); } private List getExecutionFromFlow(AuthenticationFlowRepresentation flow, String executionAuthenticator) { From da260c31ecdb0a6e38e6944db9d5ac6cd3f5ff6c Mon Sep 17 00:00:00 2001 From: Motouom Victoire Date: Tue, 26 Nov 2024 10:22:32 +0100 Subject: [PATCH 3/5] Add log output during impport --- .../config/service/RealmImportService.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) 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 c1af0878e..fad4eed42 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java @@ -173,37 +173,61 @@ private void setEventsEnabledWorkaround(RealmImport realmImport) { } private void createRealm(RealmImport realmImport) { - logger.debug("Creating realm '{}' ...", realmImport.getRealm()); + logger.info("Starting creation of realm '{}'", realmImport.getRealm()); RealmRepresentation realm = CloneUtil.deepClone(realmImport, RealmRepresentation.class, ignoredPropertiesForRealmImport); + logger.debug("RealmRepresentation created: {}", realm); + logger.info("Creating realm in repository"); realmRepository.create(realm); + logger.info("Realm '{}' created successfully", realm.getRealm()); - // refresh the access token to update the scopes. See: https://github.com/adorsys/keycloak-config-cli/issues/339 + logger.debug("Refreshing access token to update scopes"); keycloakProvider.refreshToken(); + logger.debug("Access token refreshed"); + logger.debug("Loading state for realm '{}'", realmImport.getRealm()); stateService.loadState(realmImport); + logger.debug("State loaded for realm '{}'", realmImport.getRealm()); + logger.info("Configuring realm '{}'", realmImport.getRealm()); configureRealm(realmImport, realm); + logger.info("Configuration completed for realm '{}'", realmImport.getRealm()); + logger.info("Creation of realm '{}' completed", realmImport.getRealm()); } private void updateRealm(RealmImport realmImport) { - logger.debug("Updating realm '{}'...", realmImport.getRealm()); + logger.info("Starting update of realm '{}'", realmImport.getRealm()); + logger.debug("Cloning realm import to RealmRepresentation"); RealmRepresentation realm = CloneUtil.deepClone(realmImport, RealmRepresentation.class, ignoredPropertiesForRealmImport); + logger.debug("RealmRepresentation created for update: {}", realm); + logger.info("Fetching existing realm '{}'", realmImport.getRealm()); RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); + logger.debug("Existing realm retrieved: {}", existingRealm); if (existingRealm.getEventsExpiration() != null) { + logger.debug("Preserving events expiration: {}", existingRealm.getEventsExpiration()); realm.setEventsExpiration(existingRealm.getEventsExpiration()); } + logger.info("Updating OTP policy for realm '{}'", realmImport.getRealm()); otpPolicyImportService.updateOtpPolicy(realmImport.getRealm(), realm); + logger.debug("OTP policy updated for realm '{}'", realmImport.getRealm()); + logger.debug("Loading state for updated realm '{}'", realmImport.getRealm()); stateService.loadState(realm); + logger.debug("State loaded for updated realm '{}'", realmImport.getRealm()); + logger.info("Updating realm in repository"); realmRepository.update(realm); + logger.info("Realm '{}' updated successfully", realm.getRealm()); + logger.info("Configuring updated realm '{}'", realmImport.getRealm()); configureRealm(realmImport, realm); + logger.info("Configuration completed for updated realm '{}'", realmImport.getRealm()); + logger.info("Update of realm '{}' completed", realmImport.getRealm()); } + private void importOtpPolicy(RealmImport realmImport) { RealmRepresentation realmConfig = realmRepository.get(realmImport.getRealm()); if (realmConfig.getOtpPolicyAlgorithm() != null) { From 2f5eea6f1b88576c08a08b064245b16ac455d6c2 Mon Sep 17 00:00:00 2001 From: Motouom Victoire Date: Wed, 27 Nov 2024 08:46:05 +0100 Subject: [PATCH 4/5] Edit the changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dfa99486..2ed32d8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Fixed - otpPolicyAlgorithm ignored during import [#847](https://github.com/adorsys/keycloak-config-cli/issues/847) +## Added +- Enhance Standard output logging [#1215](https://github.com/adorsys/keycloak-config-cli/issues/1215) + ### Added - Added Navigation in the readme [#1099](https://github.com/adorsys/keycloak-config-cli/issues/1099) ### Added From fcba7963ae4ecb72028e06158d7ddb28c9cb083f Mon Sep 17 00:00:00 2001 From: Motouom Victoire Date: Wed, 27 Nov 2024 10:41:54 +0100 Subject: [PATCH 5/5] Revert "Merge branch 'fork/maximilian-krauss/feat/add-support-for-default-first-broker-login-flow' into stdout-improvement" This reverts commit ad7ca225c35ad57f7ada25b1263f5a18330d636c, reversing changes made to da260c31ecdb0a6e38e6944db9d5ac6cd3f5ff6c. --- .github/workflows/ci.yaml | 18 +- CHANGELOG.md | 4 - pom.xml | 157 --- ...edAuthenticationFlowWorkaroundFactory.java | 28 +- ...nticationFlowWorkaroundFactory.java.legacy | 457 ------ .../AuthenticationFlowsImportService.java | 1 - ...thenticationFlowsImportService.java.legacy | 347 ----- .../config/service/RealmImportService.java | 1 - .../service/ImportAuthenticationFlowsIT.java | 31 - .../ImportAuthenticationFlowsIT.java.legacy | 1237 ----------------- ...ustom_default_first-broker-login-flow.json | 24 - ...ustom_default_first-broker-login-flow.json | 24 - 12 files changed, 2 insertions(+), 2327 deletions(-) delete mode 100644 src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy delete mode 100644 src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy delete mode 100644 src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy delete mode 100644 src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json delete mode 100644 src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2f5c46c70..cad440b49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -68,11 +68,6 @@ jobs: run: | echo "COMPATIBILITY_PROFILE=-Ppre-keycloak26" >> $GITHUB_ENV - - name: Adapt sources for Keycloak versions < 24.0.0 - if: ${{ matrix.env.KEYCLOAK_VERSION < '24.0.0' }} - run: | - echo "COMPATIBILITY_PROFILE=-Ppre-keycloak24" >> $GITHUB_ENV - - name: Adapt sources for Keycloak versions < 23.0.0 if: ${{ matrix.env.KEYCLOAK_VERSION < '23.0.0' }} run: | @@ -90,9 +85,7 @@ jobs: echo "COMPATIBILITY_PROFILE=-Ppre-keycloak19" >> $GITHUB_ENV - name: Build & Test - run: | - echo "using COMPATIBILITY_PROFILE: ${COMPATIBILITY_PROFILE}" - ./mvnw ${MAVEN_CLI_OPTS} -Dkeycloak.version=${{ matrix.env.KEYCLOAK_VERSION }} -Dkeycloak.client.version=${{ matrix.env.KEYCLOAK_CLIENT_VERSION }} ${ADJUSTED_RESTEASY_VERSION} clean verify -Pcoverage ${COMPATIBILITY_PROFILE} + run: ./mvnw ${MAVEN_CLI_OPTS} -Dkeycloak.version=${{ matrix.env.KEYCLOAK_VERSION }} -Dkeycloak.client.version=${{ matrix.env.KEYCLOAK_CLIENT_VERSION }} ${ADJUSTED_RESTEASY_VERSION} clean verify -Pcoverage ${COMPATIBILITY_PROFILE} - name: Upload coverage to Codecov uses: codecov/codecov-action@v5.0.7 @@ -206,11 +199,6 @@ jobs: key: ${{ runner.os }}-${{ matrix.java }}-maven-build-pom-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-${{ matrix.java }}-maven-build-pom - - name: Adapt sources for Keycloak versions < 24.0.0 - if: ${{ matrix.env.KEYCLOAK_VERSION < '24.0.0' }} - run: | - echo "COMPATIBILITY_PROFILE=-Ppre-keycloak24" >> $GITHUB_ENV - - name: Adapt sources for Keycloak versions < 23.0.0 if: ${{ matrix.env.KEYCLOAK_VERSION < '23.0.0' }} run: | @@ -249,10 +237,6 @@ jobs: key: ${{ runner.os }}-maven-keycloak-legacy-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven-keycloak-legacy - - name: Adapt sources for Keycloak versions < 24.0.0 - if: ${{ matrix.env.KEYCLOAK_VERSION < '24.0.0' }} - run: | - echo "COMPATIBILITY_PROFILE=-Ppre-keycloak24" >> $GITHUB_ENV - name: Adapt sources for Keycloak versions < 23.0.0 if: ${{ matrix.env.KEYCLOAK_VERSION < '23.0.0' }} run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed32d8e8..81141a788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,10 +31,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Updated CI to use Keycloak 26.0.5 -### Added - -- Support for first broker login flows defined on realm level - ### Fixed - Allow executions of same provider with different configurations in Sub-Auth-Flows diff --git a/pom.xml b/pom.xml index eead8633d..8c84fb126 100644 --- a/pom.xml +++ b/pom.xml @@ -677,39 +677,6 @@ ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportManagedNoDeleteIT.java - - replace-used-authentication-flow-workaround-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java - - - - replace-authentication-flow-import-service-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java - - - - replace-authentication-flow-import-service-test-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java - - @@ -803,39 +770,6 @@ import org.keycloak.representations.userprofile.config.UPConfig; ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/SubGroupUtil.java - - replace-used-authentication-flow-workaround-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java - - - - replace-authentication-flow-import-service-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java - - - - replace-authentication-flow-import-service-test-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java - - replace-keycloakmock-with-legacy generate-sources @@ -976,39 +910,6 @@ import org.keycloak.representations.userprofile.config.UPConfig; ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/SubGroupUtil.java - - replace-used-authentication-flow-workaround-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java - - - - replace-authentication-flow-import-service-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java - - - - replace-authentication-flow-import-service-test-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java - - replace-keycloakmock-with-legacy generate-sources @@ -1089,64 +990,6 @@ import org.keycloak.representations.userprofile.config.UPConfig; - - pre-keycloak24 - - - - com.coderplus.maven.plugins - copy-rename-maven-plugin - 1.0.1 - - - replace-used-authentication-flow-workaround-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java - - - - replace-authentication-flow-import-service-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy - ${project.basedir}/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java - - - - replace-authentication-flow-import-service-test-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java - - - - replace-keycloakmock-with-legacy - generate-sources - - copy - - - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakMock.java.legacy - ${project.basedir}/src/test/java/de/adorsys/keycloak/config/test/util/KeycloakMock.java - - - - - - - coverage 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 405bdb879..c1aa21641 100644 --- a/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java +++ b/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java @@ -82,7 +82,6 @@ public class UsedAuthenticationFlowWorkaround { private String dockerAuthenticationFlow; private String registrationFlow; private String resetCredentialsFlow; - private String firstBrokerLoginFlow; private UsedAuthenticationFlowWorkaround(RealmImport realmImport) { this.realmImport = realmImport; @@ -240,13 +239,6 @@ private void disableFirstBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, Real } } } - if (Objects.equals(existingRealm.getFirstBrokerLoginFlow(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable first-broker-login-flow for in realm '{}' which is '{}'", - realmImport.getRealm(), topLevelFlowAlias - ); - disableFirstBrokerLoginFlow(existingRealm); - } } private void disablePostBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { @@ -320,15 +312,6 @@ private void disableResetCredentialsFlow(RealmRepresentation existingRealm) { realmRepository.update(existingRealm); } - private void disableFirstBrokerLoginFlow(RealmRepresentation existingRealm) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - firstBrokerLoginFlow = existingRealm.getFirstBrokerLoginFlow(); - - existingRealm.setFirstBrokerLoginFlow(otherFlowAlias); - realmRepository.update(existingRealm); - } - private void disableFirstBrokerLoginFlow(String realmName, IdentityProviderRepresentation identityProvider) { String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); @@ -420,8 +403,7 @@ private boolean hasToResetFlows() { || Strings.isNotBlank(registrationFlow) || Strings.isNotBlank(resetCredentialsFlow) || !resetFirstBrokerLoginFlow.isEmpty() - || !resetPostBrokerLoginFlow.isEmpty() - || Strings.isNotBlank(firstBrokerLoginFlow); + || !resetPostBrokerLoginFlow.isEmpty(); } private void resetFlows(RealmRepresentation existingRealm) { @@ -514,14 +496,6 @@ private void resetFirstBrokerLoginFlowsIfNeeded(RealmRepresentation existingReal identityProviderRepresentation.setFirstBrokerLoginFlowAlias(entry.getValue()); identityProviderRepository.update(existingRealm.getRealm(), identityProviderRepresentation); } - if (Strings.isNotBlank(firstBrokerLoginFlow)) { - logger.debug( - "Reset first-broker-login-flow in realm '{}' to '{}'", - realmImport.getRealm(), firstBrokerLoginFlow - ); - - existingRealm.setFirstBrokerLoginFlow(firstBrokerLoginFlow); - } } private void resetPostBrokerLoginFlowsIfNeeded(RealmRepresentation existingRealm) { diff --git a/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy b/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy deleted file mode 100644 index 4b9252936..000000000 --- a/src/main/java/de/adorsys/keycloak/config/factory/UsedAuthenticationFlowWorkaroundFactory.java.legacy +++ /dev/null @@ -1,457 +0,0 @@ -/*- - * ---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.factory; - -import de.adorsys.keycloak.config.model.RealmImport; -import de.adorsys.keycloak.config.repository.AuthenticationFlowRepository; -import de.adorsys.keycloak.config.repository.IdentityProviderRepository; -import de.adorsys.keycloak.config.repository.RealmRepository; -import org.apache.logging.log4j.util.Strings; -import org.keycloak.representations.idm.AuthenticationFlowRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; -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.util.*; - -@Service -public class UsedAuthenticationFlowWorkaroundFactory { - - private final RealmRepository realmRepository; - private final IdentityProviderRepository identityProviderRepository; - private final AuthenticationFlowRepository authenticationFlowRepository; - - @Autowired - public UsedAuthenticationFlowWorkaroundFactory( - RealmRepository realmRepository, - IdentityProviderRepository identityProviderRepository, - AuthenticationFlowRepository authenticationFlowRepository - ) { - this.realmRepository = realmRepository; - this.identityProviderRepository = identityProviderRepository; - this.authenticationFlowRepository = authenticationFlowRepository; - } - - public UsedAuthenticationFlowWorkaround buildFor(RealmImport realmImport) { - return new UsedAuthenticationFlowWorkaround(realmImport); - } - - /** - * There is no chance to update a top-level-flow, and it's not possible to recreate a top-level-flow - * which is currently in use. - * So we have to disable our top-level-flow by use a temporary created flow as long as updating the considered flow. - * This code could be maybe replace by a better update-algorithm of top-level-flows - */ - public class UsedAuthenticationFlowWorkaround { - private static final String TEMPORARY_CREATED_AUTH_FLOW = "TEMPORARY_CREATED_AUTH_FLOW"; - private final Logger logger = LoggerFactory.getLogger(UsedAuthenticationFlowWorkaround.class); - private final RealmImport realmImport; - private final Map resetFirstBrokerLoginFlow = new HashMap<>(); - private final Map resetPostBrokerLoginFlow = new HashMap<>(); - private String browserFlow; - private String directGrantFlow; - private String clientAuthenticationFlow; - private String dockerAuthenticationFlow; - private String registrationFlow; - private String resetCredentialsFlow; - - private UsedAuthenticationFlowWorkaround(RealmImport realmImport) { - this.realmImport = realmImport; - } - - public void disableTopLevelFlowIfNeeded(String topLevelFlowAlias) { - RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); - - disableBrowserFlowIfNeeded(topLevelFlowAlias, existingRealm); - disableDirectGrantFlowIfNeeded(topLevelFlowAlias, existingRealm); - disableClientAuthenticationFlowIfNeeded(topLevelFlowAlias, existingRealm); - disableDockerAuthenticationFlowIfNeeded(topLevelFlowAlias, existingRealm); - disableRegistrationFlowIfNeeded(topLevelFlowAlias, existingRealm); - disableResetCredentialsFlowIfNeeded(topLevelFlowAlias, existingRealm); - disableFirstBrokerLoginFlowsIfNeeded(topLevelFlowAlias, existingRealm); - disablePostBrokerLoginFlowsIfNeeded(topLevelFlowAlias, existingRealm); - } - - private void disableBrowserFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - if (Objects.equals(existingRealm.getBrowserFlow(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable browser-flow in realm '{}' which is '{}'", - realmImport.getRealm(), topLevelFlowAlias - ); - disableBrowserFlow(existingRealm); - } - } - - private void disableDirectGrantFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - if (Objects.equals(existingRealm.getDirectGrantFlow(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable direct-grant-flow in realm '{}' which is '{}'", - realmImport.getRealm(), topLevelFlowAlias - ); - disableDirectGrantFlow(existingRealm); - } - } - - private void disableClientAuthenticationFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - if (Objects.equals(existingRealm.getClientAuthenticationFlow(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable client-authentication-flow in realm '{}' which is '{}'", - realmImport.getRealm(), topLevelFlowAlias - ); - disableClientAuthenticationFlow(existingRealm); - } - } - - private void disableDockerAuthenticationFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - if (Objects.equals(existingRealm.getDockerAuthenticationFlow(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable docker-authentication-flow in realm '{}' which is '{}'", - realmImport.getRealm(), topLevelFlowAlias - ); - disableDockerAuthenticationFlow(existingRealm); - } - } - - private void disableRegistrationFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - if (Objects.equals(existingRealm.getRegistrationFlow(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable registration-flow in realm '{}' which is '{}'", - realmImport.getRealm(), topLevelFlowAlias - ); - disableRegistrationFlow(existingRealm); - } - } - - private void disableResetCredentialsFlowIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - if (Objects.equals(existingRealm.getResetCredentialsFlow(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable reset-credentials-flow in realm '{}' which is '{}'", - realmImport.getRealm(), topLevelFlowAlias - ); - disableResetCredentialsFlow(existingRealm); - } - } - - private void disableFirstBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - List identityProviders = existingRealm.getIdentityProviders(); - if (identityProviders != null) { - for (IdentityProviderRepresentation identityProvider : identityProviders) { - if (Objects.equals(identityProvider.getFirstBrokerLoginFlowAlias(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable first-broker-login-flow for " - + "identity-provider '{}' in realm '{}' which is '{}'", - identityProvider.getAlias(), realmImport.getRealm(), topLevelFlowAlias - ); - - disableFirstBrokerLoginFlow(existingRealm.getRealm(), identityProvider); - } - } - } - } - - private void disablePostBrokerLoginFlowsIfNeeded(String topLevelFlowAlias, RealmRepresentation existingRealm) { - List identityProviders = existingRealm.getIdentityProviders(); - if (identityProviders != null) { - for (IdentityProviderRepresentation identityProvider : identityProviders) { - if (Objects.equals(identityProvider.getPostBrokerLoginFlowAlias(), topLevelFlowAlias)) { - logger.debug( - "Temporary disable post-broker-login-flow for " - + "identity-provider '{}' in realm '{}' which is '{}'", - identityProvider.getAlias(), realmImport.getRealm(), topLevelFlowAlias - ); - - disablePostBrokerLoginFlow(existingRealm.getRealm(), identityProvider); - } - } - } - } - - private void disableBrowserFlow(RealmRepresentation existingRealm) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - browserFlow = existingRealm.getBrowserFlow(); - - existingRealm.setBrowserFlow(otherFlowAlias); - realmRepository.update(existingRealm); - } - - private void disableDirectGrantFlow(RealmRepresentation existingRealm) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - directGrantFlow = existingRealm.getDirectGrantFlow(); - - existingRealm.setDirectGrantFlow(otherFlowAlias); - realmRepository.update(existingRealm); - } - - private void disableClientAuthenticationFlow(RealmRepresentation existingRealm) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - clientAuthenticationFlow = existingRealm.getClientAuthenticationFlow(); - - existingRealm.setClientAuthenticationFlow(otherFlowAlias); - realmRepository.update(existingRealm); - } - - private void disableDockerAuthenticationFlow(RealmRepresentation existingRealm) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - dockerAuthenticationFlow = existingRealm.getDockerAuthenticationFlow(); - - existingRealm.setDockerAuthenticationFlow(otherFlowAlias); - realmRepository.update(existingRealm); - } - - private void disableRegistrationFlow(RealmRepresentation existingRealm) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - registrationFlow = existingRealm.getRegistrationFlow(); - - existingRealm.setRegistrationFlow(otherFlowAlias); - realmRepository.update(existingRealm); - } - - private void disableResetCredentialsFlow(RealmRepresentation existingRealm) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - resetCredentialsFlow = existingRealm.getResetCredentialsFlow(); - - existingRealm.setResetCredentialsFlow(otherFlowAlias); - realmRepository.update(existingRealm); - } - - private void disableFirstBrokerLoginFlow(String realmName, IdentityProviderRepresentation identityProvider) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - resetFirstBrokerLoginFlow.put(identityProvider.getAlias(), identityProvider - .getFirstBrokerLoginFlowAlias()); - - identityProvider.setFirstBrokerLoginFlowAlias(otherFlowAlias); - identityProviderRepository.update(realmName, identityProvider); - } - - private void disablePostBrokerLoginFlow(String realmName, IdentityProviderRepresentation identityProvider) { - String otherFlowAlias = searchTemporaryCreatedTopLevelFlowForReplacement(); - - resetPostBrokerLoginFlow.put(identityProvider.getAlias(), identityProvider - .getPostBrokerLoginFlowAlias()); - - identityProvider.setPostBrokerLoginFlowAlias(otherFlowAlias); - identityProviderRepository.update(realmName, identityProvider); - } - - private String searchTemporaryCreatedTopLevelFlowForReplacement() { - AuthenticationFlowRepresentation otherFlow; - - Optional maybeTemporaryCreatedFlow = searchForTemporaryCreatedFlow(); - - if (maybeTemporaryCreatedFlow.isPresent()) { - otherFlow = maybeTemporaryCreatedFlow.get(); - } else { - logger.debug( - "Create top-level-flow '{}' in realm '{}' to be used temporarily", - realmImport.getRealm(), TEMPORARY_CREATED_AUTH_FLOW - ); - - AuthenticationFlowRepresentation temporaryCreatedFlow = setupTemporaryCreatedFlow(); - authenticationFlowRepository.createTopLevel(realmImport.getRealm(), temporaryCreatedFlow); - - otherFlow = temporaryCreatedFlow; - } - - return otherFlow.getAlias(); - } - - private Optional searchForTemporaryCreatedFlow() { - List existingTopLevelFlows = authenticationFlowRepository - .getTopLevelFlows(realmImport.getRealm()); - - return existingTopLevelFlows.stream() - .filter(f -> Objects.equals(f.getAlias(), TEMPORARY_CREATED_AUTH_FLOW)) - .findFirst(); - } - - public void resetFlowIfNeeded() { - if (hasToResetFlows()) { - RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); - - resetFlows(existingRealm); - realmRepository.update(existingRealm); - - if (!flowInUse()) { - deleteTemporaryCreatedFlow(); - } - } - } - - private boolean flowInUse() { - RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); - return existingRealm.getBrowserFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) - || existingRealm.getDirectGrantFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) - || existingRealm.getClientAuthenticationFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) - || existingRealm.getDockerAuthenticationFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) - || existingRealm.getRegistrationFlow().equals(TEMPORARY_CREATED_AUTH_FLOW) - || existingRealm.getResetCredentialsFlow().equals(TEMPORARY_CREATED_AUTH_FLOW); - } - - private boolean hasToResetFlows() { - return Strings.isNotBlank(browserFlow) - || Strings.isNotBlank(directGrantFlow) - || Strings.isNotBlank(clientAuthenticationFlow) - || Strings.isNotBlank(dockerAuthenticationFlow) - || Strings.isNotBlank(registrationFlow) - || Strings.isNotBlank(resetCredentialsFlow) - || !resetFirstBrokerLoginFlow.isEmpty() - || !resetPostBrokerLoginFlow.isEmpty(); - } - - private void resetFlows(RealmRepresentation existingRealm) { - resetBrowserFlowIfNeeded(existingRealm); - resetDirectGrantFlowIfNeeded(existingRealm); - resetClientAuthenticationFlowIfNeeded(existingRealm); - resetDockerAuthenticationFlowIfNeeded(existingRealm); - resetRegistrationFlowIfNeeded(existingRealm); - resetCredentialsFlowIfNeeded(existingRealm); - resetFirstBrokerLoginFlowsIfNeeded(existingRealm); - resetPostBrokerLoginFlowsIfNeeded(existingRealm); - } - - private void resetBrowserFlowIfNeeded(RealmRepresentation existingRealm) { - if (Strings.isNotBlank(browserFlow)) { - logger.debug( - "Reset browser-flow in realm '{}' to '{}'", - realmImport.getRealm(), browserFlow - ); - - existingRealm.setBrowserFlow(browserFlow); - } - } - - private void resetDirectGrantFlowIfNeeded(RealmRepresentation existingRealm) { - if (Strings.isNotBlank(directGrantFlow)) { - logger.debug( - "Reset direct-grant-flow in realm '{}' to '{}'", - realmImport.getRealm(), directGrantFlow - ); - - existingRealm.setDirectGrantFlow(directGrantFlow); - } - } - - private void resetClientAuthenticationFlowIfNeeded(RealmRepresentation existingRealm) { - if (Strings.isNotBlank(clientAuthenticationFlow)) { - logger.debug( - "Reset client-authentication-flow in realm '{}' to '{}'", - realmImport.getRealm(), clientAuthenticationFlow - ); - - existingRealm.setClientAuthenticationFlow(clientAuthenticationFlow); - } - } - - private void resetDockerAuthenticationFlowIfNeeded(RealmRepresentation existingRealm) { - if (Strings.isNotBlank(dockerAuthenticationFlow)) { - logger.debug( - "Reset docker-authentication-flow in realm '{}' to '{}'", - realmImport.getRealm(), dockerAuthenticationFlow - ); - - existingRealm.setDockerAuthenticationFlow(dockerAuthenticationFlow); - } - } - - private void resetRegistrationFlowIfNeeded(RealmRepresentation existingRealm) { - if (Strings.isNotBlank(registrationFlow)) { - logger.debug( - "Reset registration-flow in realm '{}' to '{}'", - realmImport.getRealm(), registrationFlow - ); - - existingRealm.setRegistrationFlow(registrationFlow); - } - } - - private void resetCredentialsFlowIfNeeded(RealmRepresentation existingRealm) { - if (Strings.isNotBlank(resetCredentialsFlow)) { - logger.debug( - "Reset reset-credentials-flow in realm '{}' to '{}'", - realmImport.getRealm(), resetCredentialsFlow - ); - - existingRealm.setResetCredentialsFlow(resetCredentialsFlow); - } - } - - private void resetFirstBrokerLoginFlowsIfNeeded(RealmRepresentation existingRealm) { - for (Map.Entry entry : resetFirstBrokerLoginFlow.entrySet()) { - logger.debug( - "Reset first-broker-login-flow for identity-provider '{}' in realm '{}' to '{}'", - entry.getKey(), realmImport.getRealm(), resetCredentialsFlow - ); - - IdentityProviderRepresentation identityProviderRepresentation = identityProviderRepository - .getByAlias(existingRealm.getRealm(), entry.getKey()); - - identityProviderRepresentation.setFirstBrokerLoginFlowAlias(entry.getValue()); - identityProviderRepository.update(existingRealm.getRealm(), identityProviderRepresentation); - } - } - - private void resetPostBrokerLoginFlowsIfNeeded(RealmRepresentation existingRealm) { - for (Map.Entry entry : resetPostBrokerLoginFlow.entrySet()) { - logger.debug( - "Reset post-broker-login-flow for identity-provider '{}' in realm '{}' to '{}'", - entry.getKey(), realmImport.getRealm(), resetCredentialsFlow - ); - - IdentityProviderRepresentation identityProviderRepresentation = identityProviderRepository - .getByAlias(existingRealm.getRealm(), entry.getKey()); - - identityProviderRepresentation.setPostBrokerLoginFlowAlias(entry.getValue()); - identityProviderRepository.update(existingRealm.getRealm(), identityProviderRepresentation); - } - } - - private void deleteTemporaryCreatedFlow() { - logger.debug("Delete temporary created top-level-flow '{}' in realm '{}'", - TEMPORARY_CREATED_AUTH_FLOW, realmImport.getRealm()); - - AuthenticationFlowRepresentation existingTemporaryCreatedFlow = authenticationFlowRepository - .getByAlias(realmImport.getRealm(), TEMPORARY_CREATED_AUTH_FLOW); - - authenticationFlowRepository.delete(realmImport.getRealm(), existingTemporaryCreatedFlow.getId()); - } - - private AuthenticationFlowRepresentation setupTemporaryCreatedFlow() { - AuthenticationFlowRepresentation tempFlow = new AuthenticationFlowRepresentation(); - - tempFlow.setAlias(TEMPORARY_CREATED_AUTH_FLOW); - tempFlow.setTopLevel(true); - tempFlow.setBuiltIn(false); - tempFlow.setProviderId(TEMPORARY_CREATED_AUTH_FLOW); - - return tempFlow; - } - } -} 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 808cd12b0..9de2e9cef 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java @@ -111,7 +111,6 @@ private void setupFlowsInRealm(RealmImport realmImport) { realm.setDockerAuthenticationFlow(realmImport.getDockerAuthenticationFlow()); realm.setRegistrationFlow(realmImport.getRegistrationFlow()); realm.setResetCredentialsFlow(realmImport.getResetCredentialsFlow()); - realm.setFirstBrokerLoginFlow(realmImport.getFirstBrokerLoginFlow()); realmRepository.update(realm); } diff --git a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy deleted file mode 100644 index 52fabee73..000000000 --- a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java.legacy +++ /dev/null @@ -1,347 +0,0 @@ -/*- - * ---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; - -import de.adorsys.keycloak.config.exception.InvalidImportException; -import de.adorsys.keycloak.config.factory.UsedAuthenticationFlowWorkaroundFactory; -import de.adorsys.keycloak.config.model.RealmImport; -import de.adorsys.keycloak.config.properties.ImportConfigProperties; -import de.adorsys.keycloak.config.properties.ImportConfigProperties.ImportManagedProperties.ImportManagedPropertiesValues; -import de.adorsys.keycloak.config.repository.AuthenticationFlowRepository; -import de.adorsys.keycloak.config.repository.RealmRepository; -import de.adorsys.keycloak.config.util.AuthenticationFlowUtil; -import de.adorsys.keycloak.config.util.CloneUtil; -import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; -import org.keycloak.representations.idm.AuthenticationFlowRepresentation; -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.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * We have to import authentication-flows separately because in case of an existing realmName, keycloak is ignoring or - * not supporting embedded objects in realm-import's property called "authenticationFlows" - *

- * Glossar: - * topLevel-flow: any flow which has the property 'topLevel' set to 'true'. Can contain execution-flows and executions - * sub-flow: any flow which has the property 'topLevel' set to 'false' and which are related to execution-flows within topLevel-flows - */ -@Service -public class AuthenticationFlowsImportService { - private static final Logger logger = LoggerFactory.getLogger(AuthenticationFlowsImportService.class); - - private final RealmRepository realmRepository; - private final AuthenticationFlowRepository authenticationFlowRepository; - private final ExecutionFlowsImportService executionFlowsImportService; - private final AuthenticatorConfigImportService authenticatorConfigImportService; - private final UsedAuthenticationFlowWorkaroundFactory workaroundFactory; - - private final ImportConfigProperties importConfigProperties; - - @Autowired - public AuthenticationFlowsImportService( - RealmRepository realmRepository, - AuthenticationFlowRepository authenticationFlowRepository, - ExecutionFlowsImportService executionFlowsImportService, - AuthenticatorConfigImportService authenticatorConfigImportService, UsedAuthenticationFlowWorkaroundFactory workaroundFactory, - ImportConfigProperties importConfigProperties - ) { - this.realmRepository = realmRepository; - this.authenticationFlowRepository = authenticationFlowRepository; - this.executionFlowsImportService = executionFlowsImportService; - this.authenticatorConfigImportService = authenticatorConfigImportService; - this.workaroundFactory = workaroundFactory; - this.importConfigProperties = importConfigProperties; - } - - /** - * How the import works: - * - check the authentication flows: - * -- if the flow is not present: create the authentication flow - * -- if the flow is present, check: - * --- if the flow contains any changes: update the authentication flow, which means: delete and recreate the authentication flow - * --- if nothing of above: do nothing - */ - public void doImport(RealmImport realmImport) { - List authenticationFlows = realmImport.getAuthenticationFlows(); - if (authenticationFlows == null) return; - - List topLevelFlowsToImport = AuthenticationFlowUtil.getTopLevelFlows(realmImport); - createOrUpdateTopLevelFlows(realmImport, topLevelFlowsToImport); - updateBuiltInFlows(realmImport, authenticationFlows); - setupFlowsInRealm(realmImport); - - if (importConfigProperties.getManaged().getAuthenticationFlow() == ImportManagedPropertiesValues.FULL) { - deleteTopLevelFlowsMissingInImport(realmImport, topLevelFlowsToImport); - } - } - - private void setupFlowsInRealm(RealmImport realmImport) { - RealmRepresentation realm = realmRepository.get(realmImport.getRealm()); - - realm.setBrowserFlow(realmImport.getBrowserFlow()); - realm.setDirectGrantFlow(realmImport.getDirectGrantFlow()); - realm.setClientAuthenticationFlow(realmImport.getClientAuthenticationFlow()); - realm.setDockerAuthenticationFlow(realmImport.getDockerAuthenticationFlow()); - realm.setRegistrationFlow(realmImport.getRegistrationFlow()); - realm.setResetCredentialsFlow(realmImport.getResetCredentialsFlow()); - - realmRepository.update(realm); - } - - /** - * creates or updates only the top-level flows and its executions or execution-flows - */ - private void createOrUpdateTopLevelFlows(RealmImport realmImport, List topLevelFlowsToImport) { - for (AuthenticationFlowRepresentation topLevelFlowToImport : topLevelFlowsToImport) { - if (!topLevelFlowToImport.isBuiltIn()) { - createOrUpdateTopLevelFlow(realmImport, topLevelFlowToImport); - } - } - } - - /** - * creates or updates only the top-level flow and its executions or execution-flows - */ - private void createOrUpdateTopLevelFlow( - RealmImport realmImport, - AuthenticationFlowRepresentation topLevelFlowToImport - ) { - String alias = topLevelFlowToImport.getAlias(); - - Optional maybeTopLevelFlow = authenticationFlowRepository.searchByAlias(realmImport.getRealm(), alias); - - if (maybeTopLevelFlow.isPresent()) { - AuthenticationFlowRepresentation existingTopLevelFlow = maybeTopLevelFlow.get(); - updateTopLevelFlowIfNeeded(realmImport, topLevelFlowToImport, existingTopLevelFlow); - } else { - createTopLevelFlow(realmImport, topLevelFlowToImport); - } - } - - private void createTopLevelFlow(RealmImport realmImport, AuthenticationFlowRepresentation topLevelFlowToImport) { - logger.debug("Creating top-level flow: {}", topLevelFlowToImport.getAlias()); - authenticationFlowRepository.createTopLevel(realmImport.getRealm(), topLevelFlowToImport); - - AuthenticationFlowRepresentation createdTopLevelFlow = authenticationFlowRepository.getByAlias( - realmImport.getRealm(), topLevelFlowToImport.getAlias() - ); - executionFlowsImportService.createExecutionsAndExecutionFlows(realmImport, topLevelFlowToImport, createdTopLevelFlow); - } - - private void updateTopLevelFlowIfNeeded( - RealmImport realmName, - AuthenticationFlowRepresentation topLevelFlowToImport, - AuthenticationFlowRepresentation existingAuthenticationFlow - ) { - boolean hasToBeUpdated = hasAuthenticationFlowToBeUpdated(topLevelFlowToImport, existingAuthenticationFlow) - || hasAnySubFlowToBeUpdated(realmName, topLevelFlowToImport); - - if (hasToBeUpdated) { - logger.debug("Recreate top-level flow: {}", topLevelFlowToImport.getAlias()); - recreateTopLevelFlow(realmName, topLevelFlowToImport, existingAuthenticationFlow); - } else { - logger.debug("No need to update flow: {}", topLevelFlowToImport.getAlias()); - } - } - - private boolean hasAnySubFlowToBeUpdated( - RealmImport realmImport, - AuthenticationFlowRepresentation topLevelFlowToImport - ) { - List subFlows = getAllSubFlows(realmImport, topLevelFlowToImport); - - for (AuthenticationFlowRepresentation subFlowToImport : subFlows) { - if (isSubFlowNotExistingOrHasToBeUpdated(realmImport, topLevelFlowToImport, subFlowToImport)) { - return true; - } - } - - return false; - } - - private List getAllSubFlows(RealmImport realmImport, - AuthenticationFlowRepresentation topLevelFlowToImport) { - - final List subFlows = AuthenticationFlowUtil.getSubFlowsForTopLevelFlow( - realmImport, topLevelFlowToImport); - final List allSubFlows = new ArrayList<>(subFlows); - - for (AuthenticationFlowRepresentation subflow : subFlows) { - allSubFlows.addAll(getAllSubFlows(realmImport, subflow)); - } - - return allSubFlows; - } - - private boolean isSubFlowNotExistingOrHasToBeUpdated( - RealmImport realmImport, - AuthenticationFlowRepresentation topLevelFlowToImport, - AuthenticationFlowRepresentation subFlowToImport - ) { - Optional maybeSubFlow = authenticationFlowRepository.searchSubFlow( - realmImport.getRealm(), topLevelFlowToImport.getAlias(), subFlowToImport.getAlias() - ); - - return maybeSubFlow - .map(authenticationExecutionInfoRepresentation -> hasExistingSubFlowToBeUpdated( - realmImport, subFlowToImport, authenticationExecutionInfoRepresentation - )) - .orElse(true); - } - - private boolean hasExistingSubFlowToBeUpdated( - RealmImport realmImport, - AuthenticationFlowRepresentation subFlowToImport, - AuthenticationExecutionInfoRepresentation existingSubExecutionFlow - ) { - AuthenticationFlowRepresentation existingSubFlow = authenticationFlowRepository.getFlowById( - realmImport.getRealm(), existingSubExecutionFlow.getFlowId() - ); - - return hasAuthenticationFlowToBeUpdated(subFlowToImport, existingSubFlow); - } - - /** - * Checks if the authentication flow to import and the existing representation differs in any property except "id" and: - * - * @param authenticationFlowToImport the top-level or non-top-level flow coming from import file - * @param existingAuthenticationFlow the existing top-level or non-top-level flow in keycloak - * @return true if there is any change, false if not - */ - private boolean hasAuthenticationFlowToBeUpdated( - AuthenticationFlowRepresentation authenticationFlowToImport, - AuthenticationFlowRepresentation existingAuthenticationFlow - ) { - return !CloneUtil.deepEquals( - authenticationFlowToImport, - existingAuthenticationFlow, - "id" - ); - } - - private void updateBuiltInFlows( - RealmImport realmImport, - List flowsToImport - ) { - for (AuthenticationFlowRepresentation flowToImport : flowsToImport) { - if (!flowToImport.isBuiltIn()) continue; - - String flowAlias = flowToImport.getAlias(); - Optional maybeFlow = authenticationFlowRepository - .searchByAlias(realmImport.getRealm(), flowAlias); - - if (maybeFlow.isEmpty()) { - throw new InvalidImportException(String.format( - "Cannot create flow '%s' in realm '%s': Unable to create built-in flows.", - flowToImport.getAlias(), realmImport.getRealm() - )); - } - - AuthenticationFlowRepresentation existingFlow = maybeFlow.get(); - if (hasAuthenticationFlowToBeUpdated(flowToImport, existingFlow)) { - logger.debug("Updating builtin flow: {}", flowToImport.getAlias()); - updateBuiltInFlow(realmImport, flowToImport, existingFlow); - } - } - } - - private void updateBuiltInFlow( - RealmImport realmImport, - AuthenticationFlowRepresentation topLevelFlowToImport, - AuthenticationFlowRepresentation existingAuthenticationFlow - ) { - if (!existingAuthenticationFlow.isBuiltIn()) { - throw new InvalidImportException(String.format( - "Unable to update flow '%s' in realm '%s': Change built-in flag is not possible", - topLevelFlowToImport.getAlias(), realmImport.getRealm() - )); - } - AuthenticationFlowRepresentation patchedAuthenticationFlow = CloneUtil.patch( - existingAuthenticationFlow, topLevelFlowToImport, "id" - ); - - authenticationFlowRepository.update(realmImport.getRealm(), patchedAuthenticationFlow); - - executionFlowsImportService.updateExecutionFlows(realmImport, topLevelFlowToImport); - } - - /** - * Deletes the top-level flow and all its executions and recreates them. - */ - private void recreateTopLevelFlow( - RealmImport realmImport, - AuthenticationFlowRepresentation topLevelFlowToImport, - AuthenticationFlowRepresentation existingAuthenticationFlow - ) { - AuthenticationFlowRepresentation patchedAuthenticationFlow = CloneUtil.patch( - existingAuthenticationFlow, topLevelFlowToImport, "id" - ); - - if (existingAuthenticationFlow.isBuiltIn()) { - throw new InvalidImportException(String.format( - "Unable to recreate flow '%s' in realm '%s': Deletion or creation of built-in flows is not possible", - patchedAuthenticationFlow.getAlias(), realmImport.getRealm() - )); - } - - UsedAuthenticationFlowWorkaroundFactory.UsedAuthenticationFlowWorkaround workaround = workaroundFactory.buildFor(realmImport); - workaround.disableTopLevelFlowIfNeeded(topLevelFlowToImport.getAlias()); - - authenticatorConfigImportService.deleteAuthenticationConfigs(realmImport, patchedAuthenticationFlow); - authenticationFlowRepository.delete(realmImport.getRealm(), patchedAuthenticationFlow.getId()); - authenticationFlowRepository.createTopLevel(realmImport.getRealm(), patchedAuthenticationFlow); - - AuthenticationFlowRepresentation createdTopLevelFlow = authenticationFlowRepository.getByAlias( - realmImport.getRealm(), topLevelFlowToImport.getAlias() - ); - executionFlowsImportService.createExecutionsAndExecutionFlows(realmImport, topLevelFlowToImport, createdTopLevelFlow); - - workaround.resetFlowIfNeeded(); - } - - private void deleteTopLevelFlowsMissingInImport( - RealmImport realmImport, - List importedTopLevelFlows - ) { - String realmName = realmImport.getRealm(); - List existingTopLevelFlows = authenticationFlowRepository.getTopLevelFlows(realmName) - .stream().filter(flow -> !flow.isBuiltIn()).toList(); - - Set topLevelFlowsToImportAliases = importedTopLevelFlows.stream() - .map(AuthenticationFlowRepresentation::getAlias) - .collect(Collectors.toSet()); - - for (AuthenticationFlowRepresentation existingTopLevelFlow : existingTopLevelFlows) { - if (topLevelFlowsToImportAliases.contains(existingTopLevelFlow.getAlias())) continue; - - logger.debug("Delete authentication flow: {}", existingTopLevelFlow.getAlias()); - authenticationFlowRepository.delete(realmName, existingTopLevelFlow.getId()); - } - } -} 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 7cb1d3660..fad4eed42 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java @@ -59,7 +59,6 @@ public class RealmImportService { "defaultOptionalClientScopes", "clientProfiles", "clientPolicies", - "firstBrokerLoginFlow", }; private static final Logger logger = LoggerFactory.getLogger(RealmImportService.class); diff --git a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java index 62eea8059..fb8e808f2 100644 --- a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java +++ b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java @@ -43,12 +43,10 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings({"java:S5961", "java:S5976", "deprecation"}) class ImportAuthenticationFlowsIT extends AbstractImportIT { private static final String REALM_NAME = "realmWithFlow"; - private static final String DEFAULT_FLOW_REALM_NAME = "realmWithDefaultFlow"; @Autowired private IdentityProviderRepository identityProviderRepository; @@ -1217,35 +1215,6 @@ void shouldChangeSubFlowOfFirstBrokerLoginFlow() throws IOException { assertThat(flow.getAuthenticationExecutions().get(1).getRequirement(), is("DISABLED")); } - @Test - void shouldSetCustomFirstBrokerLoginFlowAsDefaultFlow() throws IOException { - assumeTrue(VersionUtil.ge(KEYCLOAK_VERSION,"24")); // was introduced with KC 24 - - doImport("init_custom_default_first-broker-login-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(DEFAULT_FLOW_REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(DEFAULT_FLOW_REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - assertThat(realm.getFirstBrokerLoginFlow(), is("my auth flow")); - } - - @Test - void shouldUpdateCustomFirstBrokerLoginFlowWhenSetAsDefault() throws IOException { - assumeTrue(VersionUtil.ge(KEYCLOAK_VERSION,"24")); // was introduced with KC 24 - - doImport("init_custom_default_first-broker-login-flow.json"); - doImport("updated_custom_default_first-broker-login-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(DEFAULT_FLOW_REALM_NAME).partialExport(true, true); - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); - - assertThat(realm.getRealm(), is(DEFAULT_FLOW_REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - assertThat(realm.getFirstBrokerLoginFlow(), is("my auth flow")); - assertThat(flow.getAuthenticationExecutions().get(0).getAuthenticator(), is("idp-auto-link")); - } - private List getExecutionFromFlow(AuthenticationFlowRepresentation flow, String executionAuthenticator) { List executions = flow.getAuthenticationExecutions(); diff --git a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy deleted file mode 100644 index 2450d513f..000000000 --- a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java.legacy +++ /dev/null @@ -1,1237 +0,0 @@ -/*- - * ---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; - -import de.adorsys.keycloak.config.AbstractImportIT; -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.util.VersionUtil; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; -import org.keycloak.representations.idm.*; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static de.adorsys.keycloak.config.test.util.KeycloakRepository.getAuthenticatorConfig; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNull.nullValue; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@SuppressWarnings({"java:S5961", "java:S5976", "deprecation"}) -class ImportAuthenticationFlowsIT extends AbstractImportIT { - private static final String REALM_NAME = "realmWithFlow"; - - ImportAuthenticationFlowsIT() { - this.resourcePath = "import-files/auth-flows"; - } - - @Test - @Order(0) - void shouldCreateRealmWithFlows() throws IOException { - doImport("00_create_realm_with_flows.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executions = flow.getAuthenticationExecutions(); - assertThat(executions, hasSize(1)); - - List execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(1) - void shouldAddExecutionToFlow() throws IOException { - doImport("01_update_realm__add_execution_to_flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executions = flow.getAuthenticationExecutions(); - assertThat(executions, hasSize(2)); - - List execution; - execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(flow, "http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(2) - void shouldChangeExecutionRequirement() throws IOException { - doImport("02_update_realm__change_execution_requirement.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executions = flow.getAuthenticationExecutions(); - assertThat(executions, hasSize(2)); - - List execution; - execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(flow, "http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(3) - void shouldChangeExecutionPriorities() throws IOException { - doImport("03_update_realm__change_execution_priorities.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executions = flow.getAuthenticationExecutions(); - assertThat(executions, hasSize(2)); - - List execution; - execution = getExecutionFromFlow(flow, "docker-http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("docker-http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(flow, "http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(4) - void shouldAddFlowWithExecutionFlow() throws IOException { - doImport("04_update_realm__add_flow_with_execution_flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); - assertThat(flow.getDescription(), is("My registration flow")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executionFlows = flow.getAuthenticationExecutions(); - assertThat(executionFlows, hasSize(1)); - - List execution; - execution = getExecutionFromFlow(flow, "registration-page-form"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(true)); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); - List subFlowExecutions = subFlow.getAuthenticationExecutions(); - assertThat(subFlowExecutions, hasSize(2)); - - execution = getExecutionFromFlow(subFlow, "registration-user-creation"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(subFlow, "registration-password-action"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(5) - void shouldFailWhenTryAddFlowWithDefectiveExecutionFlow() throws IOException { - RealmImport foundImport = getFirstImport("05_try_to_update_realm__add_flow_with_defective_execution_flow.json"); - - InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Execution property authenticator 'registration-page-form' can be only set if the sub-flow 'my registration form' type is 'form-flow'.")); - } - - @Test - @Order(6) - void shouldChangeFlowRequirementWithExecutionFlow() throws IOException { - doImport("10_update_realm__change_requirement_flow_with_execution_flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); - assertThat(flow.getDescription(), is("My registration flow")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executionFlows = flow.getAuthenticationExecutions(); - assertThat(executionFlows, hasSize(1)); - - List execution; - execution = getExecutionFromFlow(flow, "registration-page-form"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(true)); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); - - List subFlowExecutions = subFlow.getAuthenticationExecutions(); - assertThat(subFlowExecutions, hasSize(2)); - - execution = getExecutionFromFlow(subFlow, "registration-user-creation"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(subFlow, "registration-password-action"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(7) - void shouldFailWhenTryToUpdateDefectiveFlowRequirementWithExecutionFlow() throws IOException { - RealmImport foundImport = getFirstImport("06_try_to_update_realm__change_requirement_in_defective_flow_with_execution_flow.json"); - - InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), matchesPattern("Execution property authenticator 'registration-page-form' can be only set if the sub-flow 'my registration form' type is 'form-flow'.")); - } - - @Test - @Order(8) - void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithNotExistingExecution() throws IOException { - RealmImport foundImport = getFirstImport("07_try_to_update_realm__change_requirement_flow_with_execution_flow_with_not_existing_execution.json"); - - ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), matchesPattern("Cannot create execution 'not-existing-registration-user-creation' for non-top-level-flow 'my registration form' in realm 'realmWithFlow': .*")); - } - - @Test - @Order(9) - void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithDefectiveExecution() throws IOException { - RealmImport foundImport = getFirstImport("08_try_to_update_realm__change_requirement_flow_with_execution_flow_with_defective_execution.json"); - - ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), matchesPattern("Cannot update execution-flow 'registration-user-creation' for flow 'my registration form' in realm 'realmWithFlow': .*")); - } - - @Test - @Order(10) - void shouldFailWhenTryToUpdateFlowRequirementWithDefectiveExecutionFlow() throws IOException { - RealmImport foundImport = getFirstImport("09_try_to_update_realm__change_requirement_flow_with_defective_execution_flow.json"); - - ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Cannot create execution-flow 'docker-http-basic-authenticator' for top-level-flow 'my auth flow' in realm 'realmWithFlow'")); - } - - @Test - @Order(11) - void shouldChangeFlowPriorityWithExecutionFlow() throws IOException { - doImport("11_update_realm__change_priority_flow_with_execution_flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); - assertThat(flow.getDescription(), is("My registration flow")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executionFlows = flow.getAuthenticationExecutions(); - assertThat(executionFlows, hasSize(1)); - - List execution; - execution = getExecutionFromFlow(flow, "registration-page-form"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(true)); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); - - List subFlowExecutions = subFlow.getAuthenticationExecutions(); - assertThat(subFlowExecutions, hasSize(2)); - - execution = getExecutionFromFlow(subFlow, "registration-user-creation"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(subFlow, "registration-password-action"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(12) - void shouldSetRegistrationFlow() throws IOException { - doImport("12_update_realm__set_registration_flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getRegistrationFlow(), is("my registration")); - } - - @Test - @Order(13) - void shouldChangeRegistrationFlow() throws IOException { - doImport("13_update_realm__change_registration_flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getRegistrationFlow(), is("my registration")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); - assertThat(flow.getDescription(), is("My changed registration flow")); - } - - @Test - @Order(14) - void shouldAddAndSetResetCredentialsFlow() throws IOException { - doImport("14_update_realm__add_and_set_custom_reset-credentials-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getResetCredentialsFlow(), is("my reset credentials")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my reset credentials"); - assertThat(flow.getDescription(), is("My reset credentials for a user if they forgot their password or something")); - } - - @Test - @Order(15) - void shouldChangeResetCredentialsFlow() throws IOException { - doImport("15_update_realm__change_custom_reset-credentials-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getResetCredentialsFlow(), is("my reset credentials")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my reset credentials"); - assertThat(flow.getDescription(), is("My changed reset credentials for a user if they forgot their password or something")); - } - - @Test - @Order(16) - void shouldAddAndSetBrowserFlow() throws IOException { - doImport("16_update_realm__add_and_set_custom_browser-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getBrowserFlow(), is("my browser")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my browser"); - assertThat(flow.getDescription(), is("My browser based authentication")); - } - - @Test - @Order(17) - void shouldChangeBrowserFlow() throws IOException { - doImport("17.1_update_realm__change_custom_browser-flow.json"); - - assertThatBrowserFlowIsUpdated(4); - - doImport("17.2_update_realm__change_custom_browser-flow_with_multiple_subflow.json"); - - AuthenticationFlowRepresentation flow = assertThatBrowserFlowIsUpdated(5); - - AuthenticationExecutionExportRepresentation myForms2 = getExecutionFlowFromFlow(flow, "my forms 2"); - assertThat(myForms2, notNullValue()); - assertThat(myForms2.getRequirement(), is("ALTERNATIVE")); - assertThat(myForms2.isUserSetupAllowed(), is(false)); - assertThat(myForms2.isAutheticatorFlow(), is(true)); - - if (VersionUtil.ge(KEYCLOAK_VERSION, "25")) { - assertThat(myForms2.getPriority(), is(27)); - } else { - assertThat(myForms2.getPriority(), is(4)); - } - } - - AuthenticationFlowRepresentation assertThatBrowserFlowIsUpdated(int expectedNumberOfExecutionsInFlow) { - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getBrowserFlow(), is("my browser")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my browser"); - assertThat(flow.getDescription(), is("My changed browser based authentication")); - - assertThat(flow.getAuthenticationExecutions().size(), is(expectedNumberOfExecutionsInFlow)); - - AuthenticationExecutionExportRepresentation myForms = getExecutionFlowFromFlow(flow, "my forms"); - assertThat(myForms, notNullValue()); - assertThat(myForms.getRequirement(), is("ALTERNATIVE")); - assertThat(myForms.isUserSetupAllowed(), is(false)); - assertThat(myForms.isAutheticatorFlow(), is(true)); - - if (VersionUtil.ge(KEYCLOAK_VERSION, "25")) { - assertThat(myForms.getPriority(), is(26)); - } else { - assertThat(myForms.getPriority(), is(3)); - } - - return flow; - } - - @Test - @Order(18) - void shouldAddAndSetDirectGrantFlow() throws IOException { - doImport("18_update_realm__add_and_set_custom_direct-grant-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getDirectGrantFlow(), is("my direct grant")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my direct grant"); - assertThat(flow.getDescription(), is("My OpenID Connect Resource Owner Grant")); - } - - @Test - @Order(19) - void shouldChangeDirectGrantFlow() throws IOException { - doImport("19_update_realm__change_custom_direct-grant-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getDirectGrantFlow(), is("my direct grant")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my direct grant"); - assertThat(flow.getDescription(), is("My changed OpenID Connect Resource Owner Grant")); - } - - @Test - @Order(20) - void shouldAddAndSetClientAuthenticationFlow() throws IOException { - doImport("20_update_realm__add_and_set_custom_client-authentication-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getClientAuthenticationFlow(), is("my clients")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my clients"); - assertThat(flow.getDescription(), is("My Base authentication for clients")); - } - - @Test - @Order(21) - void shouldChangeClientAuthenticationFlow() throws IOException { - doImport("21_update_realm__change_custom_client-authentication-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getClientAuthenticationFlow(), is("my clients")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my clients"); - assertThat(flow.getDescription(), is("My changed Base authentication for clients")); - } - - @Test - @Order(22) - void shouldAddAndSetDockerAuthenticationFlow() throws IOException { - doImport("22_update_realm__add_and_set_custom_docker-authentication-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getDockerAuthenticationFlow(), is("my docker auth")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my docker auth"); - assertThat(flow.getDescription(), is("My Used by Docker clients to authenticate against the IDP")); - } - - @Test - @Order(23) - void shouldChangeDockerAuthenticationFlow() throws IOException { - doImport("23_update_realm__change_custom_docker-authentication-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getDockerAuthenticationFlow(), is("my docker auth")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my docker auth"); - assertThat(flow.getDescription(), is("My changed Used by Docker clients to authenticate against the IDP")); - } - - @Test - @Order(24) - void shouldAddTopLevelFlowWithExecutionFlow() throws IOException { - doImport("24_update_realm__add-top-level-flow-with-execution-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow with execution-flows"); - assertThat(flow.getDescription(), is("My authentication flow with authentication executions")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my execution-flow"); - - List subFlowExecutions = subFlow.getAuthenticationExecutions(); - assertThat(subFlowExecutions, hasSize(2)); - - List execution = getExecutionFromFlow(subFlow, "auth-username-password-form"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("auth-username-password-form")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(subFlow, "auth-otp-form"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("auth-otp-form")); - assertThat(execution.get(0).getRequirement(), is("CONDITIONAL")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(25) - void shouldUpdateTopLevelFlowWithPseudoId() throws IOException { - doImport("25_update_realm__update-top-level-flow-with-pseudo-id.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); - } - - @Test - @Order(26) - void shouldUpdateSubFlowWithPseudoId() throws IOException { - doImport("26_update_realm__update-non-top-level-flow-with-pseudo-id.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); - assertThat(subFlow.getDescription(), is("My registration form with pseudo-id")); - } - - @Test - @Order(27) - @DisabledIfSystemProperty(named = "keycloak.version", matches = "import.files.locations*", disabledReason = "https://github.com/keycloak/keycloak/issues/10176") - void shouldNotUpdateSubFlowWithPseudoId() throws IOException { - RealmImport foundImport = getFirstImport("27_update_realm__try-to-update-non-top-level-flow-with-pseudo-id.json"); - - ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), matchesPattern("Cannot create execution-flow 'my registration form' for top-level-flow 'my registration' in realm 'realmWithFlow': .*")); - } - - @Test - @Order(28) - void shouldUpdateSubFlowWithPseudoIdAndReUseTempFlow() throws IOException { - doImport("28_update_realm__update-non-top-level-flow-with-pseudo-id.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(realm.getRegistrationFlow(), is("my registration")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my registration"); - assertThat(flow.getDescription(), is("changed registration flow")); - - AuthenticationFlowRepresentation tempFlow = getAuthenticationFlow(realm, "TEMPORARY_CREATED_AUTH_FLOW"); - assertThat(tempFlow, nullValue()); - } - - @Test - @Order(29) - @DisabledIfSystemProperty(named = "keycloak.version", matches = "import.files.locations*", disabledReason = "https://github.com/keycloak/keycloak/issues/10176") - void shouldNotUpdateInvalidTopLevelFlow() throws IOException { - RealmImport foundImport = getFirstImport("29_update_realm__try-to-update-invalid-top-level-flow.json"); - - ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), matchesPattern("Cannot create top-level-flow 'my auth flow' in realm 'realmWithFlow': .*")); - } - - @Test - @Order(30) - void shouldCreateMultipleExecutionsWithSameAuthenticator() throws IOException { - doImport("30_update_realm__add_multiple_executions_with_same_authenticator.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "with-two-ids"); - assertThat(flow.getDescription(), is("my browser based authentication")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - - List execution; - execution = getExecutionFromFlow(flow, "identity-provider-redirector"); - assertThat(execution, hasSize(2)); - - List executionsId1 = execution.stream() - .filter((config) -> config.getAuthenticatorConfig() != null) - .filter((config) -> config.getAuthenticatorConfig().equals("id1")) - .toList(); - - assertThat(executionsId1, hasSize(1)); - assertThat(executionsId1.get(0).getAuthenticator(), is("identity-provider-redirector")); - assertThat(executionsId1.get(0).getAuthenticatorConfig(), is("id1")); - assertThat(executionsId1.get(0).getRequirement(), is("ALTERNATIVE")); - - List executionsId2 = execution.stream() - .filter((config) -> config.getAuthenticatorConfig() != null) - .filter((config) -> config.getAuthenticatorConfig().equals("id2")) - .toList(); - - assertThat(executionsId2, hasSize(1)); - assertThat(executionsId2.get(0).getAuthenticator(), is("identity-provider-redirector")); - assertThat(executionsId2.get(0).getAuthenticatorConfig(), is("id2")); - assertThat(executionsId2.get(0).getRequirement(), is("ALTERNATIVE")); - - assertThat(executionsId2.get(0).getPriority(), greaterThan(executionsId1.get(0).getPriority())); - - List authConfig; - authConfig = getAuthenticatorConfig(realm, "id1"); - assertThat(authConfig, hasSize(1)); - assertThat(authConfig.get(0).getAlias(), is("id1")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); - - authConfig = getAuthenticatorConfig(realm, "id2"); - assertThat(authConfig, hasSize(1)); - assertThat(authConfig.get(0).getAlias(), is("id2")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); - } - - @Test - @Order(31) - void shouldUpdateMultipleExecutionsWithSameAuthenticator() throws IOException { - doImport("31_update_realm__update_multiple_executions_with_same_authenticator.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "with-two-ids"); - assertThat(flow.getDescription(), is("my browser based authentication")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - - List execution; - execution = getExecutionFromFlow(flow, "identity-provider-redirector"); - assertThat(execution, hasSize(3)); - - List authConfig; - authConfig = getAuthenticatorConfig(realm, "id1"); - assertThat(authConfig, hasSize(2)); - assertThat(authConfig.get(0).getAlias(), is("id1")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); - assertThat(authConfig.get(1).getAlias(), is("id1")); - assertThat(authConfig.get(1).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); - - authConfig = getAuthenticatorConfig(realm, "id2"); - assertThat(authConfig, hasSize(1)); - assertThat(authConfig.get(0).getAlias(), is("id2")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); - } - - @Test - @Order(32) - void shouldUpdateMultipleExecutionsWithSameAuthenticatorWithConfig() throws IOException { - doImport("32_update_realm__update_multiple_executions_with_same_authenticator_with_config.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "with-two-ids"); - assertThat(flow.getDescription(), is("my browser based authentication")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - - List execution; - execution = getExecutionFromFlow(flow, "identity-provider-redirector"); - assertThat(execution, hasSize(3)); - - List authConfig; - authConfig = getAuthenticatorConfig(realm, "id1"); - assertThat(authConfig, hasSize(2)); - assertThat(authConfig.get(0).getAlias(), is("id1")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); - assertThat(authConfig.get(1).getAlias(), is("id1")); - assertThat(authConfig.get(1).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); - - authConfig = getAuthenticatorConfig(realm, "id2"); - assertThat(authConfig, hasSize(1)); - assertThat(authConfig.get(0).getAlias(), is("id2")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id4"))); - } - - @Test - @Order(33) - void shouldCreateMultipleSubFlowExecutionsWithSameAuthenticator() throws IOException { - doImport("33_update_realm__add_multiple_subflow_executions_with_same_authenticator.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation topLevelFlow = getAuthenticationFlow(realm, "my top level auth flow"); - assertThat(topLevelFlow.isBuiltIn(), is(false)); - assertThat(topLevelFlow.isTopLevel(), is(true)); - assertThat(topLevelFlow.getAuthenticationExecutions().size(), is(1)); - assertThat(topLevelFlow.getAuthenticationExecutions().get(0).getFlowAlias(), is("my sub auth flow")); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my sub auth flow"); - assertThat(subFlow.isBuiltIn(), is(false)); - assertThat(subFlow.isTopLevel(), is(false)); - assertThat(subFlow.getAuthenticationExecutions().size(), is(3)); - - List execution; - execution = getExecutionFromFlow(subFlow, "identity-provider-redirector"); - assertThat(execution, hasSize(2)); - - List executionsId1 = execution.stream() - .filter((config) -> config.getAuthenticatorConfig() != null) - .filter((config) -> config.getAuthenticatorConfig().equals("config-1")) - .collect(Collectors.toList()); - - assertThat(executionsId1, hasSize(1)); - assertThat(executionsId1.get(0).getAuthenticator(), is("identity-provider-redirector")); - assertThat(executionsId1.get(0).getAuthenticatorConfig(), is("config-1")); - assertThat(executionsId1.get(0).getRequirement(), is("ALTERNATIVE")); - - List executionsId2 = execution.stream() - .filter((config) -> config.getAuthenticatorConfig() != null) - .filter((config) -> config.getAuthenticatorConfig().equals("config-2")) - .collect(Collectors.toList()); - - assertThat(executionsId2, hasSize(1)); - assertThat(executionsId2.get(0).getAuthenticator(), is("identity-provider-redirector")); - assertThat(executionsId2.get(0).getAuthenticatorConfig(), is("config-2")); - assertThat(executionsId2.get(0).getRequirement(), is("ALTERNATIVE")); - - assertThat(executionsId2.get(0).getPriority(), greaterThan(executionsId1.get(0).getPriority())); - - List authConfig; - authConfig = getAuthenticatorConfig(realm, "config-1"); - assertThat(authConfig, hasSize(1)); - assertThat(authConfig.get(0).getAlias(), is("config-1")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id1"))); - - authConfig = getAuthenticatorConfig(realm, "config-2"); - assertThat(authConfig, hasSize(1)); - assertThat(authConfig.get(0).getAlias(), is("config-2")); - assertThat(authConfig.get(0).getConfig(), hasEntry(is("defaultProvider"), is("id2"))); - } - - @Test - @Order(40) - void shouldFailWhenTryingToUpdateBuiltInFlow() throws IOException { - RealmImport foundImport = getFirstImport("40_update_realm__try-to-update-built-in-flow.json"); - - InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Unable to update flow 'my auth flow with execution-flows' in realm 'realmWithFlow': Change built-in flag is not possible")); - } - - @Test - @Order(41) - void shouldFailWhenTryingToUpdateWithNonExistingFlow() throws IOException { - RealmImport foundImport = getFirstImport("41_update_realm__try-to-update-with-non-existing-flow.json"); - - ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Non-toplevel flow not found: non existing sub flow")); - } - - @Test - @Order(42) - void shouldUpdateTopLevelBuiltinFLow() throws IOException { - doImport("42_update_realm__update_builtin-top-level-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "saml ecp"); - assertThat(flow.getDescription(), is("SAML ECP Profile Authentication Flow")); - assertThat(flow.isBuiltIn(), is(true)); - assertThat(flow.isTopLevel(), is(true)); - - List execution = getExecutionFromFlow(flow, "http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("CONDITIONAL")); - assertThat(execution.get(0).getPriority(), is(10)); - assertThat(execution.get(0).isUserSetupAllowed(), is(false)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(43) - void shouldUpdateSubBuiltinFLow() throws IOException { - doImport("43_update_realm__update_builtin-non-top-level-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "registration form"); - assertThat(flow.getDescription(), is("updated registration form")); - assertThat(flow.isBuiltIn(), is(true)); - assertThat(flow.isTopLevel(), is(false)); - - List execution = getExecutionFromFlow(flow, "registration-recaptcha-action"); - assertThat(execution.get(0).getAuthenticator(), is("registration-recaptcha-action")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(60)); - assertThat(execution.get(0).isUserSetupAllowed(), is(false)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(44) - void shouldNotUpdateFlowWithBuiltInFalse() throws IOException { - RealmImport foundImport = getFirstImport("44_update_realm__try-to-update-flow-set-builtin-false.json"); - - InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Unable to recreate flow 'saml ecp' in realm 'realmWithFlow': Deletion or creation of built-in flows is not possible")); - } - - @Test - @Order(45) - void shouldNotUpdateFlowWithBuiltInTrue() throws IOException { - RealmImport foundImport = getFirstImport("45_update_realm__try-to-update-flow-set-builtin-true.json"); - - InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Unable to update flow 'my auth flow' in realm 'realmWithFlow': Change built-in flag is not possible")); - } - - @Test - @Order(46) - @DisabledIfSystemProperty(named = "keycloak.version", matches = "17.0.0", disabledReason = "https://github.com/keycloak/keycloak/issues/10176") - void shouldNotCreateBuiltInFlow() throws IOException { - RealmImport foundImport = getFirstImport("46_update_realm__try-to-create-builtin-flow.json"); - - ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Cannot update top-level-flow 'saml ecp' in realm 'realmWithFlow'.")); - } - - @Test - @Order(47) - void shouldUpdateRealmUpdateBuiltInFlowWithPseudoId() throws IOException { - doImport("47_update_realm__update-builtin-flow-with-pseudo-id.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - } - - @Test - @Order(50) - void shouldRemoveSubFlow() throws IOException { - doImport("50_update_realm__update-remove-non-top-level-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow; - flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executions = flow.getAuthenticationExecutions(); - assertThat(executions, hasSize(1)); - - List execution; - execution = getExecutionFromFlow(flow, "http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - flow = getAuthenticationFlow(realm, "my registration"); - assertThat(flow.getDescription(), is("My registration flow")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executionFlows = flow.getAuthenticationExecutions(); - assertThat(executionFlows, hasSize(1)); - - execution = getExecutionFromFlow(flow, "registration-page-form"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(true)); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); - - List subFlowExecutions = subFlow.getAuthenticationExecutions(); - assertThat(subFlowExecutions, hasSize(2)); - - execution = getExecutionFromFlow(subFlow, "registration-password-action"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(subFlow, "registration-user-creation"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(51) - void shouldSkipRemoveTopLevelFlow() throws IOException { - doImport("51_update_realm__skip-remove-top-level-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow; - flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executions = flow.getAuthenticationExecutions(); - assertThat(executions, hasSize(1)); - - List execution = getExecutionFromFlow(flow, "http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - flow = getAuthenticationFlow(realm, "my registration"); - assertThat(flow.getDescription(), is("My registration flow")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executionFlows = flow.getAuthenticationExecutions(); - assertThat(executionFlows, hasSize(1)); - - execution = getExecutionFromFlow(flow, "registration-page-form"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-page-form")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(true)); - - AuthenticationFlowRepresentation subFlow = getAuthenticationFlow(realm, "my registration form"); - - List subFlowExecutions = subFlow.getAuthenticationExecutions(); - assertThat(subFlowExecutions, hasSize(2)); - - execution = getExecutionFromFlow(subFlow, "registration-password-action"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-password-action")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - execution = getExecutionFromFlow(subFlow, "registration-user-creation"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("registration-user-creation")); - assertThat(execution.get(0).getRequirement(), is("REQUIRED")); - assertThat(execution.get(0).getPriority(), is(1)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - } - - @Test - @Order(52) - void shouldRemoveTopLevelFlow() throws IOException { - doImport("52_update_realm__update-remove-top-level-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(flow.getDescription(), is("My auth flow for testing with pseudo-id")); - assertThat(flow.getProviderId(), is("basic-flow")); - assertThat(flow.isBuiltIn(), is(false)); - assertThat(flow.isTopLevel(), is(true)); - - List executions = flow.getAuthenticationExecutions(); - assertThat(executions, hasSize(1)); - - List execution = getExecutionFromFlow(flow, "http-basic-authenticator"); - assertThat(execution, hasSize(1)); - assertThat(execution.get(0).getAuthenticator(), is("http-basic-authenticator")); - assertThat(execution.get(0).getRequirement(), is("DISABLED")); - assertThat(execution.get(0).getPriority(), is(0)); - assertThat(execution.get(0).isAutheticatorFlow(), is(false)); - - AuthenticationFlowRepresentation deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration"); - - assertThat(deletedTopLevelFlow, is(nullValue())); - - deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration from"); - assertThat(deletedTopLevelFlow, is(nullValue())); - } - - @Test - @Order(53) - void shouldRemoveAllTopLevelFlow() throws IOException { - doImport("53_update_realm__update-remove-all-top-level-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - AuthenticationFlowRepresentation deletedTopLevelFlow; - deletedTopLevelFlow = getAuthenticationFlow(realm, "my auth flow"); - assertThat(deletedTopLevelFlow, is(nullValue())); - - deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration"); - assertThat(deletedTopLevelFlow, is(nullValue())); - - deletedTopLevelFlow = getAuthenticationFlow(realm, "my registration from"); - assertThat(deletedTopLevelFlow, is(nullValue())); - - List allTopLevelFlow = realm.getAuthenticationFlows() - .stream().filter(e -> !e.isBuiltIn()) - .toList(); - - assertThat(allTopLevelFlow, is(empty())); - } - - @Test - @Order(61) - void shouldAddAndSetFirstBrokerLoginFlowForIdentityProvider() throws IOException { - doImport("61_update_realm__add_and_set_custom_first-broker-login-flow_for_identity-provider.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - IdentityProviderRepresentation identityProviderRepresentation = realm.getIdentityProviders().stream() - .filter(idp -> Objects.equals(idp.getAlias(), "keycloak-oidc")).findFirst().orElse(null); - - assertThat(identityProviderRepresentation, is(not(nullValue()))); - assertThat(identityProviderRepresentation.getFirstBrokerLoginFlowAlias(), is("my-first-broker-login")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my-first-broker-login"); - assertThat(flow.getDescription(), is("custom first broker login")); - } - - @Test - @Order(62) - void shouldChangeFirstBrokerLoginFlowForIdentityProvider() throws IOException { - doImport("62_update_realm__change_custom_first-broker-login-flow_for_identity-provider.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - IdentityProviderRepresentation identityProviderRepresentation = realm.getIdentityProviders().stream() - .filter(idp -> Objects.equals(idp.getAlias(), "keycloak-oidc")).findFirst().orElse(null); - - assertThat(identityProviderRepresentation, is(not(nullValue()))); - assertThat(identityProviderRepresentation.getFirstBrokerLoginFlowAlias(), is("my-first-broker-login")); - - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my-first-broker-login"); - assertThat(flow.getDescription(), is("custom changed first broker login")); - } - - @Test - @Order(64) - void shouldNotUpdateFlowWithAuthenticatorOnBasicFlow() throws IOException { - RealmImport foundImport = getFirstImport("63_update-realm__try-to-set-authenticator-basic-flow.json"); - - InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport)); - - assertThat(thrown.getMessage(), is("Execution property authenticator 'registration-page-form' can be only set if the sub-flow 'JToken Conditional' type is 'form-flow'.")); - } - - @Test - void shouldChangeSubFlowOfFirstBrokerLoginFlow() throws IOException { - doImport("init_custom_first-broker-login-flow.json"); - doImport("updated_custom_first-broker-login-flow.json"); - - RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).partialExport(true, true); - AuthenticationFlowRepresentation flow = getAuthenticationFlow(realm, "my-first-broker-login-handle-existing-account"); - - assertThat(realm.getRealm(), is(REALM_NAME)); - assertThat(realm.isEnabled(), is(true)); - - assertThat(flow.getAuthenticationExecutions().get(1).getRequirement(), is("DISABLED")); - } - - private List getExecutionFromFlow(AuthenticationFlowRepresentation flow, String executionAuthenticator) { - List executions = flow.getAuthenticationExecutions(); - - return executions.stream() - .filter(e -> e.getAuthenticator().equals(executionAuthenticator)) - .toList(); - } - - private AuthenticationExecutionExportRepresentation getExecutionFlowFromFlow(AuthenticationFlowRepresentation flow, String subFlow) { - List executions = flow.getAuthenticationExecutions(); - - return executions.stream() - .filter(f -> f.getFlowAlias() != null && f.getFlowAlias().equals(subFlow)) - .findFirst() - .orElse(null); - } - - private AuthenticationFlowRepresentation getAuthenticationFlow(RealmRepresentation realm, String flowAlias) { - List authenticationFlows = realm.getAuthenticationFlows(); - return authenticationFlows.stream() - .filter(f -> f.getAlias().equals(flowAlias)) - .findFirst() - .orElse(null); - } -} diff --git a/src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json b/src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json deleted file mode 100644 index 48f02a814..000000000 --- a/src/test/resources/import-files/auth-flows/init_custom_default_first-broker-login-flow.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "enabled": true, - "realm": "realmWithDefaultFlow", - "firstBrokerLoginFlow": "my auth flow", - "authenticationFlows": [ - { - "alias": "my auth flow", - "description": "My auth flow for testing", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": false, - "authenticationExecutions": [ - { - "authenticator": "docker-http-basic-authenticator", - "requirement": "REQUIRED", - "priority": 0, - "userSetupAllowed": true, - "autheticatorFlow": false, - "authenticatorFlow": false - } - ] - } - ] -} diff --git a/src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json b/src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json deleted file mode 100644 index 6aa379b6a..000000000 --- a/src/test/resources/import-files/auth-flows/updated_custom_default_first-broker-login-flow.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "enabled": true, - "realm": "realmWithDefaultFlow", - "firstBrokerLoginFlow": "my auth flow", - "authenticationFlows": [ - { - "alias": "my auth flow", - "description": "My auth flow for testing", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": false, - "authenticationExecutions": [ - { - "authenticator": "idp-auto-link", - "requirement": "REQUIRED", - "priority": 0, - "userSetupAllowed": false, - "autheticatorFlow": false, - "authenticatorFlow": false - } - ] - } - ] -}