Skip to content

Commit

Permalink
Merge pull request #2209 from sumesh-aot/idm-changes
Browse files Browse the repository at this point in the history
Syncing up changes from ee for IDM SPIs
  • Loading branch information
arun-s-aot authored Aug 23, 2024
2 parents 342d3b1 + 0cf018d commit 031a588
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ jobs/sentiment-analysis/dbms/__pycache__
#Database
postgres/
mongodb/
forms-flow-web/scripts/node_modules
forms-flow-web/scripts/node_modules
forms-flow-idm/keycloak/idp-selector/.settings/*
12 changes: 6 additions & 6 deletions forms-flow-idm/keycloak/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions forms-flow-idm/keycloak/idp-selector/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target/
70 changes: 70 additions & 0 deletions forms-flow-idm/keycloak/idp-selector/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>formsflow.ai</groupId>
<artifactId>idp-selector</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>idp-selector</name>
<url>http://maven.apache.org</url>
<properties>
<keycloak.version>23.0.7</keycloak.version>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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<String> idpScopes = new ArrayList<String>();
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();

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() {
}
}
Original file line number Diff line number Diff line change
@@ -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<ProviderConfigProperty> 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;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.formsflow.idm.authenticator.ConfigurableIdpAuthenticatorFactory
55 changes: 41 additions & 14 deletions forms-flow-idm/keycloak/themes/formsflow/login/login.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,49 @@
<#if realm.password && social?? && social.providers?has_content>
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
<hr/>
<h2>${msg("identity-provider-login-label")}</h2>

<#if numberOfIdps??>
<#if numberOfIdps gt 0>
<h2>${msg("identity-provider-login-label")}</h2>
</#if>
<#else>
<#if social.providers??>
<h2>${msg("identity-provider-login-label")}</h2>
</#if>
</#if>
<ul class="${properties.kcFormSocialAccountListClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountListGridClass!}</#if>">
<#list social.providers as p>
<li>
<a id="social-${p.alias}" class="${properties.kcFormSocialAccountListButtonClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountGridItem!}</#if>"
type="button" href="${p.loginUrl}">
<#if p.iconClasses?has_content>
<i class="${properties.kcCommonLogoIdP!} ${p.iconClasses!}" aria-hidden="true"></i>
<span class="${properties.kcFormSocialAccountNameClass!} kc-social-icon-text">${p.displayName!}</span>
<#else>
<span class="${properties.kcFormSocialAccountNameClass!}">${p.displayName!}</span>
<#-- Check if numberOfIdps is present -->
<#if numberOfIdps??>
<#list social.providers as sp>
<#list identityProviders as idp>
<#if sp.alias == idp.alias>
<a id="social-${sp.alias}" class="${properties.kcFormSocialAccountListButtonClass!} <#if identityProviders?size gt 3>${properties.kcFormSocialAccountGridItem!}</#if>"
type="button" href="${sp.loginUrl}">
<#if sp.iconClasses?has_content>
<i class="${properties.kcCommonLogoIdP!} ${sp.iconClasses!}" aria-hidden="true"></i>
<span class="${properties.kcFormSocialAccountNameClass!} kc-social-icon-text">${sp.displayName!}</span>
<#else>
<span class="${properties.kcFormSocialAccountNameClass!}">${sp.displayName!}</span>
</#if>
</a>
</#if>
</a>
</li>
</#list>
</#list>
</#list>
<#else>
<#list social.providers as p>
<li>
<a id="social-${p.alias}" class="${properties.kcFormSocialAccountListButtonClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountGridItem!}</#if>"
type="button" href="${p.loginUrl}">
<#if p.iconClasses?has_content>
<i class="${properties.kcCommonLogoIdP!} ${p.iconClasses!}" aria-hidden="true"></i>
<span class="${properties.kcFormSocialAccountNameClass!} kc-social-icon-text">${p.displayName!}</span>
<#else>
<span class="${properties.kcFormSocialAccountNameClass!}">${p.displayName!}</span>
</#if>
</a>
</li>
</#list>
</#if>

</ul>
</div>
</#if>
Expand Down

0 comments on commit 031a588

Please sign in to comment.