View Javadoc
1   /*
2    * Copyright 2019 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.ldaptive;
18  
19  import java.time.Duration;
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.Objects;
23  import java.util.function.Predicate;
24  import java.util.function.Supplier;
25  import lombok.Data;
26  import lombok.EqualsAndHashCode;
27  import lombok.Getter;
28  import lombok.Setter;
29  import lombok.ToString;
30  import org.ldaptive.ActivePassiveConnectionStrategy;
31  import org.ldaptive.BindConnectionInitializer;
32  import org.ldaptive.ConnectionConfig;
33  import org.ldaptive.ConnectionFactory;
34  import org.ldaptive.ConnectionInitializer;
35  import org.ldaptive.ConnectionValidator;
36  import org.ldaptive.DefaultConnectionFactory;
37  import org.ldaptive.DnsSrvConnectionStrategy;
38  import org.ldaptive.PooledConnectionFactory;
39  import org.ldaptive.RandomConnectionStrategy;
40  import org.ldaptive.RetryMetadata;
41  import org.ldaptive.ReturnAttributes;
42  import org.ldaptive.RoundRobinConnectionStrategy;
43  import org.ldaptive.SearchConnectionValidator;
44  import org.ldaptive.SearchRequest;
45  import org.ldaptive.SearchScope;
46  import org.ldaptive.ad.extended.FastBindConnectionInitializer;
47  import org.ldaptive.pool.IdlePruneStrategy;
48  import org.ldaptive.ssl.AllowAnyHostnameVerifier;
49  import org.ldaptive.ssl.CertificateHostnameVerifier;
50  import org.ldaptive.ssl.DefaultHostnameVerifier;
51  import org.ldaptive.ssl.SslConfig;
52  import org.ldaptive.ssl.X509CredentialConfig;
53  
54  /**
55   * The ldap properties.
56   *
57   * @author Christian Bremer
58   */
59  @Getter
60  @Setter
61  @ToString(exclude = {"bindCredentials"})
62  @EqualsAndHashCode(exclude = {"bindCredentials"})
63  public class LdaptiveProperties {
64  
65    /**
66     * Specifies whether the connection configuration is immutable or not.
67     */
68    private boolean immutable;
69  
70    /**
71     * URL of the LDAP server(s) separated by space. For example
72     * {@code ldaps://ldap1.example.org:636 ldaps://ldap2.example.org:636}
73     */
74    private String ldapUrl;
75  
76    /**
77     * Duration of time that connects will block.
78     */
79    private Duration connectTimeout = Duration.ofMinutes(1);
80  
81    /**
82     * Duration of time to wait for startTLS responses.
83     */
84    private Duration startTlsTimeout = Duration.ofMinutes(1);
85  
86    /**
87     * Duration of time to wait for responses.
88     */
89    private Duration responseTimeout = Duration.ofMinutes(1);
90  
91    /**
92     * Duration of time that operations will block on reconnects, should generally be longer than
93     * connect timeout.
94     */
95    private Duration reconnectTimeout = Duration.ofMinutes(2);
96  
97    /**
98     * Whether to automatically reconnect to the server when a connection is lost. Default is true.
99     */
100   private boolean autoReconnect = true;
101 
102   /**
103    * The reconnect strategy.
104    */
105   private ReconnectStrategy reconnectStrategy = ReconnectStrategy.ONE_RECONNECT_ATTEMPT;
106 
107   /**
108    * Whether pending operations should be replayed after a reconnect. Default is true.
109    */
110   private boolean autoReplay = true;
111 
112   /**
113    * The ssl configuration.
114    */
115   private SslProperties sslConfig = new SslProperties();
116 
117   /**
118    * Connect to LDAP using startTLS.
119    */
120   private boolean useStartTls;
121 
122   /**
123    * DN to bind as before performing operations.
124    */
125   private String bindDn;
126 
127   /**
128    * Credential for the bind DN.
129    */
130   private String bindCredentials;
131 
132   /**
133    * Perform a fast bind, if no credentials are present.
134    */
135   private boolean fastBind = false;
136 
137   /**
138    * The connection strategy.
139    */
140   private ConnectionStrategy connectionStrategy = ConnectionStrategy.ACTIVE_PASSIVE;
141 
142   /**
143    * The connection validator.
144    */
145   private ConnectionValidatorProperties connectionValidator = new ConnectionValidatorProperties();
146 
147   /**
148    * Specifies whether the connection should be pooled or not. Default is {@code false}.
149    */
150   private boolean pooled = false;
151 
152   /**
153    * The connection pool configuration.
154    */
155   private ConnectionPoolProperties connectionPool = new ConnectionPoolProperties();
156 
157   /**
158    * Instantiates new ldaptive properties.
159    */
160   public LdaptiveProperties() {
161     super();
162   }
163 
164   /**
165    * Creates the connection config.
166    *
167    * @return the connection config
168    */
169   public ConnectionConfig createConnectionConfig() {
170     ConnectionInitializer[] connectionInitializers;
171     if (hasText(getBindDn()) && hasText(getBindCredentials())) {
172       connectionInitializers = new ConnectionInitializer[]{
173           BindConnectionInitializer.builder()
174               .dn(getBindDn())
175               .credential(getBindCredentials())
176               .build()
177       };
178     } else if (isFastBind()) {
179       connectionInitializers = new ConnectionInitializer[]{
180           new FastBindConnectionInitializer()
181       };
182     } else {
183       connectionInitializers = new ConnectionInitializer[]{};
184     }
185     ConnectionConfig connectionConfig = ConnectionConfig.builder()
186         .connectTimeout(getConnectTimeout())
187         .startTLSTimeout(getStartTlsTimeout())
188         .responseTimeout(getResponseTimeout())
189         .reconnectTimeout(getReconnectTimeout())
190         .autoReconnect(isAutoReconnect())
191         .autoReconnectCondition(getReconnectStrategy().get())
192         .autoReplay(isAutoReplay())
193         .sslConfig(getSslConfig().createSslConfig())
194         .useStartTLS(isUseStartTls())
195         .connectionInitializers(connectionInitializers)
196         .connectionStrategy(getConnectionStrategy().get())
197         .connectionValidator(getConnectionValidator().createConnectionValidator())
198         .url(getLdapUrl())
199         .build();
200     if (isImmutable()) {
201       connectionConfig.freeze();
202     }
203     return connectionConfig;
204   }
205 
206   /**
207    * Create then connection factory.
208    *
209    * @return the connection factory
210    */
211   public ConnectionFactory createConnectionFactory() {
212     if (isPooled()) {
213       ConnectionPoolProperties properties = getConnectionPool();
214       PooledConnectionFactory factory = PooledConnectionFactory.builder()
215           .config(createConnectionConfig())
216           .blockWaitTime(properties.getBlockWaitTime())
217           .connectOnCreate(properties.isConnectOnCreate())
218           .failFastInitialize(properties.isFailFastInitialize())
219           .max(properties.getMaxPoolSize())
220           .min(properties.getMinPoolSize())
221           .pruneStrategy(
222               new IdlePruneStrategy(properties.getPrunePeriod(), properties.getIdleTime()))
223           .validateOnCheckIn(properties.isValidateOnCheckIn())
224           .validateOnCheckOut(properties.isValidateOnCheckOut())
225           .validatePeriodically(properties.isValidatePeriodically())
226           .validator(properties.getValidator().createConnectionValidator())
227           .build();
228       factory.initialize();
229       return factory;
230     }
231     return new DefaultConnectionFactory(createConnectionConfig());
232   }
233 
234   /**
235    * The reconnection strategy.
236    */
237   public enum ReconnectStrategy implements Supplier<Predicate<RetryMetadata>> {
238 
239     /**
240      * One reconnect attempt strategy.
241      */
242     ONE_RECONNECT_ATTEMPT(ConnectionConfig.ONE_RECONNECT_ATTEMPT),
243 
244     /**
245      * Infinite reconnect attempts strategy.
246      */
247     INFINITE_RECONNECT_ATTEMPTS(ConnectionConfig.INFINITE_RECONNECT_ATTEMPTS),
248 
249     /**
250      * Infinite reconnect attempts with backoff strategy.
251      */
252     INFINITE_RECONNECT_ATTEMPTS_WITH_BACKOFF(
253         ConnectionConfig.INFINITE_RECONNECT_ATTEMPTS_WITH_BACKOFF);
254 
255     private final Predicate<RetryMetadata> condition;
256 
257     ReconnectStrategy(Predicate<RetryMetadata> condition) {
258       this.condition = condition;
259     }
260 
261     @Override
262     public Predicate<RetryMetadata> get() {
263       return condition;
264     }
265   }
266 
267   /**
268    * The ssl configuration.
269    */
270   @Data
271   public static class SslProperties {
272 
273     /**
274      * Path of the trust certificates to use for the SSL connection.
275      */
276     private String trustCertificates;
277 
278     /**
279      * Path of the authentication certificate to use for the SSL connection.
280      */
281     private String authenticationCertificate;
282 
283     /**
284      * Path of the key to use for the SSL connection.
285      */
286     private String authenticationKey;
287 
288     /**
289      * The hostname verifier.
290      */
291     private HostnameVerifier hostnameVerifier = HostnameVerifier.DEFAULT;
292 
293     /**
294      * Instantiates new ssl properties.
295      */
296     public SslProperties() {
297       super();
298     }
299 
300     /**
301      * Create ssl config.
302      *
303      * @return the ssl config
304      */
305     public SslConfig createSslConfig() {
306       if (hasText(getTrustCertificates())
307           || hasText(getAuthenticationCertificate())
308           || hasText(getAuthenticationKey())) {
309 
310         X509CredentialConfig x509 = new X509CredentialConfig();
311         if (hasText(getAuthenticationCertificate())) {
312           x509.setAuthenticationCertificate(getAuthenticationCertificate());
313         }
314         if (hasText(getAuthenticationKey())) {
315           x509.setAuthenticationKey(getAuthenticationKey());
316         }
317         if (hasText(getTrustCertificates())) {
318           x509.setTrustCertificates(getTrustCertificates());
319         }
320         SslConfig sc = new SslConfig();
321         sc.setCredentialConfig(x509);
322         sc.setHostnameVerifier(getHostnameVerifier().get());
323         return sc;
324       }
325       return null;
326     }
327 
328     /**
329      * The hostname verifier.
330      */
331     public enum HostnameVerifier implements Supplier<CertificateHostnameVerifier> {
332 
333       /**
334        * The default hostname verifier.
335        */
336       DEFAULT(new DefaultHostnameVerifier()),
337 
338       /**
339        * Hostname verifier that returns true for any hostname. Use with caution.
340        */
341       ALLOW_ANY(new AllowAnyHostnameVerifier());
342 
343       private final CertificateHostnameVerifier verifier;
344 
345       HostnameVerifier(CertificateHostnameVerifier verifier) {
346         this.verifier = verifier;
347       }
348 
349       @Override
350       public CertificateHostnameVerifier get() {
351         return verifier;
352       }
353     }
354   }
355 
356   /**
357    * The connection strategy.
358    */
359   public enum ConnectionStrategy implements Supplier<org.ldaptive.ConnectionStrategy> {
360 
361     /**
362      * Attempt each URL in the order provided for each connection. The URLs are always tried in the
363      * order in which they were provided.
364      */
365     ACTIVE_PASSIVE(new ActivePassiveConnectionStrategy()),
366 
367     /**
368      * Attempt a random URL from a list of URLs.
369      */
370     RANDOM(new RandomConnectionStrategy()),
371 
372     /**
373      * Attempt the next URL in the order provided for each connection. URLs are rotated regardless
374      * of connection success or failure.
375      */
376     ROUND_ROBIN(new RoundRobinConnectionStrategy()),
377 
378     /**
379      * Queries a DNS server for SRV records and uses those records to construct a list of URLs. When
380      * configuring this strategy you must use your DNS server for {@code ldapUrl} in the form
381      * {@code dns://my.server.com}.
382      */
383     DNS(new DnsSrvConnectionStrategy());
384 
385     private final org.ldaptive.ConnectionStrategy strategy;
386 
387     ConnectionStrategy(org.ldaptive.ConnectionStrategy strategy) {
388       this.strategy = strategy;
389     }
390 
391     @Override
392     public org.ldaptive.ConnectionStrategy get() {
393       return strategy;
394     }
395   }
396 
397   /**
398    * The search validator properties.
399    */
400   @Data
401   public static class ConnectionValidatorProperties {
402 
403     /**
404      * Validation period.
405      */
406     private Duration validatePeriod = Duration.ofMinutes(30);
407 
408     /**
409      * Maximum length of time a connection validation should block.
410      */
411     private Duration validateTimeout = Duration.ofSeconds(5);
412 
413     /**
414      * The search request.
415      */
416     private SearchRequestProperties searchRequest = new SearchRequestProperties();
417 
418     /**
419      * Instantiates new connection validator properties.
420      */
421     public ConnectionValidatorProperties() {
422       super();
423     }
424 
425     /**
426      * Create connection validator.
427      *
428      * @return the connection validator
429      */
430     public ConnectionValidator createConnectionValidator() {
431       if (hasText(getSearchRequest().getBaseDn())) {
432         return new SearchConnectionValidator(
433             validatePeriod,
434             validateTimeout,
435             getSearchRequest().createSearchRequest());
436       }
437       return null;
438     }
439 
440     /**
441      * The search request properties.
442      */
443     @Data
444     public static class SearchRequestProperties {
445 
446       /**
447        * The base dn (like {@code ou=peoples,dc=example,dc=org}).
448        */
449       private String baseDn;
450 
451       /**
452        * The search filter.
453        */
454       private SearchFilterProperties searchFilter = new SearchFilterProperties();
455 
456       /**
457        * The size limit.
458        */
459       private Integer sizeLimit;
460 
461       /**
462        * The search scope.
463        */
464       private SearchScope searchScope;
465 
466       /**
467        * The return attributes.
468        */
469       private List<String> returnAttributes = new ArrayList<>();
470 
471       /**
472        * Instantiates new search request properties.
473        */
474       public SearchRequestProperties() {
475         super();
476       }
477 
478       /**
479        * Gets the return attributes as array.
480        *
481        * @return the return attributes as array
482        */
483       public String[] returnAttributesAsArray() {
484         if (returnAttributes.isEmpty()) {
485           return ReturnAttributes.NONE.value();
486         }
487         return returnAttributes.toArray(new String[0]);
488       }
489 
490       /**
491        * Create search request.
492        *
493        * @return the search request
494        */
495       public SearchRequest createSearchRequest() {
496         String nonNullBaseDn = Objects.requireNonNullElse(getBaseDn(), "");
497         SearchRequest searchRequest;
498         if (Objects.nonNull(getSearchFilter()) && hasText(getSearchFilter().getFilter())) {
499           searchRequest = new SearchRequest(nonNullBaseDn);
500           searchRequest.setFilter(getSearchFilter().getFilter());
501           searchRequest.setReturnAttributes(returnAttributesAsArray());
502           if (getSearchScope() != null) {
503             searchRequest.setSearchScope(getSearchScope());
504           }
505           if (getSizeLimit() != null) {
506             searchRequest.setSizeLimit(getSizeLimit());
507           }
508         } else {
509           searchRequest = SearchRequest
510               .objectScopeSearchRequest(nonNullBaseDn, returnAttributesAsArray());
511         }
512         return searchRequest;
513       }
514 
515       /**
516        * The search filter properties.
517        */
518       @Data
519       public static class SearchFilterProperties {
520 
521         /**
522          * The search filter (like {@code (&(objectClass=inetOrgPerson)(uid=administrator))}).
523          */
524         private String filter;
525 
526         /**
527          * Instantiates new search filter properties.
528          */
529         public SearchFilterProperties() {
530           super();
531         }
532       }
533     }
534   }
535 
536   /**
537    * The connection pool properties.
538    */
539   @Data
540   public static class ConnectionPoolProperties {
541 
542     /**
543      * Duration to wait for an available connection.
544      */
545     private Duration blockWaitTime = Duration.ofMinutes(1);
546 
547     /**
548      * Minimum pool size.
549      */
550     private int minPoolSize = 3;
551 
552     /**
553      * Maximum pool size.
554      */
555     private int maxPoolSize = 10;
556 
557     /**
558      * Whether to connect to the ldap on connection creation.
559      */
560     private boolean connectOnCreate = true;
561 
562     /**
563      * Whether initialize should throw if pooling configuration requirements are not met.
564      */
565     private boolean failFastInitialize = true;
566 
567     /**
568      * Whether the ldap object should be validated when returned to the pool.
569      */
570     private boolean validateOnCheckIn = false;
571 
572     /**
573      * Whether the ldap object should be validated when given from the pool.
574      */
575     private boolean validateOnCheckOut = false;
576 
577     /**
578      * Whether the pool should be validated periodically.
579      */
580     private boolean validatePeriodically = false;
581 
582     /**
583      * The validator for the connections in the pool.
584      */
585     private ConnectionValidatorProperties validator = new ConnectionValidatorProperties();
586 
587     /**
588      * Prune period.
589      */
590     private Duration prunePeriod = Duration.ofMinutes(5);
591 
592     /**
593      * Idle time.
594      */
595     private Duration idleTime = Duration.ofMinutes(10);
596 
597     /**
598      * Instantiates new connection pool properties.
599      */
600     public ConnectionPoolProperties() {
601       super();
602     }
603 
604   }
605 
606   /**
607    * Determines whether the given value has text or not.
608    *
609    * @param value the value (can be null)
610    * @return the {@code true}, if the value has text, otherwise {@code false}
611    */
612   protected static boolean hasText(String value) {
613     return Objects.nonNull(value) && !value.isBlank();
614   }
615 }