diff --git a/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java b/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java index cc1bb25..a5117c9 100644 --- a/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java +++ b/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java @@ -609,9 +609,11 @@ void initializeAuthenticationFlow(AuthenticationManagementResource authMgmt, Str System.err.println("Failed to create flow; status code '" + response.getStatus() + "'"); System.err.println(response.readEntity(String.class)); } + } else { + updateFlowWithExecutions(authMgmt, authenticationFlowPg, authenticationFlow); } - updateFlowWithExecutions(authMgmt, authenticationFlowPg, authenticationFlow); + // Update identity provider redirector for (PropertyEntry authExecutionPe: authenticationFlowPg.getProperties()) { @@ -629,6 +631,9 @@ private void updateFlowWithExecutions(AuthenticationManagementResource authMgmt, PropertyGroup authenticationExecutionsPg = authenticationFlowPg.getPropertyGroup("authenticationExecutions"); JsonObject jsonObject = authenticationFlowPg.getJsonValue("authenticationExecutions").asJsonObject(); for (String entry : jsonObject.keySet()) { + + System.out.println("adding auth execution: " + entry); + PropertyGroup entryProps = authenticationExecutionsPg.getPropertyGroup(entry); HashMap executionParams = new HashMap(); @@ -654,40 +659,75 @@ private void updateFlowWithExecutions(AuthenticationManagementResource authMgmt, // TODO: see if we can get the display name from the authenticator provider_id somehow, instead of requiring it in our config String displayName = childEntry.getName(); PropertyGroup childEntryPg = childExecutions.getPropertyGroup(displayName); - String authenticator = childEntryPg.getStringProperty("authenticator"); - Boolean childIsFlow = childEntryPg.getBooleanProperty("authenticatorFlow", false); - if (childIsFlow) { - throw new UnsupportedOperationException("Nest subflows are not yet supported"); - } + configExecution(childEntryPg, authMgmt, entry, displayName, authenticationFlow); + } + } else { + configExecution(entryProps, authMgmt, authenticationFlow.getAlias(), entry, authenticationFlow); + } + } + } - HashMap childExecutionParams = new HashMap(); - childExecutionParams.put("provider", authenticator); - AuthenticationExecutionInfoRepresentation childExecution = getOrCreateExecution(authMgmt, entry, displayName, childIsFlow, childExecutionParams); + private void configExecution(PropertyGroup propGroup, AuthenticationManagementResource authMgmt, String entry, + String displayName, AuthenticationFlowRepresentation authenticationFlow) throws Exception { + String authenticator = propGroup.getStringProperty("authenticator"); - String configAlias = childEntryPg.getStringProperty("configAlias"); - JsonValue configJson = childEntryPg.getJsonValue("config"); - if (configJson != null) { - Map config = buildConfigMap(configJson, configAlias); + Boolean childIsFlow = propGroup.getBooleanProperty("authenticatorFlow", false); + if (childIsFlow) { + System.out.println("Adding nested flow: " + displayName); - AuthenticatorConfigRepresentation authenticatorConfig = getOrCreateAuthenticatorConfig(authMgmt, childExecution, configAlias, config); - authenticatorConfig.setConfig(config); - authMgmt.updateAuthenticatorConfig(authenticatorConfig.getId(), authenticatorConfig); + HashMap executionParams = new HashMap<>(); - childExecution.setAuthenticationConfig(configAlias); - } + // String alias = propGroup.getStringProperty("alias"); + String parentFlowAlias = entry; + String flowAlias = displayName; + String type = propGroup.getStringProperty("providerId"); + // String provider = propGroup.getStringProperty("provider"); + String description = propGroup.getStringProperty("description"); - childExecution.setRequirement(childEntryPg.getStringProperty("requirement")); - authMgmt.updateExecutions(authenticationFlow.getAlias(), childExecution); - } - } else { - executionParams.put("authenticator", entry); - getOrCreateExecution(authMgmt, authenticationFlow.getAlias(), entry, isFlow, executionParams); + executionParams.put("alias", flowAlias); + executionParams.put("type", type); + // executionParams.put("provider", "xx"); + executionParams.put("description", description); + + authMgmt.addExecutionFlow(parentFlowAlias, executionParams); + + // there doesn't seem to be a way to query for this, but the last added item + // should be the correct one + AuthenticationExecutionInfoRepresentation lastAdded = null; + for (AuthenticationExecutionInfoRepresentation flow : authMgmt.getExecutions(parentFlowAlias)) { + lastAdded = flow; + } + + // have to update the requirement separately, and also the flowAlias doesn't get + // set for some reason + lastAdded.setAlias(flowAlias); + lastAdded.setRequirement(propGroup.getStringProperty("requirement")); + authMgmt.updateExecutions(parentFlowAlias, lastAdded); - // TODO authenticatorConfig - executionParams.put("priority", Integer.toString(entryProps.getIntProperty("priority"))); + // now fetch the nested flow so we can recursively add the executions to it + authenticationFlow = authMgmt.getFlow(lastAdded.getFlowId()); + updateFlowWithExecutions(authMgmt, propGroup, authenticationFlow); + } else { + HashMap childExecutionParams = new HashMap<>(); + childExecutionParams.put("provider", authenticator); + AuthenticationExecutionInfoRepresentation childExecution = getOrCreateExecution(authMgmt, entry, displayName, + childIsFlow, childExecutionParams); + + String configAlias = propGroup.getStringProperty("configAlias"); + JsonValue configJson = propGroup.getJsonValue("config"); + if (configJson != null) { + Map config = buildConfigMap(configJson, configAlias); + + AuthenticatorConfigRepresentation authenticatorConfig = getOrCreateAuthenticatorConfig(authMgmt, + childExecution, configAlias, config); + authenticatorConfig.setConfig(config); + authMgmt.updateAuthenticatorConfig(authenticatorConfig.getId(), authenticatorConfig); + childExecution.setAuthenticationConfig(configAlias); } + childExecution.setRequirement(propGroup.getStringProperty("requirement")); + authMgmt.updateExecutions(authenticationFlow.getAlias(), childExecution); } } @@ -735,13 +775,22 @@ private Map buildConfigMap(JsonValue configJson, String configAl private AuthenticationExecutionInfoRepresentation getOrCreateExecution(AuthenticationManagementResource authMgmt, String flowAlias, String displayName, boolean isFlow, HashMap executionParams) { AuthenticationExecutionInfoRepresentation savedExecution = getExecutionByDisplayName(authMgmt, flowAlias, displayName); + + // System.out.println("savedExecution1: " + savedExecution); + if (savedExecution == null) { if (isFlow) { authMgmt.addExecutionFlow(flowAlias, executionParams); } else { + // System.out.println("calling addExecution for flowAlias: " + flowAlias); + // for (Map.Entry entry : executionParams.entrySet()) { + // System.out.println("addExecution param: " + entry.getKey() + " : " + + // entry.getValue()); + // } authMgmt.addExecution(flowAlias, executionParams); } savedExecution = getExecutionByDisplayName(authMgmt, flowAlias, displayName); + // System.out.println("savedExecution2: " + savedExecution); } if (savedExecution == null) { throw new RuntimeException("Unable to create execution '" + displayName + "'"); diff --git a/keycloak-extensions/src/test/resources/keycloak-config.json b/keycloak-extensions/src/test/resources/keycloak-config.json index ae859b8..923e6e0 100644 --- a/keycloak-extensions/src/test/resources/keycloak-config.json +++ b/keycloak-extensions/src/test/resources/keycloak-config.json @@ -64,7 +64,7 @@ "builtIn": false, "authenticationExecutions": { "SMART Login": { - "requirement": "ALTERNATIVE", + "requirement": "REQUIRED", "userSetupAllowed": false, "authenticatorFlow": true, "description": "Username, password, otp and other auth forms.", @@ -80,24 +80,34 @@ "audiences": "https://localhost:9443/fhir-server/api/v4##http://host.testcontainers.internal:9080/fhir-server/api/v4" } }, - "Username Password Form": { - "authenticator": "auth-username-password-form", - "requirement": "REQUIRED", - "priority": 20, - "authenticatorFlow": false - }, - "Patient Selection Authenticator": { - "authenticator": "auth-select-patient", - "requirement": "REQUIRED", - "priority": 30, - "authenticatorFlow": false, - "configAlias": "host.docker", - "config": { - "internalFhirUrl": "http://host.testcontainers.internal:${FHIR_PORT}/fhir-server/api/v4" - } - } + "Forms" : { + "description": "Forms", + "priority": 20, + "providerId": "basic-flow", + "builtIn": false, + "requirement": "REQUIRED", + "authenticatorFlow": true, + "authenticationExecutions": { + "Username Password Form": { + "authenticator": "auth-username-password-form", + "requirement": "REQUIRED", + "priority": 20, + "authenticatorFlow": false + } + } + } } - } + }, + "Patient Selection Authenticator": { + "authenticator": "auth-select-patient", + "requirement": "REQUIRED", + "priority": 30, + "authenticatorFlow": false, + "configAlias": "host.docker", + "config": { + "internalFhirUrl": "http://host.testcontainers.internal:${FHIR_PORT}/fhir-server/api/v4" + } + } } } },