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 /* 21cbd2aafbSVladimir Kotal * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. 22b28a5538SAdam Hornacek */ 23b28a5538SAdam Hornacek package opengrok.auth.plugin.ldap; 24b28a5538SAdam Hornacek 25b3a15424SVladimir Kotal import java.time.Duration; 26b3a15424SVladimir Kotal import java.time.Instant; 27b28a5538SAdam Hornacek import java.util.ArrayList; 28b28a5538SAdam Hornacek import java.util.HashMap; 29b28a5538SAdam Hornacek import java.util.List; 30b28a5538SAdam Hornacek import java.util.Map; 31b28a5538SAdam Hornacek import java.util.Set; 32b28a5538SAdam Hornacek import java.util.TreeSet; 33b28a5538SAdam Hornacek import java.util.logging.Level; 34b28a5538SAdam Hornacek import java.util.logging.Logger; 35b28a5538SAdam Hornacek import javax.naming.CommunicationException; 36b28a5538SAdam Hornacek import javax.naming.NameNotFoundException; 37b28a5538SAdam Hornacek import javax.naming.NamingEnumeration; 38b28a5538SAdam Hornacek import javax.naming.NamingException; 39b28a5538SAdam Hornacek import javax.naming.SizeLimitExceededException; 40b28a5538SAdam Hornacek import javax.naming.TimeLimitExceededException; 41b28a5538SAdam Hornacek import javax.naming.directory.Attribute; 42b28a5538SAdam Hornacek import javax.naming.directory.Attributes; 43b28a5538SAdam Hornacek import javax.naming.directory.SearchControls; 44b28a5538SAdam Hornacek import javax.naming.directory.SearchResult; 45b28a5538SAdam Hornacek 46b3a15424SVladimir Kotal import io.micrometer.core.instrument.Timer; 47b28a5538SAdam Hornacek import opengrok.auth.plugin.configuration.Configuration; 48b28a5538SAdam Hornacek import opengrok.auth.plugin.util.WebHook; 49b28a5538SAdam Hornacek import opengrok.auth.plugin.util.WebHooks; 50b3a15424SVladimir Kotal import org.opengrok.indexer.Metrics; 51b28a5538SAdam Hornacek 52b28a5538SAdam Hornacek public class LdapFacade extends AbstractLdapProvider { 53b28a5538SAdam Hornacek 54b28a5538SAdam Hornacek private static final Logger LOGGER = Logger.getLogger(LdapFacade.class.getName()); 55b28a5538SAdam Hornacek 56b28a5538SAdam Hornacek /** 57ff44f24aSAdam Hornáček * Default LDAP filter. 58b28a5538SAdam Hornacek */ 59b28a5538SAdam Hornacek private static final String LDAP_FILTER = "objectclass=*"; 60b28a5538SAdam Hornacek 61b28a5538SAdam Hornacek /** 62ff44f24aSAdam Hornáček * Default timeout for retrieving the results. 63b28a5538SAdam Hornacek */ 64b28a5538SAdam Hornacek private static final int LDAP_SEARCH_TIMEOUT = 5000; // ms 65b28a5538SAdam Hornacek 66b28a5538SAdam Hornacek /** 67ff44f24aSAdam Hornáček * Default limit of result traversal. 68b28a5538SAdam Hornacek * 69b28a5538SAdam Hornacek * @see 70b28a5538SAdam Hornacek * <a href="https://docs.oracle.com/javase/7/docs/api/javax/naming/directory/SearchControls.html#setCountLimit%28long%29">SearchControls</a> 71b28a5538SAdam Hornacek * 72b28a5538SAdam Hornacek * basically it does not mean that the server must send at most this number 73b28a5538SAdam Hornacek * of results, but that the program should not iterate more than this number 74b28a5538SAdam Hornacek * over the results. 75b28a5538SAdam Hornacek */ 76b28a5538SAdam Hornacek private static final int LDAP_COUNT_LIMIT = 100; 77b28a5538SAdam Hornacek 78b28a5538SAdam Hornacek /** 79b28a5538SAdam Hornacek * When there is no active server in the pool, the facade waits this time 80b28a5538SAdam Hornacek * interval (since the last failure) until it tries the servers again. 81b28a5538SAdam Hornacek * 82b28a5538SAdam Hornacek * This should avoid heavy load to the LDAP servers when they are all 83b28a5538SAdam Hornacek * broken/not responding/down - pool waiting. 84b28a5538SAdam Hornacek * 85b28a5538SAdam Hornacek * Also each server uses this same interval since its last failure - per 86b28a5538SAdam Hornacek * server waiting. 87b28a5538SAdam Hornacek */ 88b28a5538SAdam Hornacek private int interval = 10 * 1000; // ms 89b28a5538SAdam Hornacek 90b28a5538SAdam Hornacek /** 91ff44f24aSAdam Hornáček * LDAP search base. 92b28a5538SAdam Hornacek */ 93b28a5538SAdam Hornacek private String searchBase; 94b28a5538SAdam Hornacek 95b28a5538SAdam Hornacek /** 96b28a5538SAdam Hornacek * Server pool. 97b28a5538SAdam Hornacek */ 98b28a5538SAdam Hornacek private List<LdapServer> servers = new ArrayList<>(); 99b28a5538SAdam Hornacek 100b28a5538SAdam Hornacek /** 101ff44f24aSAdam Hornáček * server webHooks. 102b28a5538SAdam Hornacek */ 103b28a5538SAdam Hornacek private WebHooks webHooks; 104b28a5538SAdam Hornacek 105b28a5538SAdam Hornacek private SearchControls controls; 10614c8a3ffSVladimir Kotal private int actualServer = -1; 107b28a5538SAdam Hornacek private long errorTimestamp = 0; 108b28a5538SAdam Hornacek private boolean reported = false; 109b28a5538SAdam Hornacek 110b3a15424SVladimir Kotal private final Timer ldapLookupTimer = Timer.builder("ldap.latency"). 111b3a15424SVladimir Kotal description("LDAP lookup latency"). 112b3a15424SVladimir Kotal register(Metrics.getRegistry()); 113b3a15424SVladimir Kotal 114b28a5538SAdam Hornacek /** 115b28a5538SAdam Hornacek * Interface for converting LDAP results into user defined types. 116b28a5538SAdam Hornacek * 117b28a5538SAdam Hornacek * @param <T> the type of the result 118b28a5538SAdam Hornacek */ 119b28a5538SAdam Hornacek private interface AttributeMapper<T> { 120b28a5538SAdam Hornacek mapFromAttributes(Attributes attr)12117deb9edSVladimir Kotal T mapFromAttributes(Attributes attr) throws NamingException; 122b28a5538SAdam Hornacek } 123b28a5538SAdam Hornacek 124b28a5538SAdam Hornacek /** 125b28a5538SAdam Hornacek * Transforms the attributes to the set of strings used for authorization. 126b28a5538SAdam Hornacek * 127b28a5538SAdam Hornacek * Currently this behaves like it get all records stored in 128b28a5538SAdam Hornacek */ 129b28a5538SAdam Hornacek private static class ContentAttributeMapper implements AttributeMapper<Map<String, Set<String>>> { 130b28a5538SAdam Hornacek 131b28a5538SAdam Hornacek private final String[] values; 132b28a5538SAdam Hornacek 133b28a5538SAdam Hornacek /** 134b28a5538SAdam Hornacek * Create a new mapper which retrieves the given values in the resulting 135b28a5538SAdam Hornacek * set. 136b28a5538SAdam Hornacek * 137b28a5538SAdam Hornacek * @param values include these values in the result 138b28a5538SAdam Hornacek */ ContentAttributeMapper(String[] values)139b28a5538SAdam Hornacek ContentAttributeMapper(String[] values) { 140b28a5538SAdam Hornacek this.values = values; 141b28a5538SAdam Hornacek } 142b28a5538SAdam Hornacek 143b28a5538SAdam Hornacek @Override mapFromAttributes(Attributes attrs)14417deb9edSVladimir Kotal public Map<String, Set<String>> mapFromAttributes(Attributes attrs) throws NamingException { 145b28a5538SAdam Hornacek Map<String, Set<String>> map = new HashMap<>(); 146b28a5538SAdam Hornacek 147b28a5538SAdam Hornacek if (values == null) { 14817deb9edSVladimir Kotal for (NamingEnumeration<? extends Attribute> attrEnum = attrs.getAll(); attrEnum.hasMore();) { 149b28a5538SAdam Hornacek Attribute attr = attrEnum.next(); 150b28a5538SAdam Hornacek 1510632fab4SVladimir Kotal addAttrToMap(map, attr); 152b28a5538SAdam Hornacek } 153b28a5538SAdam Hornacek } else { 154ff44f24aSAdam Hornáček for (String value : values) { 155ff44f24aSAdam Hornáček Attribute attr = attrs.get(value); 156b28a5538SAdam Hornacek 157b28a5538SAdam Hornacek if (attr == null) { 158b28a5538SAdam Hornacek continue; 159b28a5538SAdam Hornacek } 160b28a5538SAdam Hornacek 1610632fab4SVladimir Kotal addAttrToMap(map, attr); 1620632fab4SVladimir Kotal } 1630632fab4SVladimir Kotal } 1640632fab4SVladimir Kotal 1650632fab4SVladimir Kotal return map; 1660632fab4SVladimir Kotal } 1670632fab4SVladimir Kotal addAttrToMap(Map<String, Set<String>> map, Attribute attr)1680632fab4SVladimir Kotal private void addAttrToMap(Map<String, Set<String>> map, Attribute attr) throws NamingException { 169b28a5538SAdam Hornacek if (!map.containsKey(attr.getID())) { 170b28a5538SAdam Hornacek map.put(attr.getID(), new TreeSet<>()); 171b28a5538SAdam Hornacek } 172b28a5538SAdam Hornacek 173b28a5538SAdam Hornacek final Set<String> valueSet = map.get(attr.getID()); 174b28a5538SAdam Hornacek 175b28a5538SAdam Hornacek for (NamingEnumeration<?> values = attr.getAll(); values.hasMore(); ) { 176b28a5538SAdam Hornacek valueSet.add((String) values.next()); 177b28a5538SAdam Hornacek } 178b28a5538SAdam Hornacek } 179b28a5538SAdam Hornacek } 180b28a5538SAdam Hornacek LdapFacade(Configuration cfg)181b28a5538SAdam Hornacek public LdapFacade(Configuration cfg) { 182d0624dbbSVladimir Kotal setServers(cfg.getServers(), cfg.getConnectTimeout(), cfg.getReadTimeout()); 183b28a5538SAdam Hornacek setInterval(cfg.getInterval()); 184b28a5538SAdam Hornacek setSearchBase(cfg.getSearchBase()); 185b28a5538SAdam Hornacek setWebHooks(cfg.getWebHooks()); 186e5567996SVladimir Kotal 187e5567996SVladimir Kotal // Anti-pattern: do some non trivial stuff in the constructor. 188b28a5538SAdam Hornacek prepareSearchControls(cfg.getSearchTimeout(), cfg.getCountLimit()); 189b28a5538SAdam Hornacek prepareServers(); 190b28a5538SAdam Hornacek } 191b28a5538SAdam Hornacek setWebHooks(WebHooks webHooks)192b28a5538SAdam Hornacek private void setWebHooks(WebHooks webHooks) { 193b28a5538SAdam Hornacek this.webHooks = webHooks; 194b28a5538SAdam Hornacek } 195b28a5538SAdam Hornacek 196b28a5538SAdam Hornacek /** 19714c8a3ffSVladimir Kotal * Go through all servers in the pool and record the first working. 198b28a5538SAdam Hornacek */ prepareServers()19914c8a3ffSVladimir Kotal void prepareServers() { 20043e14d01SVladimir Kotal LOGGER.log(Level.FINER, "checking servers for {0}", this); 201b28a5538SAdam Hornacek for (int i = 0; i < servers.size(); i++) { 202b28a5538SAdam Hornacek LdapServer server = servers.get(i); 20314c8a3ffSVladimir Kotal if (server.isWorking() && actualServer == -1) { 204b28a5538SAdam Hornacek actualServer = i; 205b28a5538SAdam Hornacek } 206b28a5538SAdam Hornacek } 20743e14d01SVladimir Kotal 20843e14d01SVladimir Kotal // Close the connections to the inactive servers. 20943e14d01SVladimir Kotal LOGGER.log(Level.FINER, "closing unused servers"); 21043e14d01SVladimir Kotal for (int i = 0; i < servers.size(); i++) { 21143e14d01SVladimir Kotal if (i != actualServer) { 21243e14d01SVladimir Kotal servers.get(i).close(); 21343e14d01SVladimir Kotal } 21443e14d01SVladimir Kotal } 21543e14d01SVladimir Kotal 216*05aaab8cSVladimir Kotal if (LOGGER.isLoggable(Level.FINER)) { 2178ef94609SVladimir Kotal LOGGER.log(Level.FINER, String.format("server check done (current server: %s)", 2188ef94609SVladimir Kotal actualServer != -1 ? servers.get(actualServer) : "N/A")); 219b28a5538SAdam Hornacek } 220*05aaab8cSVladimir Kotal } 221b28a5538SAdam Hornacek 222b28a5538SAdam Hornacek /** 223b28a5538SAdam Hornacek * Closes all available servers. 224b28a5538SAdam Hornacek */ 225b28a5538SAdam Hornacek @Override close()226b28a5538SAdam Hornacek public void close() { 227ff44f24aSAdam Hornáček for (LdapServer server : servers) { 228b28a5538SAdam Hornacek server.close(); 229b28a5538SAdam Hornacek } 230b28a5538SAdam Hornacek } 231b28a5538SAdam Hornacek getServers()232b28a5538SAdam Hornacek public List<LdapServer> getServers() { 233b28a5538SAdam Hornacek return servers; 234b28a5538SAdam Hornacek } 235b28a5538SAdam Hornacek setServers(List<LdapServer> servers, int connectTimeout, int readTimeout)236d0624dbbSVladimir Kotal public LdapFacade setServers(List<LdapServer> servers, int connectTimeout, int readTimeout) { 237b28a5538SAdam Hornacek this.servers = servers; 238d0624dbbSVladimir Kotal // Inherit timeout values from server pool configuration. 239b28a5538SAdam Hornacek for (LdapServer server : servers) { 240d0624dbbSVladimir Kotal if (server.getConnectTimeout() == 0 && connectTimeout != 0) { 241d0624dbbSVladimir Kotal server.setConnectTimeout(connectTimeout); 242d0624dbbSVladimir Kotal } 243d0624dbbSVladimir Kotal if (server.getReadTimeout() == 0 && readTimeout != 0) { 244d0624dbbSVladimir Kotal server.setReadTimeout(readTimeout); 245b28a5538SAdam Hornacek } 246b28a5538SAdam Hornacek } 247b28a5538SAdam Hornacek return this; 248b28a5538SAdam Hornacek } 249b28a5538SAdam Hornacek getInterval()250b28a5538SAdam Hornacek public int getInterval() { 251b28a5538SAdam Hornacek return interval; 252b28a5538SAdam Hornacek } 253b28a5538SAdam Hornacek setInterval(int interval)254b28a5538SAdam Hornacek public void setInterval(int interval) { 255b28a5538SAdam Hornacek this.interval = interval; 256ff44f24aSAdam Hornáček for (LdapServer server : servers) { 257b28a5538SAdam Hornacek server.setInterval(interval); 258b28a5538SAdam Hornacek } 259b28a5538SAdam Hornacek } 260b28a5538SAdam Hornacek getSearchBase()261b28a5538SAdam Hornacek public String getSearchBase() { 262b28a5538SAdam Hornacek return searchBase; 263b28a5538SAdam Hornacek } 264b28a5538SAdam Hornacek setSearchBase(String base)265b28a5538SAdam Hornacek public void setSearchBase(String base) { 266b28a5538SAdam Hornacek this.searchBase = base; 267b28a5538SAdam Hornacek } 268b28a5538SAdam Hornacek 269b28a5538SAdam Hornacek @Override isConfigured()270b28a5538SAdam Hornacek public boolean isConfigured() { 2718ae5e262SAdam Hornacek return servers != null && !servers.isEmpty() && searchBase != null && actualServer != -1; 272b28a5538SAdam Hornacek } 273b28a5538SAdam Hornacek 274b28a5538SAdam Hornacek /** 2750e7ff20bSVladimir Kotal * Get LDAP attributes. 276b28a5538SAdam Hornacek * 27753c33ae5SVladimir Kotal * @param dn LDAP DN attribute. If @{code null} then {@code searchBase} will be used. 278b28a5538SAdam Hornacek * @param filter LDAP filter to use. If @{code null} then @{link LDAP_FILTER} will be used. 279b28a5538SAdam Hornacek * @param values match these LDAP values 280b28a5538SAdam Hornacek * 281b28a5538SAdam Hornacek * @return set of strings describing the user's attributes 282b28a5538SAdam Hornacek */ 283b28a5538SAdam Hornacek @Override lookupLdapContent(String dn, String filter, String[] values)28417deb9edSVladimir Kotal public LdapSearchResult<Map<String, Set<String>>> lookupLdapContent(String dn, String filter, String[] values) throws LdapException { 285b28a5538SAdam Hornacek 286b28a5538SAdam Hornacek return lookup( 28753c33ae5SVladimir Kotal dn != null ? dn : getSearchBase(), 288b28a5538SAdam Hornacek filter == null ? LDAP_FILTER : filter, 289b28a5538SAdam Hornacek values, 290b28a5538SAdam Hornacek new ContentAttributeMapper(values)); 291b28a5538SAdam Hornacek } 292b28a5538SAdam Hornacek prepareSearchControls(int ldapTimeout, int ldapCountLimit)293b28a5538SAdam Hornacek private SearchControls prepareSearchControls(int ldapTimeout, int ldapCountLimit) { 294b28a5538SAdam Hornacek controls = new SearchControls(); 295b28a5538SAdam Hornacek controls.setSearchScope(SearchControls.SUBTREE_SCOPE); 296b28a5538SAdam Hornacek controls.setTimeLimit(ldapTimeout > 0 ? ldapTimeout : LDAP_SEARCH_TIMEOUT); 297b28a5538SAdam Hornacek controls.setCountLimit(ldapCountLimit > 0 ? ldapCountLimit : LDAP_COUNT_LIMIT); 298b28a5538SAdam Hornacek 299b28a5538SAdam Hornacek return controls; 300b28a5538SAdam Hornacek } 301b28a5538SAdam Hornacek getSearchControls()302b28a5538SAdam Hornacek public SearchControls getSearchControls() { 303b28a5538SAdam Hornacek return controls; 304b28a5538SAdam Hornacek } 305b28a5538SAdam Hornacek 306b28a5538SAdam Hornacek /** 307ff44f24aSAdam Hornáček * Lookups the LDAP server for content. 308b28a5538SAdam Hornacek * 309b28a5538SAdam Hornacek * @param <T> return type 310b28a5538SAdam Hornacek * @param dn search base for the query 311b28a5538SAdam Hornacek * @param filter LDAP filter for the query 312b28a5538SAdam Hornacek * @param attributes returning LDAP attributes 313b28a5538SAdam Hornacek * @param mapper mapper class implementing @code{AttributeMapper} closed 314b28a5538SAdam Hornacek * 315b28a5538SAdam Hornacek * @return results transformed with mapper 316b28a5538SAdam Hornacek */ lookup(String dn, String filter, String[] attributes, AttributeMapper<T> mapper)31717deb9edSVladimir Kotal private <T> LdapSearchResult<T> lookup(String dn, String filter, String[] attributes, AttributeMapper<T> mapper) throws LdapException { 318b3a15424SVladimir Kotal Instant start = Instant.now(); 319b3a15424SVladimir Kotal LdapSearchResult<T> res = lookup(dn, filter, attributes, mapper, 0); 320b3a15424SVladimir Kotal ldapLookupTimer.record(Duration.between(start, Instant.now())); 321b3a15424SVladimir Kotal return res; 322b28a5538SAdam Hornacek } 323b28a5538SAdam Hornacek 3249f00baffSVladimir Kotal // available for testing getSearchDescription(String dn, String filter, String[] attributes)3259f00baffSVladimir Kotal static String getSearchDescription(String dn, String filter, String[] attributes) { 3269f00baffSVladimir Kotal StringBuilder builder = new StringBuilder(); 3279f00baffSVladimir Kotal builder.append("DN: "); 3289f00baffSVladimir Kotal builder.append(dn); 3299f00baffSVladimir Kotal builder.append(", filter: "); 3309f00baffSVladimir Kotal builder.append(filter); 3319f00baffSVladimir Kotal if (attributes != null) { 3329f00baffSVladimir Kotal builder.append(", attributes: "); 3339f00baffSVladimir Kotal builder.append(String.join(",", attributes)); 3349f00baffSVladimir Kotal } 3359f00baffSVladimir Kotal return builder.toString(); 33696f96d0bSVladimir Kotal } 33796f96d0bSVladimir Kotal 338b28a5538SAdam Hornacek /** 339ff44f24aSAdam Hornáček * Lookups the LDAP server for content. 340b28a5538SAdam Hornacek * 341b28a5538SAdam Hornacek * @param <T> return type 342b28a5538SAdam Hornacek * @param dn search base for the query 343b28a5538SAdam Hornacek * @param filter LDAP filter for the query 344b28a5538SAdam Hornacek * @param attributes returning LDAP attributes 345b28a5538SAdam Hornacek * @param mapper mapper class implementing @code{AttributeMapper} closed 346b28a5538SAdam Hornacek * @param fail current count of failures 347b28a5538SAdam Hornacek * 348b28a5538SAdam Hornacek * @return results transformed with mapper or {@code null} on failure 349b28a5538SAdam Hornacek * @throws LdapException LDAP exception 350b28a5538SAdam Hornacek */ lookup(String dn, String filter, String[] attributes, AttributeMapper<T> mapper, int fail)35117deb9edSVladimir Kotal private <T> LdapSearchResult<T> lookup(String dn, String filter, String[] attributes, AttributeMapper<T> mapper, int fail) throws LdapException { 352b28a5538SAdam Hornacek 353b28a5538SAdam Hornacek if (errorTimestamp > 0 && errorTimestamp + interval > System.currentTimeMillis()) { 354b28a5538SAdam Hornacek if (!reported) { 355b28a5538SAdam Hornacek reported = true; 356b28a5538SAdam Hornacek LOGGER.log(Level.SEVERE, "LDAP server pool is still broken"); 357b28a5538SAdam Hornacek } 358b28a5538SAdam Hornacek throw new LdapException("LDAP server pool is still broken"); 359b28a5538SAdam Hornacek } 360b28a5538SAdam Hornacek 361b28a5538SAdam Hornacek if (fail > servers.size() - 1) { 362b28a5538SAdam Hornacek // did the whole rotation 363b28a5538SAdam Hornacek LOGGER.log(Level.SEVERE, "Tried all LDAP servers in a pool but no server works"); 364b28a5538SAdam Hornacek errorTimestamp = System.currentTimeMillis(); 365b28a5538SAdam Hornacek reported = false; 366b28a5538SAdam Hornacek WebHook hook; 367b28a5538SAdam Hornacek if ((hook = webHooks.getFail()) != null) { 368b28a5538SAdam Hornacek hook.post(); 369b28a5538SAdam Hornacek } 370b28a5538SAdam Hornacek throw new LdapException("Tried all LDAP servers in a pool but no server works"); 371b28a5538SAdam Hornacek } 372b28a5538SAdam Hornacek 373b28a5538SAdam Hornacek if (!isConfigured()) { 374b28a5538SAdam Hornacek LOGGER.log(Level.SEVERE, "LDAP is not configured"); 375b28a5538SAdam Hornacek throw new LdapException("LDAP is not configured"); 376b28a5538SAdam Hornacek } 377b28a5538SAdam Hornacek 378b28a5538SAdam Hornacek NamingEnumeration<SearchResult> namingEnum = null; 37917deb9edSVladimir Kotal LdapServer server = null; 380b28a5538SAdam Hornacek try { 38117deb9edSVladimir Kotal server = servers.get(actualServer); 382b28a5538SAdam Hornacek controls.setReturningAttributes(attributes); 383b28a5538SAdam Hornacek for (namingEnum = server.search(dn, filter, controls); namingEnum.hasMore();) { 384b28a5538SAdam Hornacek SearchResult sr = namingEnum.next(); 385b28a5538SAdam Hornacek reported = false; 386b28a5538SAdam Hornacek if (errorTimestamp > 0) { 387b28a5538SAdam Hornacek errorTimestamp = 0; 388b28a5538SAdam Hornacek WebHook hook; 389b28a5538SAdam Hornacek if ((hook = webHooks.getRecover()) != null) { 390b28a5538SAdam Hornacek hook.post(); 391b28a5538SAdam Hornacek } 392b28a5538SAdam Hornacek } 39317deb9edSVladimir Kotal 39417deb9edSVladimir Kotal return new LdapSearchResult<>(sr.getNameInNamespace(), processResult(sr, mapper)); 395b28a5538SAdam Hornacek } 396b28a5538SAdam Hornacek } catch (NameNotFoundException ex) { 39796f96d0bSVladimir Kotal LOGGER.log(Level.WARNING, String.format("The LDAP name for search '%s' was not found on server %s", 39896f96d0bSVladimir Kotal getSearchDescription(dn, filter, attributes), server), ex); 399b28a5538SAdam Hornacek throw new LdapException("The LDAP name was not found.", ex); 400b28a5538SAdam Hornacek } catch (SizeLimitExceededException ex) { 401ee00a213SVladimir Kotal LOGGER.log(Level.SEVERE, String.format("The maximum size of the LDAP result has exceeded " 402ee00a213SVladimir Kotal + "on server %s", server), ex); 403b28a5538SAdam Hornacek closeActualServer(); 404b28a5538SAdam Hornacek actualServer = getNextServer(); 405b28a5538SAdam Hornacek return lookup(dn, filter, attributes, mapper, fail + 1); 406b28a5538SAdam Hornacek } catch (TimeLimitExceededException ex) { 407ee00a213SVladimir Kotal LOGGER.log(Level.SEVERE, String.format("Time limit for LDAP operation has exceeded on server %s", 408ee00a213SVladimir Kotal server), ex); 409b28a5538SAdam Hornacek closeActualServer(); 410b28a5538SAdam Hornacek actualServer = getNextServer(); 411b28a5538SAdam Hornacek return lookup(dn, filter, attributes, mapper, fail + 1); 412b28a5538SAdam Hornacek } catch (CommunicationException ex) { 413f3f79db5SVladimir Kotal LOGGER.log(Level.WARNING, String.format("Communication error received on server %s, " + 414ee00a213SVladimir Kotal "reconnecting to next server.", server), ex); 415b28a5538SAdam Hornacek closeActualServer(); 416b28a5538SAdam Hornacek actualServer = getNextServer(); 417b28a5538SAdam Hornacek return lookup(dn, filter, attributes, mapper, fail + 1); 418b28a5538SAdam Hornacek } catch (NamingException ex) { 41996f96d0bSVladimir Kotal LOGGER.log(Level.SEVERE, String.format("An arbitrary LDAP error occurred on server %s " + 420ce2ab33bSVladimir Kotal "when searching for '%s'", server, getSearchDescription(dn, filter, attributes)), ex); 421b28a5538SAdam Hornacek closeActualServer(); 422b28a5538SAdam Hornacek actualServer = getNextServer(); 423b28a5538SAdam Hornacek return lookup(dn, filter, attributes, mapper, fail + 1); 424b28a5538SAdam Hornacek } finally { 425b28a5538SAdam Hornacek if (namingEnum != null) { 426b28a5538SAdam Hornacek try { 427b28a5538SAdam Hornacek namingEnum.close(); 428b28a5538SAdam Hornacek } catch (NamingException e) { 429b28a5538SAdam Hornacek LOGGER.log(Level.WARNING, 430b28a5538SAdam Hornacek "failed to close search result enumeration"); 431b28a5538SAdam Hornacek } 432b28a5538SAdam Hornacek } 433b28a5538SAdam Hornacek } 434b28a5538SAdam Hornacek 435dd0cb931SVladimir Kotal return null; 436b28a5538SAdam Hornacek } 437b28a5538SAdam Hornacek closeActualServer()438b28a5538SAdam Hornacek private void closeActualServer() { 439b28a5538SAdam Hornacek servers.get(actualServer).close(); 440b28a5538SAdam Hornacek } 441b28a5538SAdam Hornacek 442b28a5538SAdam Hornacek /** 443b28a5538SAdam Hornacek * Server take over algorithm behavior. 444b28a5538SAdam Hornacek * 445b28a5538SAdam Hornacek * @return the index of the next server to be used 446b28a5538SAdam Hornacek */ getNextServer()447b28a5538SAdam Hornacek private int getNextServer() { 448b28a5538SAdam Hornacek return (actualServer + 1) % servers.size(); 449b28a5538SAdam Hornacek } 450b28a5538SAdam Hornacek 451b28a5538SAdam Hornacek /** 452b28a5538SAdam Hornacek * Process the incoming LDAP result. 453b28a5538SAdam Hornacek * 454b28a5538SAdam Hornacek * @param <T> type of the result 455b28a5538SAdam Hornacek * @param result LDAP result 456b28a5538SAdam Hornacek * @param mapper mapper to transform the result into the result type 457b28a5538SAdam Hornacek * @return transformed result 458b28a5538SAdam Hornacek * 45917deb9edSVladimir Kotal * @throws NamingException naming exception 460b28a5538SAdam Hornacek */ processResult(SearchResult result, AttributeMapper<T> mapper)461b28a5538SAdam Hornacek private <T> T processResult(SearchResult result, AttributeMapper<T> mapper) throws NamingException { 462b28a5538SAdam Hornacek Attributes attrs = result.getAttributes(); 463b28a5538SAdam Hornacek if (attrs != null) { 46417deb9edSVladimir Kotal return mapper.mapFromAttributes(attrs); 465b28a5538SAdam Hornacek } 46617deb9edSVladimir Kotal 467b28a5538SAdam Hornacek return null; 468b28a5538SAdam Hornacek } 46953c33ae5SVladimir Kotal 4708ae5e262SAdam Hornacek @Override toString()47153c33ae5SVladimir Kotal public String toString() { 472cbd2aafbSVladimir Kotal return "{server=" + (actualServer != -1 ? servers.get(actualServer) : "no active server") + 473e5567996SVladimir Kotal ", searchBase=" + getSearchBase() + "}"; 47453c33ae5SVladimir Kotal } 475b28a5538SAdam Hornacek } 476