LdaptiveProperties.java
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.ldaptive;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.ldaptive.ActivePassiveConnectionStrategy;
import org.ldaptive.BindConnectionInitializer;
import org.ldaptive.ConnectionConfig;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.ConnectionInitializer;
import org.ldaptive.ConnectionValidator;
import org.ldaptive.DefaultConnectionFactory;
import org.ldaptive.DnsSrvConnectionStrategy;
import org.ldaptive.PooledConnectionFactory;
import org.ldaptive.RandomConnectionStrategy;
import org.ldaptive.RetryMetadata;
import org.ldaptive.ReturnAttributes;
import org.ldaptive.RoundRobinConnectionStrategy;
import org.ldaptive.SearchConnectionValidator;
import org.ldaptive.SearchRequest;
import org.ldaptive.SearchScope;
import org.ldaptive.ad.extended.FastBindConnectionInitializer;
import org.ldaptive.pool.IdlePruneStrategy;
import org.ldaptive.ssl.AllowAnyHostnameVerifier;
import org.ldaptive.ssl.CertificateHostnameVerifier;
import org.ldaptive.ssl.DefaultHostnameVerifier;
import org.ldaptive.ssl.SslConfig;
import org.ldaptive.ssl.X509CredentialConfig;
/**
* The ldap properties.
*
* @author Christian Bremer
*/
@Getter
@Setter
@ToString(exclude = {"bindCredentials"})
@EqualsAndHashCode(exclude = {"bindCredentials"})
public class LdaptiveProperties {
/**
* Specifies whether the connection configuration is immutable or not.
*/
private boolean immutable;
/**
* URL of the LDAP server(s) separated by space. For example
* {@code ldaps://ldap1.example.org:636 ldaps://ldap2.example.org:636}
*/
private String ldapUrl;
/**
* Duration of time that connects will block.
*/
private Duration connectTimeout = Duration.ofMinutes(1);
/**
* Duration of time to wait for startTLS responses.
*/
private Duration startTlsTimeout = Duration.ofMinutes(1);
/**
* Duration of time to wait for responses.
*/
private Duration responseTimeout = Duration.ofMinutes(1);
/**
* Duration of time that operations will block on reconnects, should generally be longer than
* connect timeout.
*/
private Duration reconnectTimeout = Duration.ofMinutes(2);
/**
* Whether to automatically reconnect to the server when a connection is lost. Default is true.
*/
private boolean autoReconnect = true;
/**
* The reconnect strategy.
*/
private ReconnectStrategy reconnectStrategy = ReconnectStrategy.ONE_RECONNECT_ATTEMPT;
/**
* Whether pending operations should be replayed after a reconnect. Default is true.
*/
private boolean autoReplay = true;
/**
* The ssl configuration.
*/
private SslProperties sslConfig = new SslProperties();
/**
* Connect to LDAP using startTLS.
*/
private boolean useStartTls;
/**
* DN to bind as before performing operations.
*/
private String bindDn;
/**
* Credential for the bind DN.
*/
private String bindCredentials;
/**
* Perform a fast bind, if no credentials are present.
*/
private boolean fastBind = false;
/**
* The connection strategy.
*/
private ConnectionStrategy connectionStrategy = ConnectionStrategy.ACTIVE_PASSIVE;
/**
* The connection validator.
*/
private ConnectionValidatorProperties connectionValidator = new ConnectionValidatorProperties();
/**
* Specifies whether the connection should be pooled or not. Default is {@code false}.
*/
private boolean pooled = false;
/**
* The connection pool configuration.
*/
private ConnectionPoolProperties connectionPool = new ConnectionPoolProperties();
/**
* Instantiates new ldaptive properties.
*/
public LdaptiveProperties() {
super();
}
/**
* Creates the connection config.
*
* @return the connection config
*/
public ConnectionConfig createConnectionConfig() {
ConnectionInitializer[] connectionInitializers;
if (hasText(getBindDn()) && hasText(getBindCredentials())) {
connectionInitializers = new ConnectionInitializer[]{
BindConnectionInitializer.builder()
.dn(getBindDn())
.credential(getBindCredentials())
.build()
};
} else if (isFastBind()) {
connectionInitializers = new ConnectionInitializer[]{
new FastBindConnectionInitializer()
};
} else {
connectionInitializers = new ConnectionInitializer[]{};
}
ConnectionConfig connectionConfig = ConnectionConfig.builder()
.connectTimeout(getConnectTimeout())
.startTLSTimeout(getStartTlsTimeout())
.responseTimeout(getResponseTimeout())
.reconnectTimeout(getReconnectTimeout())
.autoReconnect(isAutoReconnect())
.autoReconnectCondition(getReconnectStrategy().get())
.autoReplay(isAutoReplay())
.sslConfig(getSslConfig().createSslConfig())
.useStartTLS(isUseStartTls())
.connectionInitializers(connectionInitializers)
.connectionStrategy(getConnectionStrategy().get())
.connectionValidator(getConnectionValidator().createConnectionValidator())
.url(getLdapUrl())
.build();
if (isImmutable()) {
connectionConfig.freeze();
}
return connectionConfig;
}
/**
* Create then connection factory.
*
* @return the connection factory
*/
public ConnectionFactory createConnectionFactory() {
if (isPooled()) {
ConnectionPoolProperties properties = getConnectionPool();
PooledConnectionFactory factory = PooledConnectionFactory.builder()
.config(createConnectionConfig())
.blockWaitTime(properties.getBlockWaitTime())
.connectOnCreate(properties.isConnectOnCreate())
.failFastInitialize(properties.isFailFastInitialize())
.max(properties.getMaxPoolSize())
.min(properties.getMinPoolSize())
.pruneStrategy(
new IdlePruneStrategy(properties.getPrunePeriod(), properties.getIdleTime()))
.validateOnCheckIn(properties.isValidateOnCheckIn())
.validateOnCheckOut(properties.isValidateOnCheckOut())
.validatePeriodically(properties.isValidatePeriodically())
.validator(properties.getValidator().createConnectionValidator())
.build();
factory.initialize();
return factory;
}
return new DefaultConnectionFactory(createConnectionConfig());
}
/**
* The reconnection strategy.
*/
public enum ReconnectStrategy implements Supplier<Predicate<RetryMetadata>> {
/**
* One reconnect attempt strategy.
*/
ONE_RECONNECT_ATTEMPT(ConnectionConfig.ONE_RECONNECT_ATTEMPT),
/**
* Infinite reconnect attempts strategy.
*/
INFINITE_RECONNECT_ATTEMPTS(ConnectionConfig.INFINITE_RECONNECT_ATTEMPTS),
/**
* Infinite reconnect attempts with backoff strategy.
*/
INFINITE_RECONNECT_ATTEMPTS_WITH_BACKOFF(
ConnectionConfig.INFINITE_RECONNECT_ATTEMPTS_WITH_BACKOFF);
private final Predicate<RetryMetadata> condition;
ReconnectStrategy(Predicate<RetryMetadata> condition) {
this.condition = condition;
}
@Override
public Predicate<RetryMetadata> get() {
return condition;
}
}
/**
* The ssl configuration.
*/
@Data
public static class SslProperties {
/**
* Path of the trust certificates to use for the SSL connection.
*/
private String trustCertificates;
/**
* Path of the authentication certificate to use for the SSL connection.
*/
private String authenticationCertificate;
/**
* Path of the key to use for the SSL connection.
*/
private String authenticationKey;
/**
* The hostname verifier.
*/
private HostnameVerifier hostnameVerifier = HostnameVerifier.DEFAULT;
/**
* Instantiates new ssl properties.
*/
public SslProperties() {
super();
}
/**
* Create ssl config.
*
* @return the ssl config
*/
public SslConfig createSslConfig() {
if (hasText(getTrustCertificates())
|| hasText(getAuthenticationCertificate())
|| hasText(getAuthenticationKey())) {
X509CredentialConfig x509 = new X509CredentialConfig();
if (hasText(getAuthenticationCertificate())) {
x509.setAuthenticationCertificate(getAuthenticationCertificate());
}
if (hasText(getAuthenticationKey())) {
x509.setAuthenticationKey(getAuthenticationKey());
}
if (hasText(getTrustCertificates())) {
x509.setTrustCertificates(getTrustCertificates());
}
SslConfig sc = new SslConfig();
sc.setCredentialConfig(x509);
sc.setHostnameVerifier(getHostnameVerifier().get());
return sc;
}
return null;
}
/**
* The hostname verifier.
*/
public enum HostnameVerifier implements Supplier<CertificateHostnameVerifier> {
/**
* The default hostname verifier.
*/
DEFAULT(new DefaultHostnameVerifier()),
/**
* Hostname verifier that returns true for any hostname. Use with caution.
*/
ALLOW_ANY(new AllowAnyHostnameVerifier());
private final CertificateHostnameVerifier verifier;
HostnameVerifier(CertificateHostnameVerifier verifier) {
this.verifier = verifier;
}
@Override
public CertificateHostnameVerifier get() {
return verifier;
}
}
}
/**
* The connection strategy.
*/
public enum ConnectionStrategy implements Supplier<org.ldaptive.ConnectionStrategy> {
/**
* Attempt each URL in the order provided for each connection. The URLs are always tried in the
* order in which they were provided.
*/
ACTIVE_PASSIVE(new ActivePassiveConnectionStrategy()),
/**
* Attempt a random URL from a list of URLs.
*/
RANDOM(new RandomConnectionStrategy()),
/**
* Attempt the next URL in the order provided for each connection. URLs are rotated regardless
* of connection success or failure.
*/
ROUND_ROBIN(new RoundRobinConnectionStrategy()),
/**
* Queries a DNS server for SRV records and uses those records to construct a list of URLs. When
* configuring this strategy you must use your DNS server for {@code ldapUrl} in the form
* {@code dns://my.server.com}.
*/
DNS(new DnsSrvConnectionStrategy());
private final org.ldaptive.ConnectionStrategy strategy;
ConnectionStrategy(org.ldaptive.ConnectionStrategy strategy) {
this.strategy = strategy;
}
@Override
public org.ldaptive.ConnectionStrategy get() {
return strategy;
}
}
/**
* The search validator properties.
*/
@Data
public static class ConnectionValidatorProperties {
/**
* Validation period.
*/
private Duration validatePeriod = Duration.ofMinutes(30);
/**
* Maximum length of time a connection validation should block.
*/
private Duration validateTimeout = Duration.ofSeconds(5);
/**
* The search request.
*/
private SearchRequestProperties searchRequest = new SearchRequestProperties();
/**
* Instantiates new connection validator properties.
*/
public ConnectionValidatorProperties() {
super();
}
/**
* Create connection validator.
*
* @return the connection validator
*/
public ConnectionValidator createConnectionValidator() {
if (hasText(getSearchRequest().getBaseDn())) {
return new SearchConnectionValidator(
validatePeriod,
validateTimeout,
getSearchRequest().createSearchRequest());
}
return null;
}
/**
* The search request properties.
*/
@Data
public static class SearchRequestProperties {
/**
* The base dn (like {@code ou=peoples,dc=example,dc=org}).
*/
private String baseDn;
/**
* The search filter.
*/
private SearchFilterProperties searchFilter = new SearchFilterProperties();
/**
* The size limit.
*/
private Integer sizeLimit;
/**
* The search scope.
*/
private SearchScope searchScope;
/**
* The return attributes.
*/
private List<String> returnAttributes = new ArrayList<>();
/**
* Instantiates new search request properties.
*/
public SearchRequestProperties() {
super();
}
/**
* Gets the return attributes as array.
*
* @return the return attributes as array
*/
public String[] returnAttributesAsArray() {
if (returnAttributes.isEmpty()) {
return ReturnAttributes.NONE.value();
}
return returnAttributes.toArray(new String[0]);
}
/**
* Create search request.
*
* @return the search request
*/
public SearchRequest createSearchRequest() {
String nonNullBaseDn = Objects.requireNonNullElse(getBaseDn(), "");
SearchRequest searchRequest;
if (Objects.nonNull(getSearchFilter()) && hasText(getSearchFilter().getFilter())) {
searchRequest = new SearchRequest(nonNullBaseDn);
searchRequest.setFilter(getSearchFilter().getFilter());
searchRequest.setReturnAttributes(returnAttributesAsArray());
if (getSearchScope() != null) {
searchRequest.setSearchScope(getSearchScope());
}
if (getSizeLimit() != null) {
searchRequest.setSizeLimit(getSizeLimit());
}
} else {
searchRequest = SearchRequest
.objectScopeSearchRequest(nonNullBaseDn, returnAttributesAsArray());
}
return searchRequest;
}
/**
* The search filter properties.
*/
@Data
public static class SearchFilterProperties {
/**
* The search filter (like {@code (&(objectClass=inetOrgPerson)(uid=administrator))}).
*/
private String filter;
/**
* Instantiates new search filter properties.
*/
public SearchFilterProperties() {
super();
}
}
}
}
/**
* The connection pool properties.
*/
@Data
public static class ConnectionPoolProperties {
/**
* Duration to wait for an available connection.
*/
private Duration blockWaitTime = Duration.ofMinutes(1);
/**
* Minimum pool size.
*/
private int minPoolSize = 3;
/**
* Maximum pool size.
*/
private int maxPoolSize = 10;
/**
* Whether to connect to the ldap on connection creation.
*/
private boolean connectOnCreate = true;
/**
* Whether initialize should throw if pooling configuration requirements are not met.
*/
private boolean failFastInitialize = true;
/**
* Whether the ldap object should be validated when returned to the pool.
*/
private boolean validateOnCheckIn = false;
/**
* Whether the ldap object should be validated when given from the pool.
*/
private boolean validateOnCheckOut = false;
/**
* Whether the pool should be validated periodically.
*/
private boolean validatePeriodically = false;
/**
* The validator for the connections in the pool.
*/
private ConnectionValidatorProperties validator = new ConnectionValidatorProperties();
/**
* Prune period.
*/
private Duration prunePeriod = Duration.ofMinutes(5);
/**
* Idle time.
*/
private Duration idleTime = Duration.ofMinutes(10);
/**
* Instantiates new connection pool properties.
*/
public ConnectionPoolProperties() {
super();
}
}
/**
* Determines whether the given value has text or not.
*
* @param value the value (can be null)
* @return the {@code true}, if the value has text, otherwise {@code false}
*/
protected static boolean hasText(String value) {
return Objects.nonNull(value) && !value.isBlank();
}
}