diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index dd410a0dc37..a003c12bfcd 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -929,7 +929,9 @@ public class ModelToRepresentation { AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation(); if (model.getAuthenticatorConfig() != null) { AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfig(realm, model.getAuthenticatorConfig()); - rep.setAuthenticatorConfig(config.getAlias()); + if (config != null) { + rep.setAuthenticatorConfig(config.getAlias()); + } } rep.setAuthenticator(model.getAuthenticator()); rep.setAuthenticatorFlow(model.isAuthenticatorFlow()); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index ec3317970b4..e76795f7436 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -655,6 +655,19 @@ public class AuthenticationManagementResource { return result; } + private String getAuthenticationConfig(String flowAlias, AuthenticationExecutionModel model) { + if (model.getAuthenticatorConfig() == null) { + return null; + } + AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfig(realm, model.getAuthenticatorConfig()); + if (config == null) { + logger.warnf("Authenticator configuration '%s' is missing for execution '%s' (%s) in flow '%s'", + model.getAuthenticatorConfig(), model.getId(), model.getAuthenticator(), flowAlias); + return null; + } + return config.getId(); + } + public void recurseExecutions(AuthenticationFlowModel flow, List result, int level) { AtomicInteger index = new AtomicInteger(0); realm.getAuthenticationExecutionsStream(flow.getId()).forEachOrdered(execution -> { @@ -674,7 +687,7 @@ public class AuthenticationManagementResource { rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.REQUIRED.name()); rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.DISABLED.name()); rep.setProviderId(execution.getAuthenticator()); - rep.setAuthenticationConfig(execution.getAuthenticatorConfig()); + rep.setAuthenticationConfig(getAuthenticationConfig(flow.getAlias(), execution)); } else if (AuthenticationFlow.CLIENT_FLOW.equals(flowRef.getProviderId())) { rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name()); rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.REQUIRED.name()); @@ -724,7 +737,7 @@ public class AuthenticationManagementResource { } rep.setProviderId(providerId); - rep.setAuthenticationConfig(execution.getAuthenticatorConfig()); + rep.setAuthenticationConfig(getAuthenticationConfig(flow.getAlias(), execution)); result.add(rep); } }); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java index a1e958222fa..8f471a91096 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java @@ -22,6 +22,9 @@ import org.junit.Test; import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation; @@ -42,12 +45,13 @@ import jakarta.ws.rs.BadRequestException; */ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { + private String flowId; private String executionId; @Before public void beforeConfigTest() { AuthenticationFlowRepresentation flowRep = newFlow("firstBrokerLogin2", "firstBrokerLogin2", "basic-flow", true, false); - createFlow(flowRep); + flowId = createFlow(flowRep); HashMap params = new HashMap<>(); params.put("provider", IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID); @@ -178,6 +182,36 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { Assert.assertTrue(description.getProperties().isEmpty()); } + @Test + public void testMissingConfig() { + AuthenticatorConfigRepresentation cfg = newConfig("foo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true"); + final String cfgId = createConfig(executionId, cfg); + final String realmId = testRealmId; + AuthenticatorConfigRepresentation cfgRep = authMgmtResource.getAuthenticatorConfig(cfgId); + Assert.assertNotNull(cfgRep); + + testingClient.server().run(session -> { + // emulating a broken config id, remove the config but do not remove the link in the authenticator + RealmModel realm = session.realms().getRealm(realmId); + AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(cfgId); + realm.removeAuthenticatorConfig(config); + }); + + // check the flow can be read and execution has no config + AuthenticationFlowRepresentation flow = authMgmtResource.getFlow(flowId); + AuthenticationExecutionExportRepresentation execExport = flow.getAuthenticationExecutions().stream() + .filter(ae -> IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID.equals(ae.getAuthenticator())) + .findAny() + .orElse(null); + Assert.assertNotNull(execExport); + Assert.assertNull(execExport.getAuthenticatorConfig()); + + // check the execution can be read with no configuration assigned + AuthenticationExecutionInfoRepresentation execInfo = findExecutionByProvider( + IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, authMgmtResource.getExecutions("firstBrokerLogin2")); + Assert.assertNull(execInfo.getAuthenticationConfig()); + } + private String createConfig(String executionId, AuthenticatorConfigRepresentation cfg) { Response resp = authMgmtResource.newExecutionConfig(executionId, cfg); Assert.assertEquals(201, resp.getStatus());