Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.phasetwo.service.auth.idp.HomeIdpDiscoverer Maven / Gradle / Ivy
//package de.sventorben.keycloak.authentication.hidpd;
package io.phasetwo.service.auth.idp;
import io.phasetwo.service.model.OrganizationProvider;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
final class HomeIdpDiscoverer {
private static final Logger LOG = Logger.getLogger(HomeIdpDiscoverer.class);
private final DomainExtractor domainExtractor;
private final AuthenticationFlowContext context;
HomeIdpDiscoverer(AuthenticationFlowContext context) {
this(new DomainExtractor(new HomeIdpDiscoveryConfig(context.getAuthenticatorConfig())), context);
}
private HomeIdpDiscoverer(DomainExtractor domainExtractor, AuthenticationFlowContext context) {
this.domainExtractor = domainExtractor;
this.context = context;
}
public List discoverForUser(String username) {
String realmName = context.getRealm().getName();
AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig();
LOG.tracef("Trying to discover home IdP for username '%s' in realm '%s' with authenticator config '%s'",
username, realmName, authenticatorConfig == null ? "" : authenticatorConfig.getAlias());
List homeIdps = new ArrayList<>();
final Optional emailDomain;
UserModel user = context.getUser();
if (user == null) {
LOG.tracef("No user found in AuthenticationFlowContext. Extracting domain from provided username '%s'.",
username);
emailDomain = domainExtractor.extractFrom(username);
} else {
LOG.tracef("User found in AuthenticationFlowContext. Extracting domain from stored user '%s'.",
user.getId());
emailDomain = domainExtractor.extractFrom(user);
}
HomeIdpDiscoveryConfig config = new HomeIdpDiscoveryConfig(authenticatorConfig);
if (config.requireVerifiedEmail()
&& "email".equalsIgnoreCase(config.userAttribute())
&& !user.isEmailVerified()) {
LOG.infof("Email of user %s not verified. Skipping discovery of linked IdPs", user.getId());
return homeIdps;
}
if (emailDomain.isPresent()) {
Domain domain = emailDomain.get();
homeIdps = discoverHomeIdps(domain, user, username);
if (homeIdps.isEmpty()) {
LOG.infof("Could not find home IdP for domain '%s' and user '%s' in realm '%s'",
domain, username, realmName);
}
} else {
LOG.warnf("Could not extract domain from email address '%s'", username);
}
return homeIdps;
}
private List discoverHomeIdps(Domain domain, UserModel user, String username) {
final Map linkedIdps;
HomeIdpDiscoveryConfig config = new HomeIdpDiscoveryConfig(context.getAuthenticatorConfig());
if (user == null || !config.forwardToLinkedIdp()) {
linkedIdps = Collections.emptyMap();
LOG.tracef(
"User '%s' is not stored locally or forwarding to linked IdP is disabled. Skipping discovery of linked IdPs.",
username);
} else {
LOG.tracef(
"Found local user '%s' and forwarding to linked IdP is enabled. Discovering linked IdPs.",
username);
linkedIdps = context.getSession().users()
.getFederatedIdentitiesStream(context.getRealm(), user)
.collect(
Collectors.toMap(FederatedIdentityModel::getIdentityProvider, FederatedIdentityModel::getUserName));
}
List enabledIdps = determineEnabledIdps();
// Original; lookup mechanism from https://github.com/sventorben/keycloak-home-idp-discovery
/*
List enabledIdpsWithMatchingDomain = filterIdpsWithMatchingDomainFrom(enabledIdps,
domain,
config);
*/
// Overidden lookup mechanism to lookup via organization domain
OrganizationProvider orgs = context.getSession().getProvider(OrganizationProvider.class);
List enabledIdpsWithMatchingDomain =
orgs.getOrganizationsStreamForDomain(
context.getRealm(), domain.toString(), config.requireVerifiedDomain())
.flatMap(o -> o.getIdentityProvidersStream())
.filter(IdentityProviderModel::isEnabled)
.collect(Collectors.toList());
// Prefer linked IdP with matching domain first
List homeIdps = getLinkedIdpsFrom(enabledIdpsWithMatchingDomain, linkedIdps);
if (homeIdps.isEmpty()) {
if (!linkedIdps.isEmpty()) {
// Prefer linked and enabled IdPs without matching domain in favor of not linked IdPs with matching domain
homeIdps = getLinkedIdpsFrom(enabledIdps, linkedIdps);
}
if (homeIdps.isEmpty()) {
// Fallback to not linked IdPs with matching domain (general case if user logs in for the first time)
homeIdps = enabledIdpsWithMatchingDomain;
logFoundIdps("non-linked", "matching", homeIdps, domain, username);
} else {
logFoundIdps("non-linked", "non-matching", homeIdps, domain, username);
}
} else {
logFoundIdps("linked", "matching", homeIdps, domain, username);
}
return homeIdps;
}
private void logFoundIdps(String idpQualifier, String domainQualifier, List homeIdps, Domain domain, String username) {
String homeIdpsString = homeIdps.stream()
.map(IdentityProviderModel::getAlias)
.collect(Collectors.joining(","));
LOG.tracef("Found %s IdPs [%s] with %s domain '%s' for user '%s'",
idpQualifier, homeIdpsString, domainQualifier, domain, username);
}
private List getLinkedIdpsFrom(List enabledIdpsWithMatchingDomain, Map linkedIdps) {
return enabledIdpsWithMatchingDomain.stream()
.filter(it -> linkedIdps.containsKey(it.getAlias()))
.collect(Collectors.toList());
}
private List filterIdpsWithMatchingDomainFrom(List enabledIdps, Domain domain, HomeIdpDiscoveryConfig config) {
String userAttributeName = config.userAttribute();
List idpsWithMatchingDomain = enabledIdps.stream()
.filter(it -> new IdentityProviderModelConfig(it).supportsDomain(userAttributeName, domain))
.collect(Collectors.toList());
LOG.tracef("IdPs with matching domain '%s' for attribute '%s': %s", domain, userAttributeName,
idpsWithMatchingDomain.stream().map(IdentityProviderModel::getAlias).collect(Collectors.joining(",")));
return idpsWithMatchingDomain;
}
private List determineEnabledIdps() {
RealmModel realm = context.getRealm();
List enabledIdps = realm.getIdentityProvidersStream()
.filter(IdentityProviderModel::isEnabled)
.collect(Collectors.toList());
LOG.tracef("Enabled IdPs in realm '%s': %s",
realm.getName(),
enabledIdps.stream().map(IdentityProviderModel::getAlias).collect(Collectors.joining(",")));
return enabledIdps;
}
}