xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapServer.java (revision c6f0939b1c668e9f8e1e276424439c3106b3a029)
1b28a5538SAdam Hornacek /*
2b28a5538SAdam Hornacek  * CDDL HEADER START
3b28a5538SAdam Hornacek  *
4b28a5538SAdam Hornacek  * The contents of this file are subject to the terms of the
5b28a5538SAdam Hornacek  * Common Development and Distribution License (the "License").
6b28a5538SAdam Hornacek  * You may not use this file except in compliance with the License.
7b28a5538SAdam Hornacek  *
8b28a5538SAdam Hornacek  * See LICENSE.txt included in this distribution for the specific
9b28a5538SAdam Hornacek  * language governing permissions and limitations under the License.
10b28a5538SAdam Hornacek  *
11b28a5538SAdam Hornacek  * When distributing Covered Code, include this CDDL HEADER in each
12b28a5538SAdam Hornacek  * file and include the License file at LICENSE.txt.
13b28a5538SAdam Hornacek  * If applicable, add the following below this CDDL HEADER, with the
14b28a5538SAdam Hornacek  * fields enclosed by brackets "[]" replaced with your own identifying
15b28a5538SAdam Hornacek  * information: Portions Copyright [yyyy] [name of copyright owner]
16b28a5538SAdam Hornacek  *
17b28a5538SAdam Hornacek  * CDDL HEADER END
18b28a5538SAdam Hornacek  */
19b28a5538SAdam Hornacek 
20b28a5538SAdam Hornacek /*
21*c6f0939bSAdam Hornacek  * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
22b28a5538SAdam Hornacek  */
23b28a5538SAdam Hornacek package opengrok.auth.plugin.ldap;
24b28a5538SAdam Hornacek 
2514c8a3ffSVladimir Kotal import java.io.IOException;
26b28a5538SAdam Hornacek import java.io.Serializable;
2714c8a3ffSVladimir Kotal import java.net.InetAddress;
2814c8a3ffSVladimir Kotal import java.net.InetSocketAddress;
2914c8a3ffSVladimir Kotal import java.net.Socket;
3014c8a3ffSVladimir Kotal import java.net.URI;
3114c8a3ffSVladimir Kotal import java.net.URISyntaxException;
3214c8a3ffSVladimir Kotal import java.net.UnknownHostException;
33b28a5538SAdam Hornacek import java.util.Hashtable;
34b28a5538SAdam Hornacek import java.util.logging.Level;
35b28a5538SAdam Hornacek import java.util.logging.Logger;
36b28a5538SAdam Hornacek import javax.naming.CommunicationException;
37b28a5538SAdam Hornacek import javax.naming.Context;
38b28a5538SAdam Hornacek import javax.naming.NamingEnumeration;
39b28a5538SAdam Hornacek import javax.naming.NamingException;
40b28a5538SAdam Hornacek import javax.naming.directory.SearchControls;
41b28a5538SAdam Hornacek import javax.naming.directory.SearchResult;
42b28a5538SAdam Hornacek import javax.naming.ldap.InitialLdapContext;
43b28a5538SAdam Hornacek import javax.naming.ldap.LdapContext;
44b28a5538SAdam Hornacek 
45b28a5538SAdam Hornacek public class LdapServer implements Serializable {
46b28a5538SAdam Hornacek 
47b28a5538SAdam Hornacek     private static final long serialVersionUID = -1;
48b28a5538SAdam Hornacek 
49b28a5538SAdam Hornacek     private static final Logger LOGGER = Logger.getLogger(LdapServer.class.getName());
50b28a5538SAdam Hornacek 
51d0624dbbSVladimir Kotal     private static final String LDAP_CONNECT_TIMEOUT_PARAMETER = "com.sun.jndi.ldap.connect.timeout";
52d0624dbbSVladimir Kotal     private static final String LDAP_READ_TIMEOUT_PARAMETER = "com.sun.jndi.ldap.read.timeout";
53b28a5538SAdam Hornacek     private static final String LDAP_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
54d0624dbbSVladimir Kotal 
55d0624dbbSVladimir Kotal     // default connectTimeout value in milliseconds
56d0624dbbSVladimir Kotal     private static final int LDAP_CONNECT_TIMEOUT = 5000;
57d0624dbbSVladimir Kotal     // default readTimeout value in milliseconds
58d0624dbbSVladimir Kotal     private static final int LDAP_READ_TIMEOUT = 3000;
59b28a5538SAdam Hornacek 
60b28a5538SAdam Hornacek     private String url;
61b28a5538SAdam Hornacek     private String username;
62b28a5538SAdam Hornacek     private String password;
63b28a5538SAdam Hornacek     private int connectTimeout;
64d0624dbbSVladimir Kotal     private int readTimeout;
65b28a5538SAdam Hornacek     private int interval = 10 * 1000;
66b28a5538SAdam Hornacek 
67*c6f0939bSAdam Hornacek     private final Hashtable<String, String> env;
68b28a5538SAdam Hornacek     private LdapContext ctx;
69b28a5538SAdam Hornacek     private long errorTimestamp = 0;
70b28a5538SAdam Hornacek 
LdapServer()71b28a5538SAdam Hornacek     public LdapServer() {
72b28a5538SAdam Hornacek         this(prepareEnv());
73b28a5538SAdam Hornacek     }
74b28a5538SAdam Hornacek 
LdapServer(String server)75b28a5538SAdam Hornacek     public LdapServer(String server) {
76b28a5538SAdam Hornacek         this(prepareEnv());
7714c8a3ffSVladimir Kotal         setName(server);
78b28a5538SAdam Hornacek     }
79b28a5538SAdam Hornacek 
LdapServer(String server, String username, String password)80efd6ebf0SVladimir Kotal     public LdapServer(String server, String username, String password) {
81efd6ebf0SVladimir Kotal         this(prepareEnv());
8214c8a3ffSVladimir Kotal         setName(server);
83efd6ebf0SVladimir Kotal         this.username = username;
84efd6ebf0SVladimir Kotal         this.password = password;
85efd6ebf0SVladimir Kotal     }
86efd6ebf0SVladimir Kotal 
LdapServer(Hashtable<String, String> env)87b28a5538SAdam Hornacek     public LdapServer(Hashtable<String, String> env) {
88b28a5538SAdam Hornacek         this.env = env;
89b28a5538SAdam Hornacek     }
90b28a5538SAdam Hornacek 
getUrl()91b28a5538SAdam Hornacek     public String getUrl() {
92b28a5538SAdam Hornacek         return url;
93b28a5538SAdam Hornacek     }
94b28a5538SAdam Hornacek 
setName(String name)95b28a5538SAdam Hornacek     public LdapServer setName(String name) {
96b28a5538SAdam Hornacek         this.url = name;
97b28a5538SAdam Hornacek         return this;
98b28a5538SAdam Hornacek     }
99b28a5538SAdam Hornacek 
getUsername()100b28a5538SAdam Hornacek     public String getUsername() {
101b28a5538SAdam Hornacek         return username;
102b28a5538SAdam Hornacek     }
103b28a5538SAdam Hornacek 
setUsername(String username)104b28a5538SAdam Hornacek     public LdapServer setUsername(String username) {
105b28a5538SAdam Hornacek         this.username = username;
106b28a5538SAdam Hornacek         return this;
107b28a5538SAdam Hornacek     }
108b28a5538SAdam Hornacek 
getPassword()109b28a5538SAdam Hornacek     public String getPassword() {
110b28a5538SAdam Hornacek         return password;
111b28a5538SAdam Hornacek     }
112b28a5538SAdam Hornacek 
setPassword(String password)113b28a5538SAdam Hornacek     public LdapServer setPassword(String password) {
114b28a5538SAdam Hornacek         this.password = password;
115b28a5538SAdam Hornacek         return this;
116b28a5538SAdam Hornacek     }
117b28a5538SAdam Hornacek 
getConnectTimeout()118b28a5538SAdam Hornacek     public int getConnectTimeout() {
119b28a5538SAdam Hornacek         return connectTimeout;
120b28a5538SAdam Hornacek     }
121b28a5538SAdam Hornacek 
setConnectTimeout(int connectTimeout)122b28a5538SAdam Hornacek     public LdapServer setConnectTimeout(int connectTimeout) {
123b28a5538SAdam Hornacek         this.connectTimeout = connectTimeout;
124b28a5538SAdam Hornacek         return this;
125b28a5538SAdam Hornacek     }
126b28a5538SAdam Hornacek 
getReadTimeout()127d0624dbbSVladimir Kotal     public int getReadTimeout() {
128d0624dbbSVladimir Kotal         return readTimeout;
129d0624dbbSVladimir Kotal     }
130d0624dbbSVladimir Kotal 
setReadTimeout(int readTimeout)131d0624dbbSVladimir Kotal     public LdapServer setReadTimeout(int readTimeout) {
132d0624dbbSVladimir Kotal         this.readTimeout = readTimeout;
133d0624dbbSVladimir Kotal         return this;
134d0624dbbSVladimir Kotal     }
135d0624dbbSVladimir Kotal 
getInterval()136b28a5538SAdam Hornacek     public int getInterval() {
137b28a5538SAdam Hornacek         return interval;
138b28a5538SAdam Hornacek     }
139b28a5538SAdam Hornacek 
setInterval(int interval)140b28a5538SAdam Hornacek     public void setInterval(int interval) {
141b28a5538SAdam Hornacek         this.interval = interval;
142b28a5538SAdam Hornacek     }
143b28a5538SAdam Hornacek 
urlToHostname(String urlStr)14414c8a3ffSVladimir Kotal     private String urlToHostname(String urlStr) throws URISyntaxException {
14514c8a3ffSVladimir Kotal         URI uri = new URI(urlStr);
14614c8a3ffSVladimir Kotal         return uri.getHost();
14714c8a3ffSVladimir Kotal     }
14814c8a3ffSVladimir Kotal 
149b28a5538SAdam Hornacek     /**
15014c8a3ffSVladimir Kotal      * This method converts the scheme from URI to port number.
15114c8a3ffSVladimir Kotal      * It is limited to the ldap/ldaps schemes.
15214c8a3ffSVladimir Kotal      * The method could be static however then it cannot be easily mocked in testing.
15314c8a3ffSVladimir Kotal      * @return port number or -1 if the scheme in given URI is not known
15414c8a3ffSVladimir Kotal      * @throws URISyntaxException if the URI is not valid
15514c8a3ffSVladimir Kotal      */
getPort()15614c8a3ffSVladimir Kotal     public int getPort() throws URISyntaxException {
15714c8a3ffSVladimir Kotal         URI uri = new URI(getUrl());
15814c8a3ffSVladimir Kotal         switch (uri.getScheme()) {
15914c8a3ffSVladimir Kotal             case "ldaps":
16014c8a3ffSVladimir Kotal                 return 636;
16114c8a3ffSVladimir Kotal             case "ldap":
16214c8a3ffSVladimir Kotal                 return 389;
16314c8a3ffSVladimir Kotal         }
16414c8a3ffSVladimir Kotal 
16514c8a3ffSVladimir Kotal         return -1;
16614c8a3ffSVladimir Kotal     }
16714c8a3ffSVladimir Kotal 
isReachable(InetAddress addr, int port, int timeOutMillis)16814c8a3ffSVladimir Kotal     private boolean isReachable(InetAddress addr, int port, int timeOutMillis) {
16914c8a3ffSVladimir Kotal         try {
17014c8a3ffSVladimir Kotal             try (Socket soc = new Socket()) {
17114c8a3ffSVladimir Kotal                 soc.connect(new InetSocketAddress(addr, port), timeOutMillis);
17214c8a3ffSVladimir Kotal             }
17314c8a3ffSVladimir Kotal             return true;
17414c8a3ffSVladimir Kotal         } catch (IOException e) {
17514c8a3ffSVladimir Kotal             return false;
17614c8a3ffSVladimir Kotal         }
17714c8a3ffSVladimir Kotal     }
17814c8a3ffSVladimir Kotal 
17914c8a3ffSVladimir Kotal     /**
18014c8a3ffSVladimir Kotal      * Wraps InetAddress.getAllByName() so that it can be mocked in testing.
18114c8a3ffSVladimir Kotal      * (mocking static methods is not really possible with Mockito)
18214c8a3ffSVladimir Kotal      * @param hostname hostname string
18314c8a3ffSVladimir Kotal      * @return array of InetAddress objects
18414c8a3ffSVladimir Kotal      * @throws UnknownHostException if the host cannot be resolved to any IP address
18514c8a3ffSVladimir Kotal      */
getAddresses(String hostname)18614c8a3ffSVladimir Kotal     public InetAddress[] getAddresses(String hostname) throws UnknownHostException {
18714c8a3ffSVladimir Kotal         return InetAddress.getAllByName(hostname);
18814c8a3ffSVladimir Kotal     }
18914c8a3ffSVladimir Kotal 
19014c8a3ffSVladimir Kotal     /**
19114c8a3ffSVladimir Kotal      * Go through all IP addresses and find out if they are reachable.
19214c8a3ffSVladimir Kotal      * @return true if all IP addresses are reachable, false otherwise
19314c8a3ffSVladimir Kotal      */
isReachable()19414c8a3ffSVladimir Kotal     public boolean isReachable() {
19514c8a3ffSVladimir Kotal         try {
19614c8a3ffSVladimir Kotal             InetAddress[] addresses = getAddresses(urlToHostname(getUrl()));
19714c8a3ffSVladimir Kotal             if (addresses.length == 0) {
19814c8a3ffSVladimir Kotal                 LOGGER.log(Level.WARNING, "LDAP server {0} does not resolve to any IP address", this);
19914c8a3ffSVladimir Kotal                 return false;
20014c8a3ffSVladimir Kotal             }
20114c8a3ffSVladimir Kotal 
20214c8a3ffSVladimir Kotal             for (InetAddress addr : addresses) {
20314c8a3ffSVladimir Kotal                 // InetAddr.isReachable() is not sufficient as it can only check ICMP and TCP echo.
20414c8a3ffSVladimir Kotal                 int port = getPort();
20514c8a3ffSVladimir Kotal                 if (!isReachable(addr, port, getConnectTimeout())) {
20614c8a3ffSVladimir Kotal                     LOGGER.log(Level.WARNING, "LDAP server {0} is not reachable on {1}:{2}",
20714c8a3ffSVladimir Kotal                             new Object[]{this, addr, Integer.toString(port)});
20814c8a3ffSVladimir Kotal                     return false;
20914c8a3ffSVladimir Kotal                 }
21014c8a3ffSVladimir Kotal             }
21114c8a3ffSVladimir Kotal         } catch (UnknownHostException e) {
21214c8a3ffSVladimir Kotal             LOGGER.log(Level.SEVERE, String.format("cannot get IP addresses for LDAP server %s", this), e);
21314c8a3ffSVladimir Kotal             return false;
21414c8a3ffSVladimir Kotal         } catch (URISyntaxException e) {
21514c8a3ffSVladimir Kotal             LOGGER.log(Level.SEVERE, String.format("not a valid URI: %s", getUrl()), e);
21614c8a3ffSVladimir Kotal             return false;
21714c8a3ffSVladimir Kotal         }
21814c8a3ffSVladimir Kotal 
21914c8a3ffSVladimir Kotal         return true;
22014c8a3ffSVladimir Kotal     }
22114c8a3ffSVladimir Kotal 
22214c8a3ffSVladimir Kotal     /**
22314c8a3ffSVladimir Kotal      * The LDAP server is working only when it is reachable and its connection is not null.
22414c8a3ffSVladimir Kotal      * This method tries to establish the connection if it is not established already.
225b28a5538SAdam Hornacek      *
226b28a5538SAdam Hornacek      * @return true if it is working
227b28a5538SAdam Hornacek      */
isWorking()228b28a5538SAdam Hornacek     public synchronized boolean isWorking() {
229d1d45fe1SVladimir Kotal         if (ctx == null) {
23014c8a3ffSVladimir Kotal             if (!isReachable()) {
23114c8a3ffSVladimir Kotal                 return false;
23214c8a3ffSVladimir Kotal             }
23314c8a3ffSVladimir Kotal 
234b28a5538SAdam Hornacek             ctx = connect();
235b28a5538SAdam Hornacek         }
236b28a5538SAdam Hornacek         return ctx != null;
237b28a5538SAdam Hornacek     }
238b28a5538SAdam Hornacek 
239b28a5538SAdam Hornacek     /**
240b28a5538SAdam Hornacek      * Connects to the LDAP server.
241b28a5538SAdam Hornacek      *
242b28a5538SAdam Hornacek      * @return the new connection or null
243b28a5538SAdam Hornacek      */
connect()244b28a5538SAdam Hornacek     private synchronized LdapContext connect() {
245*c6f0939bSAdam Hornacek         LOGGER.log(Level.INFO, "Connecting to LDAP server {0} ", this);
246b28a5538SAdam Hornacek 
247b28a5538SAdam Hornacek         if (errorTimestamp > 0 && errorTimestamp + interval > System.currentTimeMillis()) {
24814c8a3ffSVladimir Kotal             LOGGER.log(Level.WARNING, "LDAP server {0} is down", this.url);
249b28a5538SAdam Hornacek             close();
250b28a5538SAdam Hornacek             return null;
251b28a5538SAdam Hornacek         }
252b28a5538SAdam Hornacek 
253b28a5538SAdam Hornacek         if (ctx == null) {
254b28a5538SAdam Hornacek             env.put(Context.PROVIDER_URL, this.url);
255b28a5538SAdam Hornacek 
256b28a5538SAdam Hornacek             if (this.username != null) {
257b28a5538SAdam Hornacek                 env.put(Context.SECURITY_PRINCIPAL, this.username);
258b28a5538SAdam Hornacek             }
259b28a5538SAdam Hornacek             if (this.password != null) {
260b28a5538SAdam Hornacek                 env.put(Context.SECURITY_CREDENTIALS, this.password);
261b28a5538SAdam Hornacek             }
262b28a5538SAdam Hornacek             if (this.connectTimeout > 0) {
263d0624dbbSVladimir Kotal                 env.put(LDAP_CONNECT_TIMEOUT_PARAMETER, Integer.toString(this.connectTimeout));
264d0624dbbSVladimir Kotal             }
265d0624dbbSVladimir Kotal             if (this.readTimeout > 0) {
266d0624dbbSVladimir Kotal                 env.put(LDAP_READ_TIMEOUT_PARAMETER, Integer.toString(this.readTimeout));
267b28a5538SAdam Hornacek             }
268b28a5538SAdam Hornacek 
269b28a5538SAdam Hornacek             try {
270b28a5538SAdam Hornacek                 ctx = new InitialLdapContext(env, null);
271b28a5538SAdam Hornacek                 ctx.setRequestControls(null);
272*c6f0939bSAdam Hornacek                 LOGGER.log(Level.INFO, "Connected to LDAP server {0}", this);
273b28a5538SAdam Hornacek                 errorTimestamp = 0;
274b28a5538SAdam Hornacek             } catch (NamingException ex) {
27514c8a3ffSVladimir Kotal                 LOGGER.log(Level.WARNING, "LDAP server {0} is not responding", env.get(Context.PROVIDER_URL));
276b28a5538SAdam Hornacek                 errorTimestamp = System.currentTimeMillis();
277b28a5538SAdam Hornacek                 close();
278b28a5538SAdam Hornacek                 return ctx = null;
279b28a5538SAdam Hornacek             }
280b28a5538SAdam Hornacek         }
281b28a5538SAdam Hornacek 
282b28a5538SAdam Hornacek         return ctx;
283b28a5538SAdam Hornacek     }
284b28a5538SAdam Hornacek 
285b28a5538SAdam Hornacek     /**
286b28a5538SAdam Hornacek      * Lookups the LDAP server.
287b28a5538SAdam Hornacek      *
288b28a5538SAdam Hornacek      * @param name base dn for the search
289b28a5538SAdam Hornacek      * @param filter LDAP filter
290b28a5538SAdam Hornacek      * @param cons controls for the LDAP request
291b28a5538SAdam Hornacek      * @return LDAP enumeration with the results
292b28a5538SAdam Hornacek      *
293b28a5538SAdam Hornacek      * @throws NamingException naming exception
294b28a5538SAdam Hornacek      */
search(String name, String filter, SearchControls cons)295b28a5538SAdam Hornacek     public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons) throws NamingException {
296b28a5538SAdam Hornacek         return search(name, filter, cons, false);
297b28a5538SAdam Hornacek     }
298b28a5538SAdam Hornacek 
299b28a5538SAdam Hornacek     /**
30014c8a3ffSVladimir Kotal      * Perform LDAP search.
301b28a5538SAdam Hornacek      *
302b28a5538SAdam Hornacek      * @param name base dn for the search
303b28a5538SAdam Hornacek      * @param filter LDAP filter
304b28a5538SAdam Hornacek      * @param controls controls for the LDAP request
305b28a5538SAdam Hornacek      * @param reconnected flag if the request has failed previously
306b28a5538SAdam Hornacek      * @return LDAP enumeration with the results
307b28a5538SAdam Hornacek      *
308b28a5538SAdam Hornacek      * @throws NamingException naming exception
309b28a5538SAdam Hornacek      */
search(String name, String filter, SearchControls controls, boolean reconnected)310b28a5538SAdam Hornacek     public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls controls, boolean reconnected)
311b28a5538SAdam Hornacek             throws NamingException {
312b28a5538SAdam Hornacek 
313b28a5538SAdam Hornacek         if (!isWorking()) {
314b28a5538SAdam Hornacek             close();
315b28a5538SAdam Hornacek             throw new CommunicationException(String.format("LDAP server \"%s\" is down",
316b28a5538SAdam Hornacek                     env.get(Context.PROVIDER_URL)));
317b28a5538SAdam Hornacek         }
318b28a5538SAdam Hornacek 
319b28a5538SAdam Hornacek         if (reconnected) {
320b28a5538SAdam Hornacek             LOGGER.log(Level.INFO, "LDAP server {0} reconnect", env.get(Context.PROVIDER_URL));
321b28a5538SAdam Hornacek             close();
322b28a5538SAdam Hornacek             if ((ctx = connect()) == null) {
323b28a5538SAdam Hornacek                 throw new CommunicationException(String.format("LDAP server \"%s\" cannot reconnect",
324b28a5538SAdam Hornacek                         env.get(Context.PROVIDER_URL)));
325b28a5538SAdam Hornacek             }
326b28a5538SAdam Hornacek         }
327b28a5538SAdam Hornacek 
328b28a5538SAdam Hornacek         try {
329b28a5538SAdam Hornacek             synchronized (this) {
330b28a5538SAdam Hornacek                 return ctx.search(name, filter, controls);
331b28a5538SAdam Hornacek             }
332b28a5538SAdam Hornacek         } catch (CommunicationException ex) {
333b28a5538SAdam Hornacek             if (reconnected) {
334b28a5538SAdam Hornacek                 throw ex;
335b28a5538SAdam Hornacek             }
336b28a5538SAdam Hornacek             return search(name, filter, controls, true);
337b28a5538SAdam Hornacek         }
338b28a5538SAdam Hornacek     }
339b28a5538SAdam Hornacek 
340b28a5538SAdam Hornacek     /**
341b28a5538SAdam Hornacek      * Closes the server context.
342b28a5538SAdam Hornacek      */
close()343b28a5538SAdam Hornacek     public synchronized void close() {
344b28a5538SAdam Hornacek         if (ctx != null) {
345b28a5538SAdam Hornacek             try {
346b28a5538SAdam Hornacek                 ctx.close();
347b28a5538SAdam Hornacek             } catch (NamingException ex) {
348b28a5538SAdam Hornacek                 LOGGER.log(Level.WARNING, "cannot close LDAP server {0}", getUrl());
349b28a5538SAdam Hornacek             }
350b28a5538SAdam Hornacek             ctx = null;
351b28a5538SAdam Hornacek         }
352b28a5538SAdam Hornacek     }
353b28a5538SAdam Hornacek 
prepareEnv()354b28a5538SAdam Hornacek     private static Hashtable<String, String> prepareEnv() {
355*c6f0939bSAdam Hornacek         Hashtable<String, String> e = new Hashtable<>();
356b28a5538SAdam Hornacek 
357b28a5538SAdam Hornacek         e.put(Context.INITIAL_CONTEXT_FACTORY, LDAP_CONTEXT_FACTORY);
358d0624dbbSVladimir Kotal         e.put(LDAP_CONNECT_TIMEOUT_PARAMETER, Integer.toString(LDAP_CONNECT_TIMEOUT));
359d0624dbbSVladimir Kotal         e.put(LDAP_READ_TIMEOUT_PARAMETER, Integer.toString(LDAP_READ_TIMEOUT));
360b28a5538SAdam Hornacek 
361b28a5538SAdam Hornacek         return e;
362b28a5538SAdam Hornacek     }
36317deb9edSVladimir Kotal 
36417deb9edSVladimir Kotal     @Override
toString()36517deb9edSVladimir Kotal     public String toString() {
366efd6ebf0SVladimir Kotal         StringBuilder sb = new StringBuilder();
367efd6ebf0SVladimir Kotal 
368efd6ebf0SVladimir Kotal         sb.append(getUrl());
369efd6ebf0SVladimir Kotal 
370efd6ebf0SVladimir Kotal         if (getConnectTimeout() > 0) {
371d0624dbbSVladimir Kotal             sb.append(", connect timeout: ");
372efd6ebf0SVladimir Kotal             sb.append(getConnectTimeout());
373efd6ebf0SVladimir Kotal         }
374d0624dbbSVladimir Kotal         if (getReadTimeout() > 0) {
375d0624dbbSVladimir Kotal             sb.append(", read timeout: ");
376d0624dbbSVladimir Kotal             sb.append(getReadTimeout());
377d0624dbbSVladimir Kotal         }
378efd6ebf0SVladimir Kotal 
379efd6ebf0SVladimir Kotal         if (getUsername() != null && !getUsername().isEmpty()) {
380d0624dbbSVladimir Kotal             sb.append(", username: ");
381efd6ebf0SVladimir Kotal             sb.append(getUsername());
382efd6ebf0SVladimir Kotal         }
383efd6ebf0SVladimir Kotal 
384efd6ebf0SVladimir Kotal         return sb.toString();
38517deb9edSVladimir Kotal     }
386b28a5538SAdam Hornacek }
387