package org.apereo.cas.configuration.support;
import com.google.common.base.Throwables;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
import com.zaxxer.hikari.HikariDataSource;
import groovy.lang.GroovyClassLoader;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apereo.cas.CipherExecutor;
import org.apereo.cas.authentication.handler.PrincipalNameTransformer;
import org.apereo.cas.configuration.model.core.authentication.PasswordEncoderProperties;
import org.apereo.cas.configuration.model.core.authentication.PrincipalAttributesProperties;
import org.apereo.cas.configuration.model.core.authentication.PrincipalTransformationProperties;
import org.apereo.cas.configuration.model.core.util.CryptographyProperties;
import org.apereo.cas.configuration.model.support.ConnectionPoolingProperties;
import org.apereo.cas.configuration.model.support.jpa.AbstractJpaProperties;
import org.apereo.cas.configuration.model.support.jpa.DatabaseProperties;
import org.apereo.cas.configuration.model.support.jpa.JpaConfigDataHolder;
import org.apereo.cas.configuration.model.support.ldap.AbstractLdapAuthenticationProperties;
import org.apereo.cas.configuration.model.support.ldap.AbstractLdapProperties;
import org.apereo.cas.configuration.model.support.mongo.AbstractMongoInstanceProperties;
import org.apereo.cas.util.cipher.DefaultTicketCipherExecutor;
import org.apereo.cas.util.cipher.NoOpCipherExecutor;
import org.apereo.cas.util.crypto.DefaultPasswordEncoder;
import org.apereo.cas.util.transforms.ConvertCasePrincipalNameTransformer;
import org.apereo.cas.util.transforms.PrefixSuffixPrincipalNameTransformer;
import org.apereo.services.persondir.IPersonAttributeDao;
import org.apereo.services.persondir.support.NamedStubPersonAttributeDao;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.ldaptive.ActivePassiveConnectionStrategy;
import org.ldaptive.BindConnectionInitializer;
import org.ldaptive.BindRequest;
import org.ldaptive.CompareRequest;
import org.ldaptive.ConnectionConfig;
import org.ldaptive.Credential;
import org.ldaptive.DefaultConnectionFactory;
import org.ldaptive.DefaultConnectionStrategy;
import org.ldaptive.DnsSrvConnectionStrategy;
import org.ldaptive.LdapAttribute;
import org.ldaptive.RandomConnectionStrategy;
import org.ldaptive.ReturnAttributes;
import org.ldaptive.RoundRobinConnectionStrategy;
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.ad.handler.ObjectGuidHandler;
import org.ldaptive.ad.handler.ObjectSidHandler;
import org.ldaptive.ad.handler.PrimaryGroupIdHandler;
import org.ldaptive.ad.handler.RangeEntryHandler;
import org.ldaptive.auth.Authenticator;
import org.ldaptive.auth.EntryResolver;
import org.ldaptive.auth.FormatDnResolver;
import org.ldaptive.auth.PooledBindAuthenticationHandler;
import org.ldaptive.auth.PooledCompareAuthenticationHandler;
import org.ldaptive.auth.PooledSearchDnResolver;
import org.ldaptive.auth.PooledSearchEntryResolver;
import org.ldaptive.control.PasswordPolicyControl;
import org.ldaptive.handler.CaseChangeEntryHandler;
import org.ldaptive.handler.DnAttributeEntryHandler;
import org.ldaptive.handler.MergeAttributeEntryHandler;
import org.ldaptive.handler.RecursiveEntryHandler;
import org.ldaptive.handler.SearchEntryHandler;
import org.ldaptive.pool.BindPassivator;
import org.ldaptive.pool.BlockingConnectionPool;
import org.ldaptive.pool.ClosePassivator;
import org.ldaptive.pool.CompareValidator;
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.referral.SearchReferralHandler;
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 org.springframework.beans.factory.BeanCreationException;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.mongodb.core.MongoClientOptionsFactoryBean;
import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* A re-usable collection of utility methods for object instantiations and configurations used cross various
* {@code @Bean} creation methods throughout CAS server.
*
* @author Dmitriy Kopylenko
* @since 5.0.0
*/
public final class Beans {
/**
* Default parameter name in search filters for ldap.
*/
public static final String LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME = "user";
private static final Logger LOGGER = LoggerFactory.getLogger(Beans.class);
protected Beans() {
}
/**
* Get new data source, from JNDI lookup or created via direct configuration
* of Hikari pool.
* <p>
* If jpaProperties contains {@link AbstractJpaProperties#getDataSourceName()} a lookup will be
* attempted. If the DataSource is not found via JNDI then CAS will attempt to
* configure a Hikari connection pool.
* <p>
* Since the datasource beans are {@link org.springframework.cloud.context.config.annotation.RefreshScope},
* they will be a proxied by Spring
* and on some application servers there have been classloading issues. A workaround
* for this is to use the {@link AbstractJpaProperties#isDataSourceProxy()} setting and then the dataSource will be
* wrapped in an application level class. If that is an issue, don't do it.
*
* @param jpaProperties the jpa properties
* @return the data source
*/
public static DataSource newDataSource(final AbstractJpaProperties jpaProperties) {
final String dataSourceName = jpaProperties.getDataSourceName();
final boolean proxyDataSource = jpaProperties.isDataSourceProxy();
if (StringUtils.isNotBlank(dataSourceName)) {
try {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
/*
if user wants to do lookup as resource, they may include java:/comp/env
in dataSourceName and put resource reference in web.xml
otherwise dataSourceName is used as JNDI name
*/
dsLookup.setResourceRef(false);
final DataSource containerDataSource = dsLookup.getDataSource(dataSourceName);
if (!proxyDataSource) {
return containerDataSource;
}
return new DataSourceProxy(containerDataSource);
} catch (final DataSourceLookupFailureException e) {
LOGGER.warn("Lookup of datasource [{}] failed due to {} "
+ "falling back to configuration via JPA properties.", dataSourceName, e.getMessage());
}
}
try {
final HikariDataSource bean = new HikariDataSource();
if (StringUtils.isNotBlank(jpaProperties.getDriverClass())) {
bean.setDriverClassName(jpaProperties.getDriverClass());
}
bean.setJdbcUrl(jpaProperties.getUrl());
bean.setUsername(jpaProperties.getUser());
bean.setPassword(jpaProperties.getPassword());
bean.setLoginTimeout(Long.valueOf(jpaProperties.getPool().getMaxWait()).intValue());
bean.setMaximumPoolSize(jpaProperties.getPool().getMaxSize());
bean.setMinimumIdle(jpaProperties.getPool().getMinSize());
bean.setIdleTimeout(jpaProperties.getIdleTimeout());
bean.setLeakDetectionThreshold(jpaProperties.getLeakThreshold());
bean.setInitializationFailTimeout(jpaProperties.isFailFast() ? 1 : 0);
bean.setIsolateInternalQueries(jpaProperties.isIsolateInternalQueries());
bean.setConnectionTestQuery(jpaProperties.getHealthQuery());
bean.setAllowPoolSuspension(jpaProperties.getPool().isSuspension());
bean.setAutoCommit(jpaProperties.isAutocommit());
bean.setValidationTimeout(jpaProperties.getPool().getTimeoutMillis());
return bean;
} catch (final Exception e) {
LOGGER.error("Error creating DataSource: [{}]", e.getMessage());
throw new IllegalArgumentException(e);
}
}
/**
* New hibernate jpa vendor adapter.
*
* @param databaseProperties the database properties
* @return the hibernate jpa vendor adapter
*/
public static HibernateJpaVendorAdapter newHibernateJpaVendorAdapter(final DatabaseProperties databaseProperties) {
final HibernateJpaVendorAdapter bean = new HibernateJpaVendorAdapter();
bean.setGenerateDdl(databaseProperties.isGenDdl());
bean.setShowSql(databaseProperties.isShowSql());
return bean;
}
/**
* New thread pool executor factory bean thread pool executor factory bean.
*
* @param config the config
* @return the thread pool executor factory bean
*/
public static ThreadPoolExecutorFactoryBean newThreadPoolExecutorFactoryBean(final ConnectionPoolingProperties config) {
final ThreadPoolExecutorFactoryBean bean = new ThreadPoolExecutorFactoryBean();
bean.setCorePoolSize(config.getMinSize());
bean.setMaxPoolSize(config.getMaxSize());
bean.setKeepAliveSeconds(Long.valueOf(config.getMaxWait()).intValue());
return bean;
}
/**
* New entity manager factory bean.
*
* @param config the config
* @param jpaProperties the jpa properties
* @return the local container entity manager factory bean
*/
public static LocalContainerEntityManagerFactoryBean newHibernateEntityManagerFactoryBean(final JpaConfigDataHolder config,
final AbstractJpaProperties jpaProperties) {
final LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setJpaVendorAdapter(config.getJpaVendorAdapter());
if (StringUtils.isNotBlank(config.getPersistenceUnitName())) {
bean.setPersistenceUnitName(config.getPersistenceUnitName());
}
bean.setPackagesToScan(config.getPackagesToScan());
bean.setDataSource(config.getDataSource());
final Properties properties = new Properties();
properties.put("hibernate.dialect", jpaProperties.getDialect());
properties.put("hibernate.hbm2ddl.auto", jpaProperties.getDdlAuto());
properties.put("hibernate.jdbc.batch_size", jpaProperties.getBatchSize());
if (StringUtils.isNotBlank(jpaProperties.getDefaultCatalog())) {
properties.put("hibernate.default_catalog", jpaProperties.getDefaultCatalog());
}
if (StringUtils.isNotBlank(jpaProperties.getDefaultSchema())) {
properties.put("hibernate.default_schema", jpaProperties.getDefaultSchema());
}
bean.setJpaProperties(properties);
bean.getJpaPropertyMap().put("hibernate.enable_lazy_load_no_trans", Boolean.TRUE);
return bean;
}
/**
* New attribute repository person attribute dao.
*
* @param p the properties
* @return the person attribute dao
*/
public static IPersonAttributeDao newStubAttributeRepository(final PrincipalAttributesProperties p) {
try {
final NamedStubPersonAttributeDao dao = new NamedStubPersonAttributeDao();
final Map<String, List<Object>> pdirMap = new HashMap<>();
p.getStub().getAttributes().forEach((key, value) -> {
final String[] vals = org.springframework.util.StringUtils.commaDelimitedListToStringArray(value);
pdirMap.put(key, Arrays.asList((Object[]) vals));
});
dao.setBackingMap(pdirMap);
return dao;
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* New password encoder password encoder.
*
* @param properties the properties
* @return the password encoder
*/
public static PasswordEncoder newPasswordEncoder(final PasswordEncoderProperties properties) {
final String type = properties.getType();
if (StringUtils.isBlank(type)) {
LOGGER.debug("No password encoder type is defined, and so none shall be created");
return NoOpPasswordEncoder.getInstance();
}
if (type.contains(".")) {
try {
LOGGER.debug("Configuration indicates use of a custom password encoder [{}]", type);
final Class<PasswordEncoder> clazz = (Class<PasswordEncoder>) Class.forName(type);
return clazz.newInstance();
} catch (final Exception e) {
LOGGER.error("Falling back to a no-op password encoder as CAS has failed to create "
+ "an instance of the custom password encoder class " + type, e);
return NoOpPasswordEncoder.getInstance();
}
}
final PasswordEncoderProperties.PasswordEncoderTypes encoderType = PasswordEncoderProperties.PasswordEncoderTypes.valueOf(type);
switch (encoderType) {
case DEFAULT:
LOGGER.debug("Creating default password encoder with encoding alg [{}] and character encoding [{}]",
properties.getEncodingAlgorithm(), properties.getCharacterEncoding());
return new DefaultPasswordEncoder(properties.getEncodingAlgorithm(), properties.getCharacterEncoding());
case STANDARD:
LOGGER.debug("Creating standard password encoder with the secret defined in the configuration");
return new StandardPasswordEncoder(properties.getSecret());
case BCRYPT:
LOGGER.debug("Creating BCRYPT password encoder given the strength [{}] and secret in the configuration",
properties.getStrength());
if (StringUtils.isBlank(properties.getSecret())) {
LOGGER.debug("Creating BCRYPT encoder without secret");
return new BCryptPasswordEncoder(properties.getStrength());
}
LOGGER.debug("Creating BCRYPT encoder with secret");
return new BCryptPasswordEncoder(properties.getStrength(),
new SecureRandom(properties.getSecret().getBytes(StandardCharsets.UTF_8)));
case SCRYPT:
LOGGER.debug("Creating SCRYPT encoder");
return new SCryptPasswordEncoder();
case PBKDF2:
if (StringUtils.isBlank(properties.getSecret())) {
LOGGER.debug("Creating PBKDF2 encoder without secret");
return new Pbkdf2PasswordEncoder();
}
final int hashWidth = 256;
return new Pbkdf2PasswordEncoder(properties.getSecret(), properties.getStrength(), hashWidth);
case NONE:
default:
LOGGER.debug("No password encoder shall be created given the requested encoder type [{}]", type);
return NoOpPasswordEncoder.getInstance();
}
}
/**
* New principal name transformer.
*
* @param p the p
* @return the principal name transformer
*/
public static PrincipalNameTransformer newPrincipalNameTransformer(final PrincipalTransformationProperties p) {
final PrincipalNameTransformer res;
if (StringUtils.isNotBlank(p.getPrefix()) || StringUtils.isNotBlank(p.getSuffix())) {
final PrefixSuffixPrincipalNameTransformer t = new PrefixSuffixPrincipalNameTransformer();
t.setPrefix(p.getPrefix());
t.setSuffix(p.getSuffix());
res = t;
} else {
res = formUserId -> formUserId;
}
switch (p.getCaseConversion()) {
case UPPERCASE:
final ConvertCasePrincipalNameTransformer t = new ConvertCasePrincipalNameTransformer(res);
t.setToUpperCase(true);
return t;
case LOWERCASE:
final ConvertCasePrincipalNameTransformer t1 = new ConvertCasePrincipalNameTransformer(res);
t1.setToUpperCase(false);
return t1;
default:
//nothing
}
return res;
}
/**
* New dn resolver entry resolver.
* Creates the necessary search entry resolver.
*
* @param l the ldap settings
* @param factory the factory
* @return the entry resolver
*/
public static EntryResolver newLdaptiveSearchEntryResolver(final AbstractLdapAuthenticationProperties l,
final PooledConnectionFactory factory) {
if (StringUtils.isBlank(l.getBaseDn())) {
throw new IllegalArgumentException("To create a search entry resolver, base dn cannot be empty/blank ");
}
if (StringUtils.isBlank(l.getUserFilter())) {
throw new IllegalArgumentException("To create a search entry resolver, user filter cannot be empty/blank");
}
final PooledSearchEntryResolver entryResolver = new PooledSearchEntryResolver();
entryResolver.setBaseDn(l.getBaseDn());
entryResolver.setUserFilter(l.getUserFilter());
entryResolver.setSubtreeSearch(l.isSubtreeSearch());
entryResolver.setConnectionFactory(factory);
final List<SearchEntryHandler> handlers = new ArrayList<>();
l.getSearchEntryHandlers().forEach(h -> {
switch (h.getType()) {
case CASE_CHANGE:
final CaseChangeEntryHandler eh = new CaseChangeEntryHandler();
eh.setAttributeNameCaseChange(h.getCasChange().getAttributeNameCaseChange());
eh.setAttributeNames(h.getCasChange().getAttributeNames());
eh.setAttributeValueCaseChange(h.getCasChange().getAttributeValueCaseChange());
eh.setDnCaseChange(h.getCasChange().getDnCaseChange());
handlers.add(eh);
break;
case DN_ATTRIBUTE_ENTRY:
final DnAttributeEntryHandler ehd = new DnAttributeEntryHandler();
ehd.setAddIfExists(h.getDnAttribute().isAddIfExists());
ehd.setDnAttributeName(h.getDnAttribute().getDnAttributeName());
handlers.add(ehd);
break;
case MERGE:
final MergeAttributeEntryHandler ehm = new MergeAttributeEntryHandler();
ehm.setAttributeNames(h.getMergeAttribute().getAttributeNames());
ehm.setMergeAttributeName(h.getMergeAttribute().getMergeAttributeName());
handlers.add(ehm);
break;
case OBJECT_GUID:
handlers.add(new ObjectGuidHandler());
break;
case OBJECT_SID:
handlers.add(new ObjectSidHandler());
break;
case PRIMARY_GROUP:
final PrimaryGroupIdHandler ehp = new PrimaryGroupIdHandler();
ehp.setBaseDn(h.getPrimaryGroupId().getBaseDn());
ehp.setGroupFilter(h.getPrimaryGroupId().getGroupFilter());
handlers.add(ehp);
break;
case RANGE_ENTRY:
handlers.add(new RangeEntryHandler());
break;
case RECURSIVE_ENTRY:
handlers.add(new RecursiveEntryHandler(h.getRecursive().getSearchAttribute(), h.getRecursive().getMergeAttributes()));
break;
default:
break;
}
});
if (!handlers.isEmpty()) {
LOGGER.debug("Search entry handlers defined for the entry resolver of [{}] are [{}]", l.getLdapUrl(), handlers);
entryResolver.setSearchEntryHandlers(handlers.toArray(new SearchEntryHandler[]{}));
}
return entryResolver;
}
/**
* Transform principal attributes into map.
* Items in the list are defined in the syntax of "cn", or "cn:commonName" for virtual renaming and maps.
*
* @param list the list
* @return the map
*/
public static Map<String, String> transformPrincipalAttributesListIntoMap(final List<String> list) {
final Map<String, String> attributes = new HashMap<>();
if (list.isEmpty()) {
LOGGER.debug("No principal attributes are defined");
} else {
list.forEach(a -> {
final String attributeName = a.trim();
if (attributeName.contains(":")) {
final String[] attrCombo = attributeName.split(":");
final String name = attrCombo[0].trim();
final String value = attrCombo[1].trim();
LOGGER.debug("Mapped principal attribute name [{}] to [{}]", name, value);
attributes.put(name, value);
} else {
LOGGER.debug("Mapped principal attribute name [{}]", attributeName);
attributes.put(attributeName, attributeName);
}
});
}
return attributes;
}
/**
* New connection config connection config.
*
* @param l the ldap properties
* @return the connection config
*/
public static ConnectionConfig newLdaptiveConnectionConfig(final AbstractLdapProperties l) {
if (StringUtils.isBlank(l.getLdapUrl())) {
throw new IllegalArgumentException("LDAP url cannot be empty/blank");
}
LOGGER.debug("Creating LDAP connection configuration for [{}]", l.getLdapUrl());
final ConnectionConfig cc = new ConnectionConfig();
final String urls = l.getLdapUrl().contains(" ")
? l.getLdapUrl()
: 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()));
cc.setResponseTimeout(newDuration(l.getResponseTimeout()));
if (StringUtils.isNotBlank(l.getConnectionStrategy())) {
final AbstractLdapProperties.LdapConnectionStrategy strategy =
AbstractLdapProperties.LdapConnectionStrategy.valueOf(l.getConnectionStrategy());
switch (strategy) {
case RANDOM:
cc.setConnectionStrategy(new RandomConnectionStrategy());
break;
case DNS_SRV:
cc.setConnectionStrategy(new DnsSrvConnectionStrategy());
break;
case ACTIVE_PASSIVE:
cc.setConnectionStrategy(new ActivePassiveConnectionStrategy());
break;
case ROUND_ROBIN:
cc.setConnectionStrategy(new RoundRobinConnectionStrategy());
break;
case DEFAULT:
default:
cc.setConnectionStrategy(new DefaultConnectionStrategy());
break;
}
}
if (l.getTrustCertificates() != null) {
LOGGER.debug("Creating LDAP SSL configuration via trust certificates [{}]", l.getTrustCertificates());
final X509CredentialConfig cfg = new X509CredentialConfig();
cfg.setTrustCertificates(l.getTrustCertificates());
cc.setSslConfig(new SslConfig(cfg));
} else if (l.getKeystore() != null) {
LOGGER.debug("Creating LDAP SSL configuration via keystore [{}]", l.getKeystore());
final KeyStoreCredentialConfig cfg = new KeyStoreCredentialConfig();
cfg.setKeyStore(l.getKeystore());
cfg.setKeyStorePassword(l.getKeystorePassword());
cfg.setKeyStoreType(l.getKeystoreType());
cc.setSslConfig(new SslConfig(cfg));
} else {
LOGGER.debug("Creating LDAP SSL configuration via the native JVM truststore");
cc.setSslConfig(new SslConfig());
}
if (l.getSaslMechanism() != null) {
LOGGER.debug("Creating LDAP SASL mechanism via [{}]", l.getSaslMechanism());
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(), "*")) {
LOGGER.debug("Creating LDAP fast-bind connection initializer");
cc.setConnectionInitializer(new FastBindOperation.FastBindConnectionInitializer());
} else if (StringUtils.isNotBlank(l.getBindDn()) && StringUtils.isNotBlank(l.getBindCredential())) {
LOGGER.debug("Creating LDAP bind connection initializer via [{}]", l.getBindDn());
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 newLdaptivePoolConfig(final AbstractLdapProperties l) {
LOGGER.debug("Creating LDAP connection pool configuration for [{}]", l.getLdapUrl());
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()));
pc.setValidateTimeout(newDuration(l.getValidateTimeout()));
return pc;
}
/**
* New connection factory connection factory.
*
* @param l the l
* @return the connection factory
*/
public static DefaultConnectionFactory newLdaptiveConnectionFactory(final AbstractLdapProperties l) {
LOGGER.debug("Creating LDAP connection factory for [{}]", l.getLdapUrl());
final ConnectionConfig cc = newLdaptiveConnectionConfig(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 newLdaptiveBlockingConnectionPool(final AbstractLdapProperties l) {
final DefaultConnectionFactory bindCf = newLdaptiveConnectionFactory(l);
final PoolConfig pc = newLdaptivePoolConfig(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);
switch (l.getValidator().getType().trim().toLowerCase()) {
case "compare":
final CompareRequest compareRequest = new CompareRequest();
compareRequest.setDn(l.getValidator().getDn());
compareRequest.setAttribute(new LdapAttribute(l.getValidator().getAttributeName(),
l.getValidator().getAttributeValues().toArray(new String[]{})));
compareRequest.setReferralHandler(new SearchReferralHandler());
cp.setValidator(new CompareValidator(compareRequest));
break;
case "none":
LOGGER.debug("No validator is configured for the LDAP connection pool of [{}]", l.getLdapUrl());
break;
case "search":
default:
final SearchRequest searchRequest = new SearchRequest();
searchRequest.setBaseDn(l.getValidator().getBaseDn());
searchRequest.setSearchFilter(new SearchFilter(l.getValidator().getSearchFilter()));
searchRequest.setReturnAttributes(ReturnAttributes.NONE.value());
searchRequest.setSearchScope(l.getValidator().getScope());
searchRequest.setSizeLimit(1L);
searchRequest.setReferralHandler(new SearchReferralHandler());
cp.setValidator(new SearchValidator(searchRequest));
break;
}
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());
LOGGER.debug("Created [{}] passivator for [{}]", l.getPoolPassivator(), l.getLdapUrl());
break;
case BIND:
if (StringUtils.isNotBlank(l.getBindDn()) && StringUtils.isNoneBlank(l.getBindCredential())) {
final BindRequest bindRequest = new BindRequest();
bindRequest.setDn(l.getBindDn());
bindRequest.setCredential(new Credential(l.getBindCredential()));
cp.setPassivator(new BindPassivator(bindRequest));
LOGGER.debug("Created [{}] passivator for [{}]", l.getPoolPassivator(), l.getLdapUrl());
} else {
LOGGER.warn("No [{}] passivator could be created for [{}] given bind credentials are not specified",
l.getPoolPassivator(), l.getLdapUrl());
}
break;
default:
break;
}
}
LOGGER.debug("Initializing ldap connection pool for [{}] and bindDn [{}]", l.getLdapUrl(), l.getBindDn());
cp.initialize();
return cp;
}
/**
* Gets credential selection predicate.
*
* @param selectionCriteria the selection criteria
* @return the credential selection predicate
*/
public static Predicate<org.apereo.cas.authentication.Credential> newCredentialSelectionPredicate(final String selectionCriteria) {
try {
if (StringUtils.isBlank(selectionCriteria)) {
return credential -> true;
}
if (selectionCriteria.endsWith(".groovy")) {
final ResourceLoader loader = new DefaultResourceLoader();
final Resource resource = loader.getResource(selectionCriteria);
if (resource != null) {
final String script = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8);
final GroovyClassLoader classLoader = new GroovyClassLoader(Beans.class.getClassLoader(),
new CompilerConfiguration(), true);
final Class<Predicate> clz = classLoader.parseClass(script);
return clz.newInstance();
}
}
final Class predicateClazz = ClassUtils.getClass(selectionCriteria);
return (Predicate<org.apereo.cas.authentication.Credential>) predicateClazz.newInstance();
} catch (final Exception e) {
final Predicate<String> predicate = Pattern.compile(selectionCriteria).asPredicate();
return credential -> predicate.test(credential.getId());
}
}
/**
* New pooled connection factory pooled connection factory.
*
* @param l the ldap properties
* @return the pooled connection factory
*/
public static PooledConnectionFactory newLdaptivePooledConnectionFactory(final AbstractLdapProperties l) {
final ConnectionPool cp = newLdaptiveBlockingConnectionPool(l);
return new PooledConnectionFactory(cp);
}
/**
* New duration. If the provided length is duration,
* it will be parsed accordingly, or if it's a numeric value
* it will be pared as a duration assuming it's provided as seconds.
*
* @param length the length in seconds.
* @return the duration
*/
public static Duration newDuration(final String length) {
try {
if (NumberUtils.isCreatable(length)) {
return Duration.ofSeconds(Long.valueOf(length));
}
return Duration.parse(length);
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* New ticket registry cipher executor cipher executor.
*
* @param registry the registry
* @return the cipher executor
*/
public static CipherExecutor newTicketRegistryCipherExecutor(final CryptographyProperties registry) {
return newTicketRegistryCipherExecutor(registry, false);
}
/**
* New ticket registry cipher executor cipher executor.
*
* @param registry the registry
* @param forceIfBlankKeys the force if blank keys
* @return the cipher executor
*/
public static CipherExecutor newTicketRegistryCipherExecutor(final CryptographyProperties registry, final boolean forceIfBlankKeys) {
if (StringUtils.isNotBlank(registry.getEncryption().getKey())
&& StringUtils.isNotBlank(registry.getEncryption().getKey())
|| forceIfBlankKeys) {
return new DefaultTicketCipherExecutor(
registry.getEncryption().getKey(),
registry.getSigning().getKey(),
registry.getAlg(),
registry.getSigning().getKeySize(),
registry.getEncryption().getKeySize());
}
LOGGER.debug("Ticket registry encryption/signing is turned off. This MAY NOT be safe in a clustered production environment. "
+ "Consider using other choices to handle encryption, signing and verification of "
+ "ticket registry tickets, and verify the chosen ticket registry does support this behavior.");
return NoOpCipherExecutor.getInstance();
}
/**
* Builds a new request.
*
* @param baseDn the base dn
* @param filter the filter
* @param binaryAttributes the binary attributes
* @param returnAttributes the return attributes
* @return the search request
*/
public static SearchRequest newLdaptiveSearchRequest(final String baseDn,
final SearchFilter filter,
final String[] binaryAttributes,
final String[] returnAttributes) {
final SearchRequest sr = new SearchRequest(baseDn, filter);
sr.setBinaryAttributes(binaryAttributes);
sr.setReturnAttributes(returnAttributes);
sr.setSearchScope(SearchScope.SUBTREE);
return sr;
}
/**
* New ldaptive search request.
* Returns all attributes.
*
* @param baseDn the base dn
* @param filter the filter
* @return the search request
*/
public static SearchRequest newLdaptiveSearchRequest(final String baseDn,
final SearchFilter filter) {
return newLdaptiveSearchRequest(baseDn, filter, ReturnAttributes.ALL_USER.value(), ReturnAttributes.ALL_USER.value());
}
/**
* Constructs a new search filter using {@link SearchExecutor#searchFilter} as a template and
* the username as a parameter.
*
* @param filterQuery the query filter
* @return Search filter with parameters applied.
*/
public static SearchFilter newLdaptiveSearchFilter(final String filterQuery) {
return newLdaptiveSearchFilter(filterQuery, Collections.emptyList());
}
/**
* 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 newLdaptiveSearchFilter(final String filterQuery, final List<String> params) {
return newLdaptiveSearchFilter(filterQuery, LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME, params);
}
/**
* Constructs a new search filter using {@link SearchExecutor#searchFilter} as a template and
* the username as a parameter.
*
* @param filterQuery the query filter
* @param paramName the param name
* @param params the username
* @return Search filter with parameters applied.
*/
public static SearchFilter newLdaptiveSearchFilter(final String filterQuery, final String paramName, final List<String> params) {
final SearchFilter filter = new SearchFilter();
filter.setFilter(filterQuery);
if (params != null) {
IntStream.range(0, params.size()).forEach(i -> {
if (filter.getFilter().contains("{" + i + '}')) {
filter.setParameter(i, params.get(i));
} else {
filter.setParameter(paramName, params.get(i));
}
});
}
LOGGER.debug("Constructed LDAP search filter [{}]", filter.format());
return filter;
}
/**
* New search executor.
*
* @param baseDn the base dn
* @param filterQuery the filter query
* @param params the params
* @return the search executor
*/
public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery, final List<String> params) {
return newLdaptiveSearchExecutor(baseDn, filterQuery, params, ReturnAttributes.ALL.value());
}
/**
* New ldaptive search executor search executor.
*
* @param baseDn the base dn
* @param filterQuery the filter query
* @param params the params
* @param returnAttributes the return attributes
* @return the search executor
*/
public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery,
final List<String> params,
final List<String> returnAttributes) {
return newLdaptiveSearchExecutor(baseDn, filterQuery, params, returnAttributes.toArray(new String[]{}));
}
/**
* New ldaptive search executor search executor.
*
* @param baseDn the base dn
* @param filterQuery the filter query
* @param params the params
* @param returnAttributes the return attributes
* @return the search executor
*/
public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery,
final List<String> params,
final String[] returnAttributes) {
final SearchExecutor executor = new SearchExecutor();
executor.setBaseDn(baseDn);
executor.setSearchFilter(newLdaptiveSearchFilter(filterQuery, params));
executor.setReturnAttributes(returnAttributes);
executor.setSearchScope(SearchScope.SUBTREE);
return executor;
}
/**
* New search executor search executor.
*
* @param baseDn the base dn
* @param filterQuery the filter query
* @return the search executor
*/
public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery) {
return newLdaptiveSearchExecutor(baseDn, filterQuery, Collections.emptyList());
}
/**
* New mongo db client options factory bean.
*
* @param mongo the mongo properties.
* @return the mongo client options factory bean
*/
public static MongoClientOptionsFactoryBean newMongoDbClientOptionsFactoryBean(final AbstractMongoInstanceProperties mongo) {
try {
final MongoClientOptionsFactoryBean bean = new MongoClientOptionsFactoryBean();
bean.setWriteConcern(WriteConcern.valueOf(mongo.getWriteConcern()));
bean.setHeartbeatConnectTimeout(Long.valueOf(mongo.getTimeout()).intValue());
bean.setHeartbeatSocketTimeout(Long.valueOf(mongo.getTimeout()).intValue());
bean.setMaxConnectionLifeTime(mongo.getConns().getLifetime());
bean.setSocketKeepAlive(mongo.isSocketKeepAlive());
bean.setMaxConnectionIdleTime(Long.valueOf(mongo.getIdleTimeout()).intValue());
bean.setConnectionsPerHost(mongo.getConns().getPerHost());
bean.setSocketTimeout(Long.valueOf(mongo.getTimeout()).intValue());
bean.setConnectTimeout(Long.valueOf(mongo.getTimeout()).intValue());
bean.afterPropertiesSet();
return bean;
} catch (final Exception e) {
throw new BeanCreationException(e.getMessage(), e);
}
}
/**
* New mongo db client options.
*
* @param mongo the mongo
* @return the mongo client options
*/
public static MongoClientOptions newMongoDbClientOptions(final AbstractMongoInstanceProperties mongo) {
try {
return newMongoDbClientOptionsFactoryBean(mongo).getObject();
} catch (final Exception e) {
throw new BeanCreationException(e.getMessage(), e);
}
}
/**
* New mongo db client.
*
* @param mongo the mongo
* @return the mongo
*/
public static Mongo newMongoDbClient(final AbstractMongoInstanceProperties mongo) {
return new MongoClient(new ServerAddress(
mongo.getHost(),
mongo.getPort()),
Collections.singletonList(
MongoCredential.createCredential(
mongo.getUserId(),
mongo.getDatabaseName(),
mongo.getPassword().toCharArray())),
newMongoDbClientOptions(mongo));
}
/**
* New ldap authenticator.
*
* @param l the ldap settings.
* @return the authenticator
*/
public static Authenticator newLdaptiveAuthenticator(final AbstractLdapAuthenticationProperties l) {
switch (l.getType()) {
case AD:
LOGGER.debug("Creating active directory authenticator for [{}]", l.getLdapUrl());
return getActiveDirectoryAuthenticator(l);
case DIRECT:
LOGGER.debug("Creating direct-bind authenticator for [{}]", l.getLdapUrl());
return getDirectBindAuthenticator(l);
case AUTHENTICATED:
LOGGER.debug("Creating authenticated authenticator for [{}]", l.getLdapUrl());
return getAuthenticatedOrAnonSearchAuthenticator(l);
default:
LOGGER.debug("Creating anonymous authenticator for [{}]", l.getLdapUrl());
return getAuthenticatedOrAnonSearchAuthenticator(l);
}
}
private static Authenticator getAuthenticatedOrAnonSearchAuthenticator(final AbstractLdapAuthenticationProperties l) {
if (StringUtils.isBlank(l.getBaseDn())) {
throw new IllegalArgumentException("Base dn cannot be empty/blank for authenticated/anonymous authentication");
}
if (StringUtils.isBlank(l.getUserFilter())) {
throw new IllegalArgumentException("User filter cannot be empty/blank for authenticated/anonymous authentication");
}
final PooledConnectionFactory connectionFactoryForSearch = Beans.newLdaptivePooledConnectionFactory(l);
final PooledSearchDnResolver resolver = new PooledSearchDnResolver();
resolver.setBaseDn(l.getBaseDn());
resolver.setSubtreeSearch(l.isSubtreeSearch());
resolver.setAllowMultipleDns(l.isAllowMultipleDns());
resolver.setConnectionFactory(connectionFactoryForSearch);
resolver.setUserFilter(l.getUserFilter());
final Authenticator auth;
if (StringUtils.isBlank(l.getPrincipalAttributePassword())) {
auth = new Authenticator(resolver, getPooledBindAuthenticationHandler(l, Beans.newLdaptivePooledConnectionFactory(l)));
} else {
auth = new Authenticator(resolver, getPooledCompareAuthenticationHandler(l, Beans.newLdaptivePooledConnectionFactory(l)));
}
if (l.isEnhanceWithEntryResolver()) {
auth.setEntryResolver(Beans.newLdaptiveSearchEntryResolver(l, Beans.newLdaptivePooledConnectionFactory(l)));
}
return auth;
}
private static Authenticator getDirectBindAuthenticator(final AbstractLdapAuthenticationProperties 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, Beans.newLdaptivePooledConnectionFactory(l)));
if (l.isEnhanceWithEntryResolver()) {
authenticator.setEntryResolver(Beans.newLdaptiveSearchEntryResolver(l, Beans.newLdaptivePooledConnectionFactory(l)));
}
return authenticator;
}
private static Authenticator getActiveDirectoryAuthenticator(final AbstractLdapAuthenticationProperties 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, Beans.newLdaptivePooledConnectionFactory(l)));
if (l.isEnhanceWithEntryResolver()) {
authn.setEntryResolver(Beans.newLdaptiveSearchEntryResolver(l, Beans.newLdaptivePooledConnectionFactory(l)));
}
return authn;
}
private static PooledBindAuthenticationHandler getPooledBindAuthenticationHandler(final AbstractLdapAuthenticationProperties l,
final PooledConnectionFactory factory) {
final PooledBindAuthenticationHandler handler = new PooledBindAuthenticationHandler(factory);
handler.setAuthenticationControls(new PasswordPolicyControl());
return handler;
}
private static PooledCompareAuthenticationHandler getPooledCompareAuthenticationHandler(final AbstractLdapAuthenticationProperties l,
final PooledConnectionFactory factory) {
final PooledCompareAuthenticationHandler handler = new PooledCompareAuthenticationHandler(factory);
handler.setPasswordAttribute(l.getPrincipalAttributePassword());
return handler;
}
}