package org.pac4j.config.ldaptive;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.ldaptive.BindConnectionInitializer;
import org.ldaptive.BindRequest;
import org.ldaptive.ConnectionConfig;
import org.ldaptive.Credential;
import org.ldaptive.DefaultConnectionFactory;
import org.ldaptive.ReturnAttributes;
import org.ldaptive.SearchExecutor;
import org.ldaptive.SearchFilter;
import org.ldaptive.SearchRequest;
import org.ldaptive.SearchScope;
import org.ldaptive.ad.extended.FastBindOperation;
import org.ldaptive.auth.*;
import org.ldaptive.control.PasswordPolicyControl;
import org.ldaptive.pool.BindPassivator;
import org.ldaptive.pool.BlockingConnectionPool;
import org.ldaptive.pool.ClosePassivator;
import org.ldaptive.pool.ConnectionPool;
import org.ldaptive.pool.IdlePruneStrategy;
import org.ldaptive.pool.PoolConfig;
import org.ldaptive.pool.PooledConnectionFactory;
import org.ldaptive.pool.SearchValidator;
import org.ldaptive.provider.Provider;
import org.ldaptive.sasl.CramMd5Config;
import org.ldaptive.sasl.DigestMd5Config;
import org.ldaptive.sasl.ExternalConfig;
import org.ldaptive.sasl.GssApiConfig;
import org.ldaptive.sasl.SaslConfig;
import org.ldaptive.ssl.KeyStoreCredentialConfig;
import org.ldaptive.ssl.SslConfig;
import org.ldaptive.ssl.X509CredentialConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Copy/pasted from CAS server v5.0.4: Beans + LdapAuthenticationConfiguration classes, only the Ldaptive stuffs are kept.
*/
public class LdaptiveAuthenticatorBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(LdaptiveAuthenticatorBuilder.class);
protected LdaptiveAuthenticatorBuilder() {
}
/*
* ####################################################################################################################################
* ####################################################################################################################################
* From the LdapAuthenticationConfiguration class:
* ####################################################################################################################################
* ####################################################################################################################################
*/
public static Authenticator getAuthenticator(final LdapAuthenticationProperties l) {
if (l.getType() == LdapAuthenticationProperties.AuthenticationTypes.AD) {
LOGGER.debug("Creating active directory authenticator for {}", l.getLdapUrl());
return getActiveDirectoryAuthenticator(l);
}
if (l.getType() == LdapAuthenticationProperties.AuthenticationTypes.DIRECT) {
LOGGER.debug("Creating direct-bind authenticator for {}", l.getLdapUrl());
return getDirectBindAuthenticator(l);
}
if (l.getType() == LdapAuthenticationProperties.AuthenticationTypes.SASL) {
LOGGER.debug("Creating SASL authenticator for {}", l.getLdapUrl());
return getSaslAuthenticator(l);
}
if (l.getType() == LdapAuthenticationProperties.AuthenticationTypes.AUTHENTICATED) {
LOGGER.debug("Creating authenticated authenticator for {}", l.getLdapUrl());
return getAuthenticatedOrAnonSearchAuthenticator(l);
}
LOGGER.debug("Creating anonymous authenticator for {}", l.getLdapUrl());
return getAuthenticatedOrAnonSearchAuthenticator(l);
}
private static Authenticator getSaslAuthenticator(final LdapAuthenticationProperties l) {
final PooledSearchDnResolver resolver = new PooledSearchDnResolver();
resolver.setBaseDn(l.getBaseDn());
resolver.setSubtreeSearch(l.isSubtreeSearch());
resolver.setAllowMultipleDns(l.isAllowMultipleDns());
resolver.setConnectionFactory(newPooledConnectionFactory(l));
resolver.setUserFilter(l.getUserFilter());
return new Authenticator(resolver, getPooledBindAuthenticationHandler(l));
}
private static Authenticator getAuthenticatedOrAnonSearchAuthenticator(final LdapAuthenticationProperties l) {
final PooledSearchDnResolver resolver = new PooledSearchDnResolver();
resolver.setBaseDn(l.getBaseDn());
resolver.setSubtreeSearch(l.isSubtreeSearch());
resolver.setAllowMultipleDns(l.isAllowMultipleDns());
resolver.setConnectionFactory(newPooledConnectionFactory(l));
resolver.setUserFilter(l.getUserFilter());
final Authenticator auth;
if (StringUtils.isBlank(l.getPrincipalAttributePassword())) {
auth = new Authenticator(resolver, getPooledBindAuthenticationHandler(l));
} else {
auth = new Authenticator(resolver, getPooledCompareAuthenticationHandler(l));
}
if (l.isEnhanceWithEntryResolver()) {
auth.setEntryResolver(newSearchEntryResolver(l));
}
return auth;
}
private static Authenticator getDirectBindAuthenticator(final LdapAuthenticationProperties l) {
if (StringUtils.isBlank(l.getDnFormat())) {
throw new IllegalArgumentException("Dn format cannot be empty/blank for direct bind authentication");
}
final FormatDnResolver resolver = new FormatDnResolver(l.getDnFormat());
final Authenticator authenticator = new Authenticator(resolver, getPooledBindAuthenticationHandler(l));
if (l.isEnhanceWithEntryResolver()) {
authenticator.setEntryResolver(newSearchEntryResolver(l));
}
return authenticator;
}
private static Authenticator getActiveDirectoryAuthenticator(final LdapAuthenticationProperties l) {
if (StringUtils.isBlank(l.getDnFormat())) {
throw new IllegalArgumentException("Dn format cannot be empty/blank for active directory authentication");
}
final FormatDnResolver resolver = new FormatDnResolver(l.getDnFormat());
final Authenticator authn = new Authenticator(resolver, getPooledBindAuthenticationHandler(l));
if (l.isEnhanceWithEntryResolver()) {
authn.setEntryResolver(newSearchEntryResolver(l));
}
return authn;
}
private static PooledBindAuthenticationHandler getPooledBindAuthenticationHandler(final LdapAuthenticationProperties l) {
final PooledBindAuthenticationHandler handler = new PooledBindAuthenticationHandler(newPooledConnectionFactory(l));
handler.setAuthenticationControls(new PasswordPolicyControl());
return handler;
}
private static PooledCompareAuthenticationHandler getPooledCompareAuthenticationHandler(final LdapAuthenticationProperties l) {
final PooledCompareAuthenticationHandler handler = new PooledCompareAuthenticationHandler(newPooledConnectionFactory(l));
handler.setPasswordAttribute(l.getPrincipalAttributePassword());
return handler;
}
/*
* ####################################################################################################################################
* ####################################################################################################################################
* From the Beans class:
* ####################################################################################################################################
* ####################################################################################################################################
*/
/**
* New dn resolver entry resolver.
*
* @param l the ldap settings
* @return the entry resolver
*/
public static EntryResolver newSearchEntryResolver(final LdapAuthenticationProperties l) {
final PooledSearchEntryResolver entryResolver = new PooledSearchEntryResolver();
entryResolver.setBaseDn(l.getBaseDn());
entryResolver.setUserFilter(l.getUserFilter());
entryResolver.setSubtreeSearch(l.isSubtreeSearch());
entryResolver.setConnectionFactory(LdaptiveAuthenticatorBuilder.newPooledConnectionFactory(l));
return entryResolver;
}
/**
* New connection config connection config.
*
* @param l the ldap properties
* @return the connection config
*/
public static ConnectionConfig newConnectionConfig(final AbstractLdapProperties l) {
final ConnectionConfig cc = new ConnectionConfig();
final String urls = Arrays.stream(l.getLdapUrl().split(",")).collect(Collectors.joining(" "));
LOGGER.debug("Transformed LDAP urls from [{}] to [{}]", l.getLdapUrl(), urls);
cc.setLdapUrl(urls);
cc.setUseSSL(l.isUseSsl());
cc.setUseStartTLS(l.isUseStartTls());
cc.setConnectTimeout(newDuration(l.getConnectTimeout()));
if (l.getTrustCertificates() != null) {
final X509CredentialConfig cfg = new X509CredentialConfig();
cfg.setTrustCertificates(l.getTrustCertificates());
cc.setSslConfig(new SslConfig(cfg));
} else if (l.getKeystore() != null) {
final KeyStoreCredentialConfig cfg = new KeyStoreCredentialConfig();
cfg.setKeyStore(l.getKeystore());
cfg.setKeyStorePassword(l.getKeystorePassword());
cfg.setKeyStoreType(l.getKeystoreType());
cc.setSslConfig(new SslConfig(cfg));
} else {
cc.setSslConfig(new SslConfig());
}
if (l.getSaslMechanism() != null) {
final BindConnectionInitializer bc = new BindConnectionInitializer();
final SaslConfig sc;
switch (l.getSaslMechanism()) {
case DIGEST_MD5:
sc = new DigestMd5Config();
((DigestMd5Config) sc).setRealm(l.getSaslRealm());
break;
case CRAM_MD5:
sc = new CramMd5Config();
break;
case EXTERNAL:
sc = new ExternalConfig();
break;
case GSSAPI:
sc = new GssApiConfig();
((GssApiConfig) sc).setRealm(l.getSaslRealm());
break;
default:
throw new IllegalArgumentException("Unknown SASL mechanism " + l.getSaslMechanism().name());
}
sc.setAuthorizationId(l.getSaslAuthorizationId());
sc.setMutualAuthentication(l.getSaslMutualAuth());
sc.setQualityOfProtection(l.getSaslQualityOfProtection());
sc.setSecurityStrength(l.getSaslSecurityStrength());
bc.setBindSaslConfig(sc);
cc.setConnectionInitializer(bc);
} else if (StringUtils.equals(l.getBindCredential(), "*") && StringUtils.equals(l.getBindDn(), "*")) {
cc.setConnectionInitializer(new FastBindOperation.FastBindConnectionInitializer());
} else if (StringUtils.isNotBlank(l.getBindDn()) && StringUtils.isNotBlank(l.getBindCredential())) {
cc.setConnectionInitializer(new BindConnectionInitializer(l.getBindDn(), new Credential(l.getBindCredential())));
}
return cc;
}
/**
* New pool config pool config.
*
* @param l the ldap properties
* @return the pool config
*/
public static PoolConfig newPoolConfig(final AbstractLdapProperties l) {
final PoolConfig pc = new PoolConfig();
pc.setMinPoolSize(l.getMinPoolSize());
pc.setMaxPoolSize(l.getMaxPoolSize());
pc.setValidateOnCheckOut(l.isValidateOnCheckout());
pc.setValidatePeriodically(l.isValidatePeriodically());
pc.setValidatePeriod(newDuration(l.getValidatePeriod()));
return pc;
}
/**
* New connection factory connection factory.
*
* @param l the l
* @return the connection factory
*/
public static DefaultConnectionFactory newConnectionFactory(final AbstractLdapProperties l) {
final ConnectionConfig cc = newConnectionConfig(l);
final DefaultConnectionFactory bindCf = new DefaultConnectionFactory(cc);
if (l.getProviderClass() != null) {
try {
final Class clazz = ClassUtils.getClass(l.getProviderClass());
bindCf.setProvider(Provider.class.cast(clazz.newInstance()));
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
return bindCf;
}
/**
* New blocking connection pool connection pool.
*
* @param l the l
* @return the connection pool
*/
public static ConnectionPool newBlockingConnectionPool(final AbstractLdapProperties l) {
final DefaultConnectionFactory bindCf = newConnectionFactory(l);
final PoolConfig pc = newPoolConfig(l);
final BlockingConnectionPool cp = new BlockingConnectionPool(pc, bindCf);
cp.setBlockWaitTime(newDuration(l.getBlockWaitTime()));
cp.setPoolConfig(pc);
final IdlePruneStrategy strategy = new IdlePruneStrategy();
strategy.setIdleTime(newDuration(l.getIdleTime()));
strategy.setPrunePeriod(newDuration(l.getPrunePeriod()));
cp.setPruneStrategy(strategy);
cp.setValidator(new SearchValidator());
cp.setFailFastInitialize(l.isFailFast());
if (StringUtils.isNotBlank(l.getPoolPassivator())) {
final AbstractLdapProperties.LdapConnectionPoolPassivator pass =
AbstractLdapProperties.LdapConnectionPoolPassivator.valueOf(l.getPoolPassivator().toUpperCase());
switch (pass) {
case CLOSE:
cp.setPassivator(new ClosePassivator());
break;
case BIND:
LOGGER.debug("Creating a bind passivator instance for the connection pool");
final BindRequest bindRequest = new BindRequest();
bindRequest.setDn(l.getBindDn());
bindRequest.setCredential(new Credential(l.getBindCredential()));
cp.setPassivator(new BindPassivator(bindRequest));
break;
default:
break;
}
}
LOGGER.debug("Initializing ldap connection pool for {} and bindDn {}", l.getLdapUrl(), l.getBindDn());
cp.initialize();
return cp;
}
/**
* New pooled connection factory pooled connection factory.
*
* @param l the ldap properties
* @return the pooled connection factory
*/
public static PooledConnectionFactory newPooledConnectionFactory(final AbstractLdapProperties l) {
final ConnectionPool cp = newBlockingConnectionPool(l);
return new PooledConnectionFactory(cp);
}
/**
* New duration.
*
* @param length the length in seconds.
* @return the duration
*/
public static Duration newDuration(final long length) {
return Duration.ofSeconds(length);
}
/**
* Builds a new request.
*
* @param baseDn the base dn
* @param filter the filter
* @return the search request
*/
public static SearchRequest newSearchRequest(final String baseDn, final SearchFilter filter) {
final SearchRequest sr = new SearchRequest(baseDn, filter);
sr.setBinaryAttributes(ReturnAttributes.ALL_USER.value());
sr.setReturnAttributes(ReturnAttributes.ALL_USER.value());
sr.setSearchScope(SearchScope.SUBTREE);
return sr;
}
/**
* Constructs a new search filter using {@link SearchExecutor#searchFilter} as a template and
* the username as a parameter.
*
* @param filterQuery the query filter
* @param params the username
* @return Search filter with parameters applied.
*/
public static SearchFilter newSearchFilter(final String filterQuery, final String... params) {
final SearchFilter filter = new SearchFilter();
filter.setFilter(filterQuery);
if (params != null) {
for (int i = 0; i < params.length; i++) {
if (filter.getFilter().contains("{" + i + "}")) {
filter.setParameter(i, params[i]);
} else {
filter.setParameter("user", params[i]);
}
}
}
LOGGER.debug("Constructed LDAP search filter [{}]", filter.format());
return filter;
}
/**
* New search executor search executor.
*
* @param baseDn the base dn
* @param filterQuery the filter query
* @param params the params
* @return the search executor
*/
public static SearchExecutor newSearchExecutor(final String baseDn, final String filterQuery, final String... params) {
final SearchExecutor executor = new SearchExecutor();
executor.setBaseDn(baseDn);
executor.setSearchFilter(newSearchFilter(filterQuery, params));
executor.setReturnAttributes(ReturnAttributes.ALL.value());
executor.setSearchScope(SearchScope.SUBTREE);
return executor;
}
}