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 52fabee73..57bdb1a0c 100644
--- a/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java
+++ b/src/main/java/de/adorsys/keycloak/config/service/AuthenticationFlowsImportService.java
@@ -110,6 +110,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 14b257063..46843101a 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 2450d513f..2c9ed11ef 100644
--- a/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java
+++ b/src/test/java/de/adorsys/keycloak/config/service/ImportAuthenticationFlowsIT.java
@@ -41,10 +41,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";
ImportAuthenticationFlowsIT() {
this.resourcePath = "import-files/auth-flows";
@@ -1210,6 +1212,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/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
+ }
+ ]
+ }
+ ]
+}