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; } }