From 0cf018d147124c9c13ecbffebddd42c7056d76c6 Mon Sep 17 00:00:00 2001 From: Sumesh Kariyil Date: Thu, 22 Aug 2024 10:21:15 -0700 Subject: [PATCH] Syncing up changes from ee for IDM SPIs --- .gitignore | 3 +- forms-flow-idm/keycloak/Dockerfile | 12 +-- .../keycloak/idp-selector/.gitignore | 1 + forms-flow-idm/keycloak/idp-selector/pom.xml | 70 ++++++++++++ .../ConfigurableIdpAuthenticator.java | 100 ++++++++++++++++++ .../ConfigurableIdpAuthenticatorFactory.java | 87 +++++++++++++++ ...ycloak.authentication.AuthenticatorFactory | 1 + .../keycloak/themes/formsflow/login/login.ftl | 55 +++++++--- 8 files changed, 308 insertions(+), 21 deletions(-) create mode 100644 forms-flow-idm/keycloak/idp-selector/.gitignore create mode 100644 forms-flow-idm/keycloak/idp-selector/pom.xml create mode 100644 forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticator.java create mode 100644 forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticatorFactory.java create mode 100644 forms-flow-idm/keycloak/idp-selector/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory diff --git a/.gitignore b/.gitignore index 2080e2690d..d4ed11a621 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ jobs/sentiment-analysis/dbms/__pycache__ #Database postgres/ mongodb/ -forms-flow-web/scripts/node_modules \ No newline at end of file +forms-flow-web/scripts/node_modules +forms-flow-idm/keycloak/idp-selector/.settings/* diff --git a/forms-flow-idm/keycloak/Dockerfile b/forms-flow-idm/keycloak/Dockerfile index e1c4571eff..ef1b392671 100644 --- a/forms-flow-idm/keycloak/Dockerfile +++ b/forms-flow-idm/keycloak/Dockerfile @@ -1,17 +1,17 @@ -# FROM maven:3.8.6-jdk-11 AS builder +FROM maven:3.8.7-openjdk-18-slim AS builder -# WORKDIR /build +WORKDIR /build -# COPY idp-selector/pom.xml idp-selector/ -# COPY idp-selector/src idp-selector/src/ +COPY idp-selector/pom.xml idp-selector/ +COPY idp-selector/src idp-selector/src/ -# RUN mvn -f idp-selector/pom.xml clean package +RUN mvn -f idp-selector/pom.xml clean package FROM alpine:latest WORKDIR /custom -# COPY --from=builder /build/idp-selector/target/*.jar /custom/providers/ +COPY --from=builder /build/idp-selector/target/*.jar /custom/providers/ COPY ./themes /custom/themes COPY ./imports /custom/imports COPY ./start-keycloak.sh /custom/start-keycloak.sh diff --git a/forms-flow-idm/keycloak/idp-selector/.gitignore b/forms-flow-idm/keycloak/idp-selector/.gitignore new file mode 100644 index 0000000000..b83d22266a --- /dev/null +++ b/forms-flow-idm/keycloak/idp-selector/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/forms-flow-idm/keycloak/idp-selector/pom.xml b/forms-flow-idm/keycloak/idp-selector/pom.xml new file mode 100644 index 0000000000..eff7ea8383 --- /dev/null +++ b/forms-flow-idm/keycloak/idp-selector/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + formsflow.ai + idp-selector + jar + 1.0.0 + idp-selector + http://maven.apache.org + + 23.0.7 + + + + org.keycloak + keycloak-server-spi + ${keycloak.version} + + + org.keycloak + keycloak-server-spi-private + ${keycloak.version} + + + org.keycloak + keycloak-common + ${keycloak.version} + + + org.keycloak + keycloak-core + ${keycloak.version} + + + org.keycloak + keycloak-services + ${keycloak.version} + + + org.slf4j + slf4j-api + 1.7.32 + + + ch.qos.logback + logback-classic + 1.2.10 + + + junit + junit + 3.8.1 + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + + + + + diff --git a/forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticator.java b/forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticator.java new file mode 100644 index 0000000000..b8a2a01878 --- /dev/null +++ b/forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticator.java @@ -0,0 +1,100 @@ +package com.formsflow.idm.authenticator; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.Authenticator; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * IDP Authenticator used to selectively display Identity Providers based on the client scopes. + */ +public class ConfigurableIdpAuthenticator implements Authenticator { + + private static final Logger logger = LoggerFactory.getLogger(ConfigurableIdpAuthenticator.class); + + /** + * Set the identity providers based on the client scope + * 1 : Get the default client scopes for the client which initiated the authentication + * 2 : Find out the IDPs configured for the client using the scope which starts with idp_ + * 3 : Filter the provider models using the scoped idps and set ito the attribute + */ + @Override + public void authenticate(AuthenticationFlowContext context) { + AuthenticatorConfigModel config = context.getAuthenticatorConfig(); + logger.info("Inside ConfigurableIdpAuthenticator --> authenticate"); + + List idpScopes = new ArrayList(); + List identityProviders = new ArrayList(); + + logger.info("context.getAuthenticationSession().getClient().getAttributes() {}", + context.getAuthenticationSession().getClient().getClientScopes(true)); + + + for (String key : context.getAuthenticationSession().getClient().getClientScopes(true).keySet()) { + if (key.startsWith("idp_")) { + idpScopes.add(key.replaceFirst("idp_", "")); + } + } + + logger.info("Inside ConfigurableIdpAuthenticator --> idpScopes: {}", idpScopes); + + if (!idpScopes.isEmpty()) { + // Filter the IDPs based on the configuration + identityProviders = context.getRealm().getIdentityProvidersStream() + .filter(idp -> idpScopes.contains(idp.getAlias())).collect(Collectors.toList()); + + logger.info("Inside ConfigurableIdpAuthenticator --> identityProviders: {}", identityProviders); + + } + + for (IdentityProviderModel idpModel : identityProviders) { + logger.info("IdentityProviderModel: {}", idpModel); + logger.info("idpModel.getDisplayName: {}", idpModel.getDisplayName()); + logger.info("idpModel.getAlias: {}", idpModel.getAlias()); + logger.info("idpModel.getInternalId: {}", idpModel.getInternalId()); + logger.info("idpModel.getProviderId: {}", idpModel.getProviderId()); + } + context.form().setAttribute("numberOfIdps", identityProviders.size()); + context.challenge( + context.form().setAttribute("identityProviders", identityProviders).createLoginUsernamePassword()); + + } + + @Override + public void action(AuthenticationFlowContext context) { + String selectedIdp = context.getHttpRequest().getDecodedFormParameters().getFirst("selectedIdp"); + if (selectedIdp != null) { + context.getAuthenticationSession().setClientNote("selectedIdp", selectedIdp); + context.getAuthenticationSession().setAuthNote("selectedIdp", selectedIdp); + context.success(); + } + + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + } + + @Override + public void close() { + } +} diff --git a/forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticatorFactory.java b/forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticatorFactory.java new file mode 100644 index 0000000000..87084b4b21 --- /dev/null +++ b/forms-flow-idm/keycloak/idp-selector/src/main/java/com/formsflow/idm/authenticator/ConfigurableIdpAuthenticatorFactory.java @@ -0,0 +1,87 @@ +package com.formsflow.idm.authenticator; + +import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE; + +import java.util.Collections; +import java.util.List; + +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; + +public class ConfigurableIdpAuthenticatorFactory implements AuthenticatorFactory { + + public static final String ID = "configurable-idp-authenticator"; + + private static final Authenticator AUTHENTICATOR_INSTANCE = new ConfigurableIdpAuthenticator(); + + static final String IDP_LIST = "idpList"; + + @Override + public Authenticator create(KeycloakSession keycloakSession) { + return AUTHENTICATOR_INSTANCE; + } + + @Override + public String getDisplayType() { + return "Custom IDP Selector"; + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return new AuthenticationExecutionModel.Requirement[] { AuthenticationExecutionModel.Requirement.REQUIRED }; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + + @Override + public String getHelpText() { + return "formsflow.ai addon to limit the IDPs which can be used for the client application"; + } + + @Override + public List getConfigProperties() { + ProviderConfigProperty name = new ProviderConfigProperty(); + + name.setType(STRING_TYPE); + name.setName(IDP_LIST); + name.setLabel("Comma-separated list of IDP aliases to display"); + name.setHelpText("Comma-separated list of IDP aliases to display"); + + return Collections.singletonList(name); + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public void init(org.keycloak.Config.Scope scope) { + } + + @Override + public void postInit(KeycloakSessionFactory keycloakSessionFactory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return ID; + } + +} diff --git a/forms-flow-idm/keycloak/idp-selector/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/forms-flow-idm/keycloak/idp-selector/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory new file mode 100644 index 0000000000..27de0a9b21 --- /dev/null +++ b/forms-flow-idm/keycloak/idp-selector/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -0,0 +1 @@ +com.formsflow.idm.authenticator.ConfigurableIdpAuthenticatorFactory \ No newline at end of file diff --git a/forms-flow-idm/keycloak/themes/formsflow/login/login.ftl b/forms-flow-idm/keycloak/themes/formsflow/login/login.ftl index 8707854824..a2bf1f9e10 100644 --- a/forms-flow-idm/keycloak/themes/formsflow/login/login.ftl +++ b/forms-flow-idm/keycloak/themes/formsflow/login/login.ftl @@ -91,22 +91,49 @@ <#if realm.password && social?? && social.providers?has_content>

-

${msg("identity-provider-login-label")}

- + <#if numberOfIdps??> + <#if numberOfIdps gt 0> +

${msg("identity-provider-login-label")}

+ + <#else> + <#if social.providers??> +

${msg("identity-provider-login-label")}

+ +