LdapConnectionService.java 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /*
  2. * Password Management Servlets (PWM)
  3. * http://www.pwm-project.org
  4. *
  5. * Copyright (c) 2006-2009 Novell, Inc.
  6. * Copyright (c) 2009-2016 The PWM Project
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  21. */
  22. package password.pwm.ldap;
  23. import com.google.gson.reflect.TypeToken;
  24. import com.novell.ldapchai.provider.ChaiProvider;
  25. import com.novell.ldapchai.provider.ChaiProviderFactory;
  26. import password.pwm.AppProperty;
  27. import password.pwm.PwmApplication;
  28. import password.pwm.config.option.DataStorageMethod;
  29. import password.pwm.config.profile.LdapProfile;
  30. import password.pwm.error.ErrorInformation;
  31. import password.pwm.error.PwmError;
  32. import password.pwm.error.PwmException;
  33. import password.pwm.error.PwmUnrecoverableException;
  34. import password.pwm.health.HealthRecord;
  35. import password.pwm.svc.PwmService;
  36. import password.pwm.util.java.AtomicLoopIntIncrementer;
  37. import password.pwm.util.java.JsonUtil;
  38. import password.pwm.util.logging.PwmLogger;
  39. import java.time.Instant;
  40. import java.util.ArrayList;
  41. import java.util.Collection;
  42. import java.util.Collections;
  43. import java.util.HashMap;
  44. import java.util.List;
  45. import java.util.Map;
  46. import java.util.concurrent.ConcurrentHashMap;
  47. public class LdapConnectionService implements PwmService {
  48. private static final PwmLogger LOGGER = PwmLogger.forClass(LdapConnectionService.class);
  49. private final Map<LdapProfile, Map<Integer,ChaiProvider>> proxyChaiProviders = new ConcurrentHashMap<>();
  50. private final Map<LdapProfile, ErrorInformation> lastLdapErrors = new ConcurrentHashMap<>();
  51. private PwmApplication pwmApplication;
  52. private STATUS status = STATUS.NEW;
  53. private AtomicLoopIntIncrementer slotIncrementer;
  54. private final ThreadLocal<Map<LdapProfile,ChaiProvider>> threadLocalProvider = new ThreadLocal<>();
  55. private final ChaiProviderFactory chaiProviderFactory = ChaiProviderFactory.newProviderFactory();
  56. public STATUS status()
  57. {
  58. return status;
  59. }
  60. public void init(final PwmApplication pwmApplication)
  61. throws PwmException
  62. {
  63. this.pwmApplication = pwmApplication;
  64. // read the lastLoginTime
  65. this.lastLdapErrors.putAll(readLastLdapFailure(pwmApplication));
  66. final int connectionsPerProfile = maxSlotsPerProfile(pwmApplication);
  67. LOGGER.trace("allocating " + connectionsPerProfile + " ldap proxy connections per profile");
  68. slotIncrementer = new AtomicLoopIntIncrementer(connectionsPerProfile);
  69. for (final LdapProfile ldapProfile: pwmApplication.getConfig().getLdapProfiles().values()) {
  70. proxyChaiProviders.put(ldapProfile, new ConcurrentHashMap<>());
  71. }
  72. status = STATUS.OPEN;
  73. }
  74. public void close()
  75. {
  76. status = STATUS.CLOSED;
  77. LOGGER.trace("closing ldap proxy connections");
  78. for (final ChaiProvider existingProvider : getAllProviders()) {
  79. try {
  80. existingProvider.close();
  81. } catch (Exception e) {
  82. LOGGER.error("error closing ldap proxy connection: " + e.getMessage(), e);
  83. }
  84. }
  85. proxyChaiProviders.clear();
  86. }
  87. public List<HealthRecord> healthCheck()
  88. {
  89. return null;
  90. }
  91. public ServiceInfoBean serviceInfo()
  92. {
  93. return new ServiceInfoBean(Collections.singletonList(DataStorageMethod.LDAP));
  94. }
  95. public ChaiProvider getProxyChaiProvider(final String identifier)
  96. throws PwmUnrecoverableException
  97. {
  98. final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(identifier);
  99. return getProxyChaiProvider(ldapProfile);
  100. }
  101. public ChaiProvider getProxyChaiProvider(final LdapProfile ldapProfile)
  102. throws PwmUnrecoverableException
  103. {
  104. final LdapProfile effectiveProfile = ldapProfile == null
  105. ? pwmApplication.getConfig().getDefaultLdapProfile()
  106. : ldapProfile;
  107. if (threadLocalProvider.get() != null && threadLocalProvider.get().containsKey(effectiveProfile)) {
  108. return threadLocalProvider.get().get(effectiveProfile);
  109. }
  110. final ChaiProvider chaiProvider = getNewProxyChaiProvider(effectiveProfile);
  111. if (threadLocalProvider.get() == null) {
  112. threadLocalProvider.set(new ConcurrentHashMap<>());
  113. }
  114. threadLocalProvider.get().put(effectiveProfile, chaiProvider);
  115. return chaiProvider;
  116. }
  117. private ChaiProvider getNewProxyChaiProvider(final LdapProfile ldapProfile)
  118. throws PwmUnrecoverableException
  119. {
  120. if (ldapProfile == null) {
  121. throw new NullPointerException("ldapProfile must not be null");
  122. }
  123. final int slot = slotIncrementer.next();
  124. final ChaiProvider proxyChaiProvider = proxyChaiProviders.get(ldapProfile).get(slot);
  125. if (proxyChaiProvider != null) {
  126. return proxyChaiProvider;
  127. }
  128. try {
  129. final ChaiProvider newProvider = LdapOperationsHelper.openProxyChaiProvider(
  130. null,
  131. ldapProfile,
  132. pwmApplication.getConfig(),
  133. pwmApplication.getStatisticsManager()
  134. );
  135. proxyChaiProviders.get(ldapProfile).put(slot, newProvider);
  136. return newProvider;
  137. } catch (PwmUnrecoverableException e) {
  138. setLastLdapFailure(ldapProfile,e.getErrorInformation());
  139. throw e;
  140. } catch (Exception e) {
  141. final String errorMsg = "unexpected error creating new proxy ldap connection: " + e.getMessage();
  142. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
  143. LOGGER.error(errorInformation);
  144. throw new PwmUnrecoverableException(errorInformation);
  145. }
  146. }
  147. public void setLastLdapFailure(final LdapProfile ldapProfile, final ErrorInformation errorInformation) {
  148. lastLdapErrors.put(ldapProfile, errorInformation);
  149. final HashMap<String,ErrorInformation> outputMap = new HashMap<>();
  150. for (final Map.Entry<LdapProfile, ErrorInformation> entry : lastLdapErrors.entrySet()) {
  151. final LdapProfile loopProfile = entry.getKey();
  152. outputMap.put(loopProfile.getIdentifier(), entry.getValue());
  153. }
  154. final String jsonString = JsonUtil.serialize(outputMap);
  155. pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.LAST_LDAP_ERROR, jsonString);
  156. }
  157. public Map<LdapProfile,ErrorInformation> getLastLdapFailure() {
  158. return Collections.unmodifiableMap(lastLdapErrors);
  159. }
  160. public Instant getLastLdapFailureTime(final LdapProfile ldapProfile) {
  161. final ErrorInformation errorInformation = lastLdapErrors.get(ldapProfile);
  162. if (errorInformation != null) {
  163. return errorInformation.getDate();
  164. }
  165. return null;
  166. }
  167. private static Map<LdapProfile,ErrorInformation> readLastLdapFailure(final PwmApplication pwmApplication) {
  168. String lastLdapFailureStr = null;
  169. try {
  170. lastLdapFailureStr = pwmApplication.readAppAttribute(PwmApplication.AppAttribute.LAST_LDAP_ERROR, String.class);
  171. if (lastLdapFailureStr != null && lastLdapFailureStr.length() > 0) {
  172. final Map<String, ErrorInformation> fromJson = JsonUtil.deserialize(lastLdapFailureStr,new TypeToken<Map<String, ErrorInformation>>() {});
  173. final Map<LdapProfile, ErrorInformation> returnMap = new HashMap<>();
  174. for (final Map.Entry<String, ErrorInformation> entry : fromJson.entrySet()) {
  175. final String id = entry.getKey();
  176. final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(id);
  177. if (ldapProfile != null) {
  178. returnMap.put(ldapProfile, entry.getValue());
  179. }
  180. }
  181. return returnMap;
  182. }
  183. } catch (Exception e) {
  184. LOGGER.error("unexpected error loading cached lastLdapFailure statuses: " + e.getMessage() + ", input=" + lastLdapFailureStr);
  185. }
  186. return Collections.emptyMap();
  187. }
  188. private int maxSlotsPerProfile(final PwmApplication pwmApplication) {
  189. final int maxConnections = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PROXY_MAX_CONNECTIONS));
  190. final int perProfile = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PROXY_CONNECTION_PER_PROFILE));
  191. final int profileCount = pwmApplication.getConfig().getLdapProfiles().size();
  192. if ((perProfile * profileCount) >= maxConnections) {
  193. final int adjustedConnections = Math.min(1, (maxConnections / profileCount));
  194. LOGGER.warn("connections per profile (" + perProfile + ") multiplied by number of profiles ("
  195. + profileCount + ") exceeds max connections (" + maxConnections + "), will limit to " + adjustedConnections);
  196. return adjustedConnections;
  197. }
  198. return perProfile;
  199. }
  200. private Collection<ChaiProvider> getAllProviders() {
  201. final List<ChaiProvider> returnList = new ArrayList<>();
  202. for (final Map<Integer,ChaiProvider> loopProfileMap : proxyChaiProviders.values()) {
  203. for (final ChaiProvider chaiProvider : loopProfileMap.values()) {
  204. if (chaiProvider != null) {
  205. returnList.add(chaiProvider);
  206. }
  207. }
  208. }
  209. return Collections.unmodifiableList(returnList);
  210. }
  211. public int connectionCount() {
  212. int count = 0;
  213. for (final ChaiProvider chaiProvider : getAllProviders()) {
  214. if (chaiProvider.isConnected()) {
  215. count++;
  216. }
  217. }
  218. return count;
  219. }
  220. }