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

oracle / opengrok / #3642

23 Oct 2023 02:33PM UTC coverage: 75.784% (+1.4%) from 74.413%
#3642

push

web-flow
Sonar code smell issue fixes (#4450)

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>

200 of 200 new or added lines in 39 files covered. (100.0%)

44390 of 58574 relevant lines covered (75.78%)

0.76 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.HashMap;
34
import java.util.Hashtable;
35
import java.util.Map;
36
import java.util.logging.Level;
37
import java.util.logging.Logger;
38
import javax.naming.CommunicationException;
39
import javax.naming.Context;
40
import javax.naming.NamingEnumeration;
41
import javax.naming.NamingException;
42
import javax.naming.directory.SearchControls;
43
import javax.naming.directory.SearchResult;
44
import javax.naming.ldap.InitialLdapContext;
45
import javax.naming.ldap.LdapContext;
46

47
public class LdapServer implements Serializable {
48

49
    private static final long serialVersionUID = -1;
50

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

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

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

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

69
    private final Map<String, String> env;
70
    private transient LdapContext ctx;
71
    private long errorTimestamp = 0;
1✔
72

73
    public LdapServer() {
74
        this(prepareEnv());
1✔
75
    }
1✔
76

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

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

89
    public LdapServer(Map<String, String> env) {
1✔
90
        this.env = env;
1✔
91
    }
1✔
92

93
    public String getUrl() {
94
        return url;
1✔
95
    }
96

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

102
    public String getUsername() {
103
        return username;
1✔
104
    }
105

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

111
    public String getPassword() {
112
        return password;
1✔
113
    }
114

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

120
    public int getConnectTimeout() {
121
        return connectTimeout;
1✔
122
    }
123

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

129
    public int getReadTimeout() {
130
        return readTimeout;
1✔
131
    }
132

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

138
    public int getInterval() {
139
        return interval;
1✔
140
    }
141

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

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

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

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

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

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

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

218
        return true;
1✔
219
    }
220

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

233
            ctx = connect();
×
234
        }
235
        return ctx != null;
×
236
    }
237

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

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

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

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

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

281
        return ctx;
×
282
    }
283

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

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

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

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

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

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

353
    private static Map<String, String> prepareEnv() {
354
        var e = new HashMap<String, String>();
1✔
355

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

360
        return e;
1✔
361
    }
362

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

367
        sb.append(getUrl());
1✔
368

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

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

383
        return sb.toString();
1✔
384
    }
385
}
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