• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3630

20 Oct 2023 10:04AM UTC coverage: 65.943% (-9.8%) from 75.715%
#3630

push

web-flow
junit and docker file sonar issue fixes (#4447)

---------

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>
Co-authored-by: Vladimir Kotal <vladimir.kotal@oracle.com>

49 of 49 new or added lines in 9 files covered. (100.0%)

38645 of 58604 relevant lines covered (65.94%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.77
/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapServer.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
22
 */
23
package opengrok.auth.plugin.ldap;
24

25
import java.io.IOException;
26
import java.io.Serializable;
27
import java.net.InetAddress;
28
import java.net.InetSocketAddress;
29
import java.net.Socket;
30
import java.net.URI;
31
import java.net.URISyntaxException;
32
import java.net.UnknownHostException;
33
import java.util.Hashtable;
34
import java.util.logging.Level;
35
import java.util.logging.Logger;
36
import javax.naming.CommunicationException;
37
import javax.naming.Context;
38
import javax.naming.NamingEnumeration;
39
import javax.naming.NamingException;
40
import javax.naming.directory.SearchControls;
41
import javax.naming.directory.SearchResult;
42
import javax.naming.ldap.InitialLdapContext;
43
import javax.naming.ldap.LdapContext;
44

45
public class LdapServer implements Serializable {
46

47
    private static final long serialVersionUID = -1;
48

49
    private static final Logger LOGGER = Logger.getLogger(LdapServer.class.getName());
1✔
50

51
    private static final String LDAP_CONNECT_TIMEOUT_PARAMETER = "com.sun.jndi.ldap.connect.timeout";
52
    private static final String LDAP_READ_TIMEOUT_PARAMETER = "com.sun.jndi.ldap.read.timeout";
53
    private static final String LDAP_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
54

55
    // default connectTimeout value in milliseconds
56
    private static final int LDAP_CONNECT_TIMEOUT = 5000;
57
    // default readTimeout value in milliseconds
58
    private static final int LDAP_READ_TIMEOUT = 3000;
59

60
    private String url;
61
    private String username;
62
    private String password;
63
    private int connectTimeout;
64
    private int readTimeout;
65
    private int interval = 10 * 1000;
1✔
66

67
    private final Hashtable<String, String> env;
68
    private transient LdapContext ctx;
69
    private long errorTimestamp = 0;
1✔
70

71
    public LdapServer() {
72
        this(prepareEnv());
1✔
73
    }
1✔
74

75
    public LdapServer(String server) {
76
        this(prepareEnv());
1✔
77
        setName(server);
1✔
78
    }
1✔
79

80
    public LdapServer(String server, String username, String password) {
81
        this(prepareEnv());
1✔
82
        setName(server);
1✔
83
        this.username = username;
1✔
84
        this.password = password;
1✔
85
    }
1✔
86

87
    public LdapServer(Hashtable<String, String> env) {
1✔
88
        this.env = env;
1✔
89
    }
1✔
90

91
    public String getUrl() {
92
        return url;
1✔
93
    }
94

95
    public LdapServer setName(String name) {
96
        this.url = name;
1✔
97
        return this;
1✔
98
    }
99

100
    public String getUsername() {
101
        return username;
1✔
102
    }
103

104
    public LdapServer setUsername(String username) {
105
        this.username = username;
1✔
106
        return this;
1✔
107
    }
108

109
    public String getPassword() {
110
        return password;
1✔
111
    }
112

113
    public LdapServer setPassword(String password) {
114
        this.password = password;
1✔
115
        return this;
1✔
116
    }
117

118
    public int getConnectTimeout() {
119
        return connectTimeout;
1✔
120
    }
121

122
    public LdapServer setConnectTimeout(int connectTimeout) {
123
        this.connectTimeout = connectTimeout;
1✔
124
        return this;
1✔
125
    }
126

127
    public int getReadTimeout() {
128
        return readTimeout;
1✔
129
    }
130

131
    public LdapServer setReadTimeout(int readTimeout) {
132
        this.readTimeout = readTimeout;
1✔
133
        return this;
1✔
134
    }
135

136
    public int getInterval() {
137
        return interval;
1✔
138
    }
139

140
    public void setInterval(int interval) {
141
        this.interval = interval;
1✔
142
    }
1✔
143

144
    private String urlToHostname(String urlStr) throws URISyntaxException {
145
        URI uri = new URI(urlStr);
1✔
146
        return uri.getHost();
1✔
147
    }
148

149
    /**
150
     * This method converts the scheme from URI to port number.
151
     * It is limited to the ldap/ldaps schemes.
152
     * The method could be static however then it cannot be easily mocked in testing.
153
     * @return port number or -1 if the scheme in given URI is not known
154
     * @throws URISyntaxException if the URI is not valid
155
     */
156
    public int getPort() throws URISyntaxException {
157
        URI uri = new URI(getUrl());
1✔
158
        switch (uri.getScheme()) {
1✔
159
            case "ldaps":
160
                return 636;
1✔
161
            case "ldap":
162
                return 389;
1✔
163
            default: return -1;
1✔
164
        }
165
    }
166

167
    private boolean isReachable(InetAddress addr, int port, int timeOutMillis) {
168
        try (Socket soc = new Socket()) {
1✔
169
            soc.connect(new InetSocketAddress(addr, port), timeOutMillis);
1✔
170
            return true;
1✔
171
        } catch (IOException e) {
1✔
172
            return false;
1✔
173
        }
174
    }
175

176
    /**
177
     * Wraps InetAddress.getAllByName() so that it can be mocked in testing.
178
     * (mocking static methods is not really possible with Mockito)
179
     * @param hostname hostname string
180
     * @return array of InetAddress objects
181
     * @throws UnknownHostException if the host cannot be resolved to any IP address
182
     */
183
    public InetAddress[] getAddresses(String hostname) throws UnknownHostException {
184
        return InetAddress.getAllByName(hostname);
×
185
    }
186

187
    /**
188
     * Go through all IP addresses and find out if they are reachable.
189
     * @return true if all IP addresses are reachable, false otherwise
190
     */
191
    public boolean isReachable() {
192
        try {
193
            InetAddress[] addresses = getAddresses(urlToHostname(getUrl()));
1✔
194
            if (addresses.length == 0) {
1✔
195
                LOGGER.log(Level.WARNING, "LDAP server {0} does not resolve to any IP address", this);
1✔
196
                return false;
1✔
197
            }
198

199
            for (InetAddress addr : addresses) {
1✔
200
                // InetAddr.isReachable() is not sufficient as it can only check ICMP and TCP echo.
201
                int port = getPort();
1✔
202
                if (!isReachable(addr, port, getConnectTimeout())) {
1✔
203
                    LOGGER.log(Level.WARNING, "LDAP server {0} is not reachable on {1}:{2}",
1✔
204
                            new Object[]{this, addr, Integer.toString(port)});
1✔
205
                    return false;
1✔
206
                }
207
            }
208
        } catch (UnknownHostException e) {
1✔
209
            LOGGER.log(Level.SEVERE, String.format("cannot get IP addresses for LDAP server %s", this), e);
1✔
210
            return false;
1✔
211
        } catch (URISyntaxException e) {
1✔
212
            LOGGER.log(Level.SEVERE, String.format("not a valid URI: %s", getUrl()), e);
1✔
213
            return false;
1✔
214
        }
1✔
215

216
        return true;
1✔
217
    }
218

219
    /**
220
     * The LDAP server is working only when it is reachable and its connection is not null.
221
     * This method tries to establish the connection if it is not established already.
222
     *
223
     * @return true if it is working
224
     */
225
    public synchronized boolean isWorking() {
226
        if (ctx == null) {
1✔
227
            if (!isReachable()) {
1✔
228
                return false;
1✔
229
            }
230

231
            ctx = connect();
×
232
        }
233
        return ctx != null;
×
234
    }
235

236
    /**
237
     * Connects to the LDAP server.
238
     *
239
     * @return the new connection or null
240
     */
241
    private synchronized LdapContext connect() {
242
        LOGGER.log(Level.INFO, "Connecting to LDAP server {0} ", this);
×
243

244
        if (errorTimestamp > 0 && errorTimestamp + interval > System.currentTimeMillis()) {
×
245
            LOGGER.log(Level.WARNING, "LDAP server {0} is down", this.url);
×
246
            close();
×
247
            return null;
×
248
        }
249

250
        if (ctx == null) {
×
251
            env.put(Context.PROVIDER_URL, this.url);
×
252

253
            if (this.username != null) {
×
254
                env.put(Context.SECURITY_PRINCIPAL, this.username);
×
255
            }
256
            if (this.password != null) {
×
257
                env.put(Context.SECURITY_CREDENTIALS, this.password);
×
258
            }
259
            if (this.connectTimeout > 0) {
×
260
                env.put(LDAP_CONNECT_TIMEOUT_PARAMETER, Integer.toString(this.connectTimeout));
×
261
            }
262
            if (this.readTimeout > 0) {
×
263
                env.put(LDAP_READ_TIMEOUT_PARAMETER, Integer.toString(this.readTimeout));
×
264
            }
265

266
            try {
267
                ctx = new InitialLdapContext(env, null);
×
268
                ctx.setRequestControls(null);
×
269
                LOGGER.log(Level.INFO, "Connected to LDAP server {0}", this);
×
270
                errorTimestamp = 0;
×
271
            } catch (NamingException ex) {
×
272
                LOGGER.log(Level.WARNING, "LDAP server {0} is not responding", env.get(Context.PROVIDER_URL));
×
273
                errorTimestamp = System.currentTimeMillis();
×
274
                close();
×
275
                ctx = null;
×
276
            }
×
277
        }
278

279
        return ctx;
×
280
    }
281

282
    /**
283
     * Lookups the LDAP server.
284
     *
285
     * @param name base dn for the search
286
     * @param filter LDAP filter
287
     * @param cons controls for the LDAP request
288
     * @return LDAP enumeration with the results
289
     *
290
     * @throws NamingException naming exception
291
     */
292
    public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons) throws NamingException {
293
        return search(name, filter, cons, false);
×
294
    }
295

296
    /**
297
     * Perform LDAP search.
298
     *
299
     * @param name base dn for the search
300
     * @param filter LDAP filter
301
     * @param controls controls for the LDAP request
302
     * @param reconnected flag if the request has failed previously
303
     * @return LDAP enumeration with the results
304
     *
305
     * @throws NamingException naming exception
306
     */
307
    public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls controls, boolean reconnected)
308
            throws NamingException {
309

310
        if (!isWorking()) {
×
311
            close();
×
312
            throw new CommunicationException(String.format("LDAP server \"%s\" is down",
×
313
                    env.get(Context.PROVIDER_URL)));
×
314
        }
315

316
        if (reconnected) {
×
317
            LOGGER.log(Level.INFO, "LDAP server {0} reconnect", env.get(Context.PROVIDER_URL));
×
318
            close();
×
319
            if ((ctx = connect()) == null) {
×
320
                throw new CommunicationException(String.format("LDAP server \"%s\" cannot reconnect",
×
321
                        env.get(Context.PROVIDER_URL)));
×
322
            }
323
        }
324

325
        try {
326
            synchronized (this) {
×
327
                return ctx.search(name, filter, controls);
×
328
            }
329
        } catch (CommunicationException ex) {
×
330
            if (reconnected) {
×
331
                throw ex;
×
332
            }
333
            return search(name, filter, controls, true);
×
334
        }
335
    }
336

337
    /**
338
     * Closes the server context.
339
     */
340
    public synchronized void close() {
341
        if (ctx != null) {
1✔
342
            try {
343
                ctx.close();
×
344
            } catch (NamingException ex) {
×
345
                LOGGER.log(Level.WARNING, "cannot close LDAP server {0}", getUrl());
×
346
            }
×
347
            ctx = null;
×
348
        }
349
    }
1✔
350

351
    private static Hashtable<String, String> prepareEnv() {
352
        Hashtable<String, String> e = new Hashtable<>();
1✔
353

354
        e.put(Context.INITIAL_CONTEXT_FACTORY, LDAP_CONTEXT_FACTORY);
1✔
355
        e.put(LDAP_CONNECT_TIMEOUT_PARAMETER, Integer.toString(LDAP_CONNECT_TIMEOUT));
1✔
356
        e.put(LDAP_READ_TIMEOUT_PARAMETER, Integer.toString(LDAP_READ_TIMEOUT));
1✔
357

358
        return e;
1✔
359
    }
360

361
    @Override
362
    public String toString() {
363
        StringBuilder sb = new StringBuilder();
1✔
364

365
        sb.append(getUrl());
1✔
366

367
        if (getConnectTimeout() > 0) {
1✔
368
            sb.append(", connect timeout: ");
1✔
369
            sb.append(getConnectTimeout());
1✔
370
        }
371
        if (getReadTimeout() > 0) {
1✔
372
            sb.append(", read timeout: ");
1✔
373
            sb.append(getReadTimeout());
1✔
374
        }
375

376
        if (getUsername() != null && !getUsername().isEmpty()) {
1✔
377
            sb.append(", username: ");
1✔
378
            sb.append(getUsername());
1✔
379
        }
380

381
        return sb.toString();
1✔
382
    }
383
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc