PageRenderTime 114ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/support/cas-server-support-ldap-core/src/main/java/org/apereo/cas/util/LdapUtils.java

https://github.com/frett/cas
Java | 1040 lines | 678 code | 76 blank | 286 comment | 75 complexity | a282a4f9b4f2d7d04a60b03a7d4fb931 MD5 | raw file
  1. package org.apereo.cas.util;
  2. import org.apereo.cas.configuration.model.support.ldap.AbstractLdapAuthenticationProperties;
  3. import org.apereo.cas.configuration.model.support.ldap.AbstractLdapProperties;
  4. import org.apereo.cas.configuration.support.Beans;
  5. import lombok.experimental.UtilityClass;
  6. import lombok.extern.slf4j.Slf4j;
  7. import lombok.val;
  8. import org.apache.commons.lang3.ClassUtils;
  9. import org.apache.commons.lang3.StringUtils;
  10. import org.apache.commons.lang3.math.NumberUtils;
  11. import org.ldaptive.ActivePassiveConnectionStrategy;
  12. import org.ldaptive.AddOperation;
  13. import org.ldaptive.AddRequest;
  14. import org.ldaptive.AttributeModification;
  15. import org.ldaptive.AttributeModificationType;
  16. import org.ldaptive.BindConnectionInitializer;
  17. import org.ldaptive.BindRequest;
  18. import org.ldaptive.CompareRequest;
  19. import org.ldaptive.Connection;
  20. import org.ldaptive.ConnectionConfig;
  21. import org.ldaptive.ConnectionFactory;
  22. import org.ldaptive.Credential;
  23. import org.ldaptive.DefaultConnectionFactory;
  24. import org.ldaptive.DefaultConnectionStrategy;
  25. import org.ldaptive.DeleteOperation;
  26. import org.ldaptive.DeleteRequest;
  27. import org.ldaptive.DerefAliases;
  28. import org.ldaptive.DnsSrvConnectionStrategy;
  29. import org.ldaptive.LdapAttribute;
  30. import org.ldaptive.LdapEntry;
  31. import org.ldaptive.LdapException;
  32. import org.ldaptive.ModifyOperation;
  33. import org.ldaptive.ModifyRequest;
  34. import org.ldaptive.RandomConnectionStrategy;
  35. import org.ldaptive.Response;
  36. import org.ldaptive.ResultCode;
  37. import org.ldaptive.ReturnAttributes;
  38. import org.ldaptive.RoundRobinConnectionStrategy;
  39. import org.ldaptive.SearchExecutor;
  40. import org.ldaptive.SearchFilter;
  41. import org.ldaptive.SearchOperation;
  42. import org.ldaptive.SearchRequest;
  43. import org.ldaptive.SearchResult;
  44. import org.ldaptive.SearchScope;
  45. import org.ldaptive.ad.UnicodePwdAttribute;
  46. import org.ldaptive.ad.extended.FastBindOperation;
  47. import org.ldaptive.ad.handler.ObjectGuidHandler;
  48. import org.ldaptive.ad.handler.ObjectSidHandler;
  49. import org.ldaptive.ad.handler.PrimaryGroupIdHandler;
  50. import org.ldaptive.ad.handler.RangeEntryHandler;
  51. import org.ldaptive.auth.Authenticator;
  52. import org.ldaptive.auth.EntryResolver;
  53. import org.ldaptive.auth.FormatDnResolver;
  54. import org.ldaptive.auth.PooledBindAuthenticationHandler;
  55. import org.ldaptive.auth.PooledCompareAuthenticationHandler;
  56. import org.ldaptive.auth.PooledSearchDnResolver;
  57. import org.ldaptive.auth.PooledSearchEntryResolver;
  58. import org.ldaptive.control.PasswordPolicyControl;
  59. import org.ldaptive.extended.PasswordModifyOperation;
  60. import org.ldaptive.extended.PasswordModifyRequest;
  61. import org.ldaptive.handler.CaseChangeEntryHandler;
  62. import org.ldaptive.handler.DnAttributeEntryHandler;
  63. import org.ldaptive.handler.MergeAttributeEntryHandler;
  64. import org.ldaptive.handler.RecursiveEntryHandler;
  65. import org.ldaptive.handler.SearchEntryHandler;
  66. import org.ldaptive.pool.BindPassivator;
  67. import org.ldaptive.pool.BlockingConnectionPool;
  68. import org.ldaptive.pool.ClosePassivator;
  69. import org.ldaptive.pool.CompareValidator;
  70. import org.ldaptive.pool.ConnectionPool;
  71. import org.ldaptive.pool.IdlePruneStrategy;
  72. import org.ldaptive.pool.PoolConfig;
  73. import org.ldaptive.pool.PooledConnectionFactory;
  74. import org.ldaptive.pool.SearchValidator;
  75. import org.ldaptive.provider.Provider;
  76. import org.ldaptive.referral.DeleteReferralHandler;
  77. import org.ldaptive.referral.ModifyReferralHandler;
  78. import org.ldaptive.referral.SearchReferralHandler;
  79. import org.ldaptive.sasl.CramMd5Config;
  80. import org.ldaptive.sasl.DigestMd5Config;
  81. import org.ldaptive.sasl.ExternalConfig;
  82. import org.ldaptive.sasl.GssApiConfig;
  83. import org.ldaptive.sasl.Mechanism;
  84. import org.ldaptive.sasl.QualityOfProtection;
  85. import org.ldaptive.sasl.SaslConfig;
  86. import org.ldaptive.sasl.SecurityStrength;
  87. import org.ldaptive.ssl.KeyStoreCredentialConfig;
  88. import org.ldaptive.ssl.SslConfig;
  89. import org.ldaptive.ssl.X509CredentialConfig;
  90. import java.net.URI;
  91. import java.net.URL;
  92. import java.nio.charset.StandardCharsets;
  93. import java.util.ArrayList;
  94. import java.util.Arrays;
  95. import java.util.HashSet;
  96. import java.util.List;
  97. import java.util.Map;
  98. import java.util.Set;
  99. import java.util.stream.Collectors;
  100. import java.util.stream.IntStream;
  101. /**
  102. * Utilities related to LDAP functions.
  103. *
  104. * @author Scott Battaglia
  105. * @author Misagh Moayyed
  106. * @since 3.0.0
  107. */
  108. @Slf4j
  109. @UtilityClass
  110. public class LdapUtils {
  111. /**
  112. * Default parameter name in search filters for ldap.
  113. */
  114. public static final String LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME = "user";
  115. /**
  116. * The objectClass attribute.
  117. */
  118. public static final String OBJECT_CLASS_ATTRIBUTE = "objectClass";
  119. private static final String LDAP_PREFIX = "ldap";
  120. /**
  121. * Reads a Boolean value from the LdapEntry.
  122. *
  123. * @param ctx the ldap entry
  124. * @param attribute the attribute name
  125. * @return {@code true} if the attribute's value matches (case-insensitive) {@code "true"}, otherwise false
  126. */
  127. public static Boolean getBoolean(final LdapEntry ctx, final String attribute) {
  128. return getBoolean(ctx, attribute, Boolean.FALSE);
  129. }
  130. /**
  131. * Reads a Boolean value from the LdapEntry.
  132. *
  133. * @param ctx the ldap entry
  134. * @param attribute the attribute name
  135. * @param nullValue the value which should be returning in case of a null value
  136. * @return {@code true} if the attribute's value matches (case-insensitive) {@code "true"}, otherwise false
  137. */
  138. public static Boolean getBoolean(final LdapEntry ctx, final String attribute, final Boolean nullValue) {
  139. val v = getString(ctx, attribute, nullValue.toString());
  140. if (v != null) {
  141. return v.equalsIgnoreCase(Boolean.TRUE.toString());
  142. }
  143. return nullValue;
  144. }
  145. /**
  146. * Reads a Long value from the LdapEntry.
  147. *
  148. * @param ctx the ldap entry
  149. * @param attribute the attribute name
  150. * @return the long value
  151. */
  152. public static Long getLong(final LdapEntry ctx, final String attribute) {
  153. return getLong(ctx, attribute, Long.MIN_VALUE);
  154. }
  155. /**
  156. * Reads a Long value from the LdapEntry.
  157. *
  158. * @param entry the ldap entry
  159. * @param attribute the attribute name
  160. * @param nullValue the value which should be returning in case of a null value
  161. * @return the long value
  162. */
  163. public static Long getLong(final LdapEntry entry, final String attribute, final Long nullValue) {
  164. val v = getString(entry, attribute, nullValue.toString());
  165. if (NumberUtils.isCreatable(v)) {
  166. return Long.valueOf(v);
  167. }
  168. return nullValue;
  169. }
  170. /**
  171. * Reads a String value from the LdapEntry.
  172. *
  173. * @param entry the ldap entry
  174. * @param attribute the attribute name
  175. * @return the string
  176. */
  177. public static String getString(final LdapEntry entry, final String attribute) {
  178. return getString(entry, attribute, null);
  179. }
  180. /**
  181. * Reads a String value from the LdapEntry.
  182. *
  183. * @param entry the ldap entry
  184. * @param attribute the attribute name
  185. * @param nullValue the value which should be returning in case of a null value
  186. * @return the string
  187. */
  188. public static String getString(final LdapEntry entry, final String attribute, final String nullValue) {
  189. val attr = entry.getAttribute(attribute);
  190. if (attr == null) {
  191. return nullValue;
  192. }
  193. val v = attr.isBinary()
  194. ? new String(attr.getBinaryValue(), StandardCharsets.UTF_8)
  195. : attr.getStringValue();
  196. if (StringUtils.isNotBlank(v)) {
  197. return v;
  198. }
  199. return nullValue;
  200. }
  201. /**
  202. * Execute search operation.
  203. *
  204. * @param connectionFactory the connection factory
  205. * @param baseDn the base dn
  206. * @param filter the filter
  207. * @param returnAttributes the return attributes
  208. * @return the response
  209. * @throws LdapException the ldap exception
  210. */
  211. public static Response<SearchResult> executeSearchOperation(final ConnectionFactory connectionFactory,
  212. final String baseDn,
  213. final SearchFilter filter,
  214. final String... returnAttributes) throws LdapException {
  215. return executeSearchOperation(connectionFactory, baseDn, filter, null, returnAttributes);
  216. }
  217. /**
  218. * Execute search operation.
  219. *
  220. * @param connectionFactory the connection factory
  221. * @param baseDn the base dn
  222. * @param filter the filter
  223. * @param binaryAttributes the binary attributes
  224. * @param returnAttributes the return attributes
  225. * @return the response
  226. * @throws LdapException the ldap exception
  227. */
  228. public static Response<SearchResult> executeSearchOperation(final ConnectionFactory connectionFactory,
  229. final String baseDn,
  230. final SearchFilter filter,
  231. final String[] binaryAttributes,
  232. final String[] returnAttributes) throws LdapException {
  233. try (val connection = createConnection(connectionFactory)) {
  234. val searchOperation = new SearchOperation(connection);
  235. val request = LdapUtils.newLdaptiveSearchRequest(baseDn, filter, binaryAttributes, returnAttributes);
  236. request.setReferralHandler(new SearchReferralHandler());
  237. return searchOperation.execute(request);
  238. }
  239. }
  240. /**
  241. * Execute search operation response.
  242. *
  243. * @param connectionFactory the connection factory
  244. * @param baseDn the base dn
  245. * @param filter the filter
  246. * @return the response
  247. * @throws LdapException the ldap exception
  248. */
  249. public static Response<SearchResult> executeSearchOperation(final ConnectionFactory connectionFactory,
  250. final String baseDn,
  251. final SearchFilter filter) throws LdapException {
  252. return executeSearchOperation(connectionFactory, baseDn, filter, ReturnAttributes.ALL_USER.value(), ReturnAttributes.ALL_USER.value());
  253. }
  254. /**
  255. * Checks to see if response has a result.
  256. *
  257. * @param response the response
  258. * @return true, if successful
  259. */
  260. public static boolean containsResultEntry(final Response<SearchResult> response) {
  261. if (response != null) {
  262. val result = response.getResult();
  263. return result != null && result.getEntry() != null;
  264. }
  265. return false;
  266. }
  267. /**
  268. * Gets connection from the factory.
  269. * Opens the connection if needed.
  270. *
  271. * @param connectionFactory the connection factory
  272. * @return the connection
  273. * @throws LdapException the ldap exception
  274. */
  275. public static Connection createConnection(final ConnectionFactory connectionFactory) throws LdapException {
  276. val c = connectionFactory.getConnection();
  277. if (!c.isOpen()) {
  278. c.open();
  279. }
  280. return c;
  281. }
  282. /**
  283. * Execute a password modify operation.
  284. *
  285. * @param currentDn the current dn
  286. * @param connectionFactory the connection factory
  287. * @param oldPassword the old password
  288. * @param newPassword the new password
  289. * @param type the type
  290. * @return true /false
  291. */
  292. public static boolean executePasswordModifyOperation(final String currentDn,
  293. final ConnectionFactory connectionFactory,
  294. final String oldPassword,
  295. final String newPassword,
  296. final AbstractLdapProperties.LdapType type) {
  297. try (val modifyConnection = createConnection(connectionFactory)) {
  298. if (!modifyConnection.getConnectionConfig().getUseSSL()
  299. && !modifyConnection.getConnectionConfig().getUseStartTLS()) {
  300. LOGGER.warn("Executing password modification op under a non-secure LDAP connection; "
  301. + "To modify password attributes, the connection to the LDAP server SHOULD be secured and/or encrypted.");
  302. }
  303. if (type == AbstractLdapProperties.LdapType.AD) {
  304. LOGGER.debug("Executing password modification op for active directory based on "
  305. + "[https://support.microsoft.com/en-us/kb/269190]");
  306. val operation = new ModifyOperation(modifyConnection);
  307. val response = operation.execute(new ModifyRequest(currentDn, new AttributeModification(AttributeModificationType.REPLACE, new UnicodePwdAttribute(newPassword))));
  308. LOGGER.debug("Result code [{}], message: [{}]", response.getResult(), response.getMessage());
  309. return response.getResultCode() == ResultCode.SUCCESS;
  310. }
  311. LOGGER.debug("Executing password modification op for generic LDAP");
  312. val operation = new PasswordModifyOperation(modifyConnection);
  313. val response = operation.execute(new PasswordModifyRequest(currentDn,
  314. StringUtils.isNotBlank(oldPassword) ? new Credential(oldPassword) : null,
  315. new Credential(newPassword)));
  316. LOGGER.debug("Result code [{}], message: [{}]", response.getResult(), response.getMessage());
  317. return response.getResultCode() == ResultCode.SUCCESS;
  318. } catch (final LdapException e) {
  319. LOGGER.error(e.getMessage(), e);
  320. }
  321. return false;
  322. }
  323. /**
  324. * Execute modify operation boolean.
  325. *
  326. * @param currentDn the current dn
  327. * @param connectionFactory the connection factory
  328. * @param attributes the attributes
  329. * @return true/false
  330. */
  331. public static boolean executeModifyOperation(final String currentDn, final ConnectionFactory connectionFactory,
  332. final Map<String, Set<String>> attributes) {
  333. try (val modifyConnection = createConnection(connectionFactory)) {
  334. val operation = new ModifyOperation(modifyConnection);
  335. val mods = attributes.entrySet()
  336. .stream()
  337. .map(entry -> {
  338. val values = entry.getValue().toArray(new String[]{});
  339. val attr = new LdapAttribute(entry.getKey(), values);
  340. return new AttributeModification(AttributeModificationType.REPLACE, attr);
  341. })
  342. .toArray(value -> new AttributeModification[attributes.size()]);
  343. val request = new ModifyRequest(currentDn, mods);
  344. request.setReferralHandler(new ModifyReferralHandler());
  345. operation.execute(request);
  346. return true;
  347. } catch (final LdapException e) {
  348. LOGGER.error(e.getMessage(), e);
  349. }
  350. return false;
  351. }
  352. /**
  353. * Execute modify operation boolean.
  354. *
  355. * @param currentDn the current dn
  356. * @param connectionFactory the connection factory
  357. * @param entry the entry
  358. * @return true/false
  359. */
  360. public static boolean executeModifyOperation(final String currentDn, final ConnectionFactory connectionFactory, final LdapEntry entry) {
  361. final Map<String, Set<String>> attributes = entry.getAttributes().stream()
  362. .collect(Collectors.toMap(LdapAttribute::getName, ldapAttribute -> new HashSet<>(ldapAttribute.getStringValues())));
  363. return executeModifyOperation(currentDn, connectionFactory, attributes);
  364. }
  365. /**
  366. * Execute add operation boolean.
  367. *
  368. * @param connectionFactory the connection factory
  369. * @param entry the entry
  370. * @return true/false
  371. */
  372. public static boolean executeAddOperation(final ConnectionFactory connectionFactory, final LdapEntry entry) {
  373. try (val connection = createConnection(connectionFactory)) {
  374. val operation = new AddOperation(connection);
  375. operation.execute(new AddRequest(entry.getDn(), entry.getAttributes()));
  376. return true;
  377. } catch (final LdapException e) {
  378. LOGGER.error(e.getMessage(), e);
  379. }
  380. return false;
  381. }
  382. /**
  383. * Execute delete operation boolean.
  384. *
  385. * @param connectionFactory the connection factory
  386. * @param entry the entry
  387. * @return true/false
  388. */
  389. public static boolean executeDeleteOperation(final ConnectionFactory connectionFactory, final LdapEntry entry) {
  390. try (val connection = createConnection(connectionFactory)) {
  391. val delete = new DeleteOperation(connection);
  392. val request = new DeleteRequest(entry.getDn());
  393. request.setReferralHandler(new DeleteReferralHandler());
  394. val res = delete.execute(request);
  395. return res.getResultCode() == ResultCode.SUCCESS;
  396. } catch (final LdapException e) {
  397. LOGGER.error(e.getMessage(), e);
  398. }
  399. return false;
  400. }
  401. /**
  402. * Is ldap connection url?.
  403. *
  404. * @param r the resource
  405. * @return true/false
  406. */
  407. public static boolean isLdapConnectionUrl(final String r) {
  408. return r.toLowerCase().startsWith(LDAP_PREFIX);
  409. }
  410. /**
  411. * Is ldap connection url?.
  412. *
  413. * @param r the resource
  414. * @return true/false
  415. */
  416. public static boolean isLdapConnectionUrl(final URI r) {
  417. return r.getScheme().equalsIgnoreCase(LDAP_PREFIX);
  418. }
  419. /**
  420. * Is ldap connection url?.
  421. *
  422. * @param r the resource
  423. * @return true/false
  424. */
  425. public static boolean isLdapConnectionUrl(final URL r) {
  426. return r.getProtocol().equalsIgnoreCase(LDAP_PREFIX);
  427. }
  428. /**
  429. * Builds a new request.
  430. *
  431. * @param baseDn the base dn
  432. * @param filter the filter
  433. * @param binaryAttributes the binary attributes
  434. * @param returnAttributes the return attributes
  435. * @return the search request
  436. */
  437. public static SearchRequest newLdaptiveSearchRequest(final String baseDn,
  438. final SearchFilter filter,
  439. final String[] binaryAttributes,
  440. final String[] returnAttributes) {
  441. val sr = new SearchRequest(baseDn, filter);
  442. sr.setBinaryAttributes(binaryAttributes);
  443. sr.setReturnAttributes(returnAttributes);
  444. sr.setSearchScope(SearchScope.SUBTREE);
  445. return sr;
  446. }
  447. /**
  448. * New ldaptive search request.
  449. * Returns all attributes.
  450. *
  451. * @param baseDn the base dn
  452. * @param filter the filter
  453. * @return the search request
  454. */
  455. public static SearchRequest newLdaptiveSearchRequest(final String baseDn,
  456. final SearchFilter filter) {
  457. return newLdaptiveSearchRequest(baseDn, filter, ReturnAttributes.ALL_USER.value(), ReturnAttributes.ALL_USER.value());
  458. }
  459. /**
  460. * Constructs a new search filter using {@link SearchExecutor#getSearchFilter()} as a template and
  461. * the username as a parameter.
  462. *
  463. * @param filterQuery the query filter
  464. * @return Search filter with parameters applied.
  465. */
  466. public static SearchFilter newLdaptiveSearchFilter(final String filterQuery) {
  467. return newLdaptiveSearchFilter(filterQuery, new ArrayList<>(0));
  468. }
  469. /**
  470. * Constructs a new search filter using {@link SearchExecutor#getSearchFilter()} as a template and
  471. * the username as a parameter.
  472. *
  473. * @param filterQuery the query filter
  474. * @param params the username
  475. * @return Search filter with parameters applied.
  476. */
  477. public static SearchFilter newLdaptiveSearchFilter(final String filterQuery, final List<String> params) {
  478. return newLdaptiveSearchFilter(filterQuery, LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME, params);
  479. }
  480. /**
  481. * Constructs a new search filter using {@link SearchExecutor#getSearchFilter()} as a template and
  482. * the username as a parameter.
  483. *
  484. * @param filterQuery the query filter
  485. * @param paramName the param name
  486. * @param params the username
  487. * @return Search filter with parameters applied.
  488. */
  489. public static SearchFilter newLdaptiveSearchFilter(final String filterQuery, final String paramName, final List<String> params) {
  490. val filter = new SearchFilter();
  491. filter.setFilter(filterQuery);
  492. if (params != null) {
  493. IntStream.range(0, params.size()).forEach(i -> {
  494. if (filter.getFilter().contains("{" + i + '}')) {
  495. filter.setParameter(i, params.get(i));
  496. } else {
  497. filter.setParameter(paramName, params.get(i));
  498. }
  499. });
  500. }
  501. LOGGER.debug("Constructed LDAP search filter [{}]", filter.format());
  502. return filter;
  503. }
  504. /**
  505. * New ldaptive search filter search filter.
  506. *
  507. * @param filterQuery the filter query
  508. * @param paramName the param name
  509. * @param params the params
  510. * @return the search filter
  511. */
  512. public static SearchFilter newLdaptiveSearchFilter(final String filterQuery, final List<String> paramName, final List<String> params) {
  513. val filter = new SearchFilter();
  514. filter.setFilter(filterQuery);
  515. if (params != null) {
  516. IntStream.range(0, params.size()).forEach(i -> {
  517. val value = params.get(i);
  518. if (filter.getFilter().contains("{" + i + '}')) {
  519. filter.setParameter(i, value);
  520. }
  521. val name = paramName.get(i);
  522. if (filter.getFilter().contains('{' + name + '}')) {
  523. filter.setParameter(name, value);
  524. }
  525. });
  526. }
  527. LOGGER.debug("Constructed LDAP search filter [{}]", filter.format());
  528. return filter;
  529. }
  530. /**
  531. * New search executor.
  532. *
  533. * @param baseDn the base dn
  534. * @param filterQuery the filter query
  535. * @param params the params
  536. * @return the search executor
  537. */
  538. public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery, final List<String> params) {
  539. return newLdaptiveSearchExecutor(baseDn, filterQuery, params, ReturnAttributes.ALL.value());
  540. }
  541. /**
  542. * New ldaptive search executor search executor.
  543. *
  544. * @param baseDn the base dn
  545. * @param filterQuery the filter query
  546. * @param params the params
  547. * @param returnAttributes the return attributes
  548. * @return the search executor
  549. */
  550. public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery,
  551. final List<String> params,
  552. final List<String> returnAttributes) {
  553. return newLdaptiveSearchExecutor(baseDn, filterQuery, params, returnAttributes.toArray(new String[]{}));
  554. }
  555. /**
  556. * New ldaptive search executor search executor.
  557. *
  558. * @param baseDn the base dn
  559. * @param filterQuery the filter query
  560. * @param params the params
  561. * @param returnAttributes the return attributes
  562. * @return the search executor
  563. */
  564. public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery,
  565. final List<String> params,
  566. final String[] returnAttributes) {
  567. val executor = new SearchExecutor();
  568. executor.setBaseDn(baseDn);
  569. executor.setSearchFilter(newLdaptiveSearchFilter(filterQuery, params));
  570. executor.setReturnAttributes(returnAttributes);
  571. executor.setSearchScope(SearchScope.SUBTREE);
  572. return executor;
  573. }
  574. /**
  575. * New search executor search executor.
  576. *
  577. * @param baseDn the base dn
  578. * @param filterQuery the filter query
  579. * @return the search executor
  580. */
  581. public static SearchExecutor newLdaptiveSearchExecutor(final String baseDn, final String filterQuery) {
  582. return newLdaptiveSearchExecutor(baseDn, filterQuery, new ArrayList<>(0));
  583. }
  584. /**
  585. * New ldap authenticator.
  586. *
  587. * @param l the ldap settings.
  588. * @return the authenticator
  589. */
  590. public static Authenticator newLdaptiveAuthenticator(final AbstractLdapAuthenticationProperties l) {
  591. switch (l.getType()) {
  592. case AD:
  593. LOGGER.debug("Creating active directory authenticator for [{}]", l.getLdapUrl());
  594. return getActiveDirectoryAuthenticator(l);
  595. case DIRECT:
  596. LOGGER.debug("Creating direct-bind authenticator for [{}]", l.getLdapUrl());
  597. return getDirectBindAuthenticator(l);
  598. case AUTHENTICATED:
  599. LOGGER.debug("Creating authenticated authenticator for [{}]", l.getLdapUrl());
  600. return getAuthenticatedOrAnonSearchAuthenticator(l);
  601. default:
  602. LOGGER.debug("Creating anonymous authenticator for [{}]", l.getLdapUrl());
  603. return getAuthenticatedOrAnonSearchAuthenticator(l);
  604. }
  605. }
  606. private static Authenticator getAuthenticatedOrAnonSearchAuthenticator(final AbstractLdapAuthenticationProperties l) {
  607. if (StringUtils.isBlank(l.getBaseDn())) {
  608. throw new IllegalArgumentException("Base dn cannot be empty/blank for authenticated/anonymous authentication");
  609. }
  610. if (StringUtils.isBlank(l.getSearchFilter())) {
  611. throw new IllegalArgumentException("User filter cannot be empty/blank for authenticated/anonymous authentication");
  612. }
  613. val connectionFactoryForSearch = newLdaptivePooledConnectionFactory(l);
  614. val resolver = new PooledSearchDnResolver();
  615. resolver.setBaseDn(l.getBaseDn());
  616. resolver.setSubtreeSearch(l.isSubtreeSearch());
  617. resolver.setAllowMultipleDns(l.isAllowMultipleDns());
  618. resolver.setConnectionFactory(connectionFactoryForSearch);
  619. resolver.setUserFilter(l.getSearchFilter());
  620. resolver.setReferralHandler(new SearchReferralHandler());
  621. if (StringUtils.isNotBlank(l.getDerefAliases())) {
  622. resolver.setDerefAliases(DerefAliases.valueOf(l.getDerefAliases()));
  623. }
  624. val auth = StringUtils.isBlank(l.getPrincipalAttributePassword())
  625. ? new Authenticator(resolver, getPooledBindAuthenticationHandler(l, newLdaptivePooledConnectionFactory(l)))
  626. : new Authenticator(resolver, getPooledCompareAuthenticationHandler(l, newLdaptivePooledConnectionFactory(l)));
  627. if (l.isEnhanceWithEntryResolver()) {
  628. auth.setEntryResolver(newLdaptiveSearchEntryResolver(l, newLdaptivePooledConnectionFactory(l)));
  629. }
  630. return auth;
  631. }
  632. private static Authenticator getDirectBindAuthenticator(final AbstractLdapAuthenticationProperties l) {
  633. if (StringUtils.isBlank(l.getDnFormat())) {
  634. throw new IllegalArgumentException("Dn format cannot be empty/blank for direct bind authentication");
  635. }
  636. val resolver = new FormatDnResolver(l.getDnFormat());
  637. val authenticator = new Authenticator(resolver, getPooledBindAuthenticationHandler(l, newLdaptivePooledConnectionFactory(l)));
  638. if (l.isEnhanceWithEntryResolver()) {
  639. authenticator.setEntryResolver(newLdaptiveSearchEntryResolver(l, newLdaptivePooledConnectionFactory(l)));
  640. }
  641. return authenticator;
  642. }
  643. private static Authenticator getActiveDirectoryAuthenticator(final AbstractLdapAuthenticationProperties l) {
  644. if (StringUtils.isBlank(l.getDnFormat())) {
  645. throw new IllegalArgumentException("Dn format cannot be empty/blank for active directory authentication");
  646. }
  647. val resolver = new FormatDnResolver(l.getDnFormat());
  648. val authn = new Authenticator(resolver, getPooledBindAuthenticationHandler(l, newLdaptivePooledConnectionFactory(l)));
  649. if (l.isEnhanceWithEntryResolver()) {
  650. authn.setEntryResolver(newLdaptiveSearchEntryResolver(l, newLdaptivePooledConnectionFactory(l)));
  651. }
  652. return authn;
  653. }
  654. private static PooledBindAuthenticationHandler getPooledBindAuthenticationHandler(final AbstractLdapAuthenticationProperties l,
  655. final PooledConnectionFactory factory) {
  656. val handler = new PooledBindAuthenticationHandler(factory);
  657. handler.setAuthenticationControls(new PasswordPolicyControl());
  658. return handler;
  659. }
  660. private static PooledCompareAuthenticationHandler getPooledCompareAuthenticationHandler(final AbstractLdapAuthenticationProperties l,
  661. final PooledConnectionFactory factory) {
  662. val handler = new PooledCompareAuthenticationHandler(factory);
  663. handler.setPasswordAttribute(l.getPrincipalAttributePassword());
  664. return handler;
  665. }
  666. /**
  667. * New pooled connection factory pooled connection factory.
  668. *
  669. * @param l the ldap properties
  670. * @return the pooled connection factory
  671. */
  672. public static PooledConnectionFactory newLdaptivePooledConnectionFactory(final AbstractLdapProperties l) {
  673. val cp = newLdaptiveBlockingConnectionPool(l);
  674. return new PooledConnectionFactory(cp);
  675. }
  676. /**
  677. * New connection config connection config.
  678. *
  679. * @param l the ldap properties
  680. * @return the connection config
  681. */
  682. public static ConnectionConfig newLdaptiveConnectionConfig(final AbstractLdapProperties l) {
  683. if (StringUtils.isBlank(l.getLdapUrl())) {
  684. throw new IllegalArgumentException("LDAP url cannot be empty/blank");
  685. }
  686. LOGGER.debug("Creating LDAP connection configuration for [{}]", l.getLdapUrl());
  687. val cc = new ConnectionConfig();
  688. val urls = l.getLdapUrl().contains(" ")
  689. ? l.getLdapUrl()
  690. : String.join(" ", l.getLdapUrl().split(","));
  691. LOGGER.debug("Transformed LDAP urls from [{}] to [{}]", l.getLdapUrl(), urls);
  692. cc.setLdapUrl(urls);
  693. cc.setUseSSL(l.isUseSsl());
  694. cc.setUseStartTLS(l.isUseStartTls());
  695. cc.setConnectTimeout(Beans.newDuration(l.getConnectTimeout()));
  696. cc.setResponseTimeout(Beans.newDuration(l.getResponseTimeout()));
  697. if (StringUtils.isNotBlank(l.getConnectionStrategy())) {
  698. val strategy =
  699. AbstractLdapProperties.LdapConnectionStrategy.valueOf(l.getConnectionStrategy());
  700. switch (strategy) {
  701. case RANDOM:
  702. cc.setConnectionStrategy(new RandomConnectionStrategy());
  703. break;
  704. case DNS_SRV:
  705. cc.setConnectionStrategy(new DnsSrvConnectionStrategy());
  706. break;
  707. case ACTIVE_PASSIVE:
  708. cc.setConnectionStrategy(new ActivePassiveConnectionStrategy());
  709. break;
  710. case ROUND_ROBIN:
  711. cc.setConnectionStrategy(new RoundRobinConnectionStrategy());
  712. break;
  713. case DEFAULT:
  714. default:
  715. cc.setConnectionStrategy(new DefaultConnectionStrategy());
  716. break;
  717. }
  718. }
  719. if (l.getTrustCertificates() != null) {
  720. LOGGER.debug("Creating LDAP SSL configuration via trust certificates [{}]", l.getTrustCertificates());
  721. val cfg = new X509CredentialConfig();
  722. cfg.setTrustCertificates(l.getTrustCertificates());
  723. cc.setSslConfig(new SslConfig(cfg));
  724. } else if (l.getKeystore() != null) {
  725. LOGGER.debug("Creating LDAP SSL configuration via keystore [{}]", l.getKeystore());
  726. val cfg = new KeyStoreCredentialConfig();
  727. cfg.setKeyStore(l.getKeystore());
  728. cfg.setKeyStorePassword(l.getKeystorePassword());
  729. cfg.setKeyStoreType(l.getKeystoreType());
  730. cc.setSslConfig(new SslConfig(cfg));
  731. } else {
  732. LOGGER.debug("Creating LDAP SSL configuration via the native JVM truststore");
  733. cc.setSslConfig(new SslConfig());
  734. }
  735. if (StringUtils.isNotBlank(l.getSaslMechanism())) {
  736. LOGGER.debug("Creating LDAP SASL mechanism via [{}]", l.getSaslMechanism());
  737. val bc = new BindConnectionInitializer();
  738. val sc = getSaslConfigFrom(l);
  739. if (StringUtils.isNotBlank(l.getSaslAuthorizationId())) {
  740. sc.setAuthorizationId(l.getSaslAuthorizationId());
  741. }
  742. sc.setMutualAuthentication(l.getSaslMutualAuth());
  743. if (StringUtils.isNotBlank(l.getSaslQualityOfProtection())) {
  744. sc.setQualityOfProtection(QualityOfProtection.valueOf(l.getSaslQualityOfProtection()));
  745. }
  746. if (StringUtils.isNotBlank(l.getSaslSecurityStrength())) {
  747. sc.setSecurityStrength(SecurityStrength.valueOf(l.getSaslSecurityStrength()));
  748. }
  749. bc.setBindSaslConfig(sc);
  750. cc.setConnectionInitializer(bc);
  751. } else if (StringUtils.equals(l.getBindCredential(), "*") && StringUtils.equals(l.getBindDn(), "*")) {
  752. LOGGER.debug("Creating LDAP fast-bind connection initializer");
  753. cc.setConnectionInitializer(new FastBindOperation.FastBindConnectionInitializer());
  754. } else if (StringUtils.isNotBlank(l.getBindDn()) && StringUtils.isNotBlank(l.getBindCredential())) {
  755. LOGGER.debug("Creating LDAP bind connection initializer via [{}]", l.getBindDn());
  756. cc.setConnectionInitializer(new BindConnectionInitializer(l.getBindDn(), new Credential(l.getBindCredential())));
  757. }
  758. return cc;
  759. }
  760. private static SaslConfig getSaslConfigFrom(final AbstractLdapProperties l) {
  761. if (Mechanism.valueOf(l.getSaslMechanism()) == Mechanism.DIGEST_MD5) {
  762. val sc = new DigestMd5Config();
  763. sc.setRealm(l.getSaslRealm());
  764. return sc;
  765. }
  766. if (Mechanism.valueOf(l.getSaslMechanism()) == Mechanism.CRAM_MD5) {
  767. return new CramMd5Config();
  768. }
  769. if (Mechanism.valueOf(l.getSaslMechanism()) == Mechanism.EXTERNAL) {
  770. return new ExternalConfig();
  771. }
  772. val sc = new GssApiConfig();
  773. sc.setRealm(l.getSaslRealm());
  774. return sc;
  775. }
  776. /**
  777. * New pool config pool config.
  778. *
  779. * @param l the ldap properties
  780. * @return the pool config
  781. */
  782. public static PoolConfig newLdaptivePoolConfig(final AbstractLdapProperties l) {
  783. LOGGER.debug("Creating LDAP connection pool configuration for [{}]", l.getLdapUrl());
  784. val pc = new PoolConfig();
  785. pc.setMinPoolSize(l.getMinPoolSize());
  786. pc.setMaxPoolSize(l.getMaxPoolSize());
  787. pc.setValidateOnCheckOut(l.isValidateOnCheckout());
  788. pc.setValidatePeriodically(l.isValidatePeriodically());
  789. pc.setValidatePeriod(Beans.newDuration(l.getValidatePeriod()));
  790. pc.setValidateTimeout(Beans.newDuration(l.getValidateTimeout()));
  791. return pc;
  792. }
  793. /**
  794. * New connection factory connection factory.
  795. *
  796. * @param l the l
  797. * @return the connection factory
  798. */
  799. public static DefaultConnectionFactory newLdaptiveConnectionFactory(final AbstractLdapProperties l) {
  800. LOGGER.debug("Creating LDAP connection factory for [{}]", l.getLdapUrl());
  801. val cc = newLdaptiveConnectionConfig(l);
  802. val bindCf = new DefaultConnectionFactory(cc);
  803. if (l.getProviderClass() != null) {
  804. try {
  805. val clazz = ClassUtils.getClass(l.getProviderClass());
  806. bindCf.setProvider(Provider.class.cast(clazz.getDeclaredConstructor().newInstance()));
  807. } catch (final Exception e) {
  808. LOGGER.error(e.getMessage(), e);
  809. }
  810. }
  811. return bindCf;
  812. }
  813. /**
  814. * New blocking connection pool connection pool.
  815. *
  816. * @param l the l
  817. * @return the connection pool
  818. */
  819. public static ConnectionPool newLdaptiveBlockingConnectionPool(final AbstractLdapProperties l) {
  820. val bindCf = newLdaptiveConnectionFactory(l);
  821. val pc = newLdaptivePoolConfig(l);
  822. val cp = new BlockingConnectionPool(pc, bindCf);
  823. cp.setBlockWaitTime(Beans.newDuration(l.getBlockWaitTime()));
  824. cp.setPoolConfig(pc);
  825. val strategy = new IdlePruneStrategy();
  826. strategy.setIdleTime(Beans.newDuration(l.getIdleTime()));
  827. strategy.setPrunePeriod(Beans.newDuration(l.getPrunePeriod()));
  828. cp.setPruneStrategy(strategy);
  829. switch (l.getValidator().getType().trim().toLowerCase()) {
  830. case "compare":
  831. val compareRequest = new CompareRequest();
  832. compareRequest.setDn(l.getValidator().getDn());
  833. compareRequest.setAttribute(new LdapAttribute(l.getValidator().getAttributeName(),
  834. l.getValidator().getAttributeValues().toArray(new String[]{})));
  835. compareRequest.setReferralHandler(new SearchReferralHandler());
  836. cp.setValidator(new CompareValidator(compareRequest));
  837. break;
  838. case "none":
  839. LOGGER.debug("No validator is configured for the LDAP connection pool of [{}]", l.getLdapUrl());
  840. break;
  841. case "search":
  842. default:
  843. val searchRequest = new SearchRequest();
  844. searchRequest.setBaseDn(l.getValidator().getBaseDn());
  845. searchRequest.setSearchFilter(new SearchFilter(l.getValidator().getSearchFilter()));
  846. searchRequest.setReturnAttributes(ReturnAttributes.NONE.value());
  847. searchRequest.setSearchScope(SearchScope.valueOf(l.getValidator().getScope()));
  848. searchRequest.setSizeLimit(1L);
  849. searchRequest.setReferralHandler(new SearchReferralHandler());
  850. cp.setValidator(new SearchValidator(searchRequest));
  851. break;
  852. }
  853. cp.setFailFastInitialize(l.isFailFast());
  854. if (StringUtils.isNotBlank(l.getPoolPassivator())) {
  855. val pass =
  856. AbstractLdapProperties.LdapConnectionPoolPassivator.valueOf(l.getPoolPassivator().toUpperCase());
  857. switch (pass) {
  858. case CLOSE:
  859. cp.setPassivator(new ClosePassivator());
  860. LOGGER.debug("Created [{}] passivator for [{}]", l.getPoolPassivator(), l.getLdapUrl());
  861. break;
  862. case BIND:
  863. if (StringUtils.isNotBlank(l.getBindDn()) && StringUtils.isNoneBlank(l.getBindCredential())) {
  864. val bindRequest = new BindRequest();
  865. bindRequest.setDn(l.getBindDn());
  866. bindRequest.setCredential(new Credential(l.getBindCredential()));
  867. cp.setPassivator(new BindPassivator(bindRequest));
  868. LOGGER.debug("Created [{}] passivator for [{}]", l.getPoolPassivator(), l.getLdapUrl());
  869. } else {
  870. val values = Arrays.stream(AbstractLdapProperties.LdapConnectionPoolPassivator.values())
  871. .filter(v -> v != AbstractLdapProperties.LdapConnectionPoolPassivator.BIND)
  872. .collect(Collectors.toList());
  873. LOGGER.warn("[{}] pool passivator could not be created for [{}] given bind credentials are not specified. "
  874. + "If you are dealing with LDAP in such a way that does not require bind credentials, you may need to "
  875. + "set the pool passivator setting to one of [{}]",
  876. l.getPoolPassivator(), l.getLdapUrl(), values);
  877. }
  878. break;
  879. default:
  880. break;
  881. }
  882. }
  883. LOGGER.debug("Initializing ldap connection pool for [{}] and bindDn [{}]", l.getLdapUrl(), l.getBindDn());
  884. cp.initialize();
  885. return cp;
  886. }
  887. /**
  888. * New dn resolver entry resolver.
  889. * Creates the necessary search entry resolver.
  890. *
  891. * @param l the ldap settings
  892. * @param factory the factory
  893. * @return the entry resolver
  894. */
  895. public static EntryResolver newLdaptiveSearchEntryResolver(final AbstractLdapAuthenticationProperties l,
  896. final PooledConnectionFactory factory) {
  897. if (StringUtils.isBlank(l.getBaseDn())) {
  898. throw new IllegalArgumentException("To create a search entry resolver, base dn cannot be empty/blank ");
  899. }
  900. if (StringUtils.isBlank(l.getSearchFilter())) {
  901. throw new IllegalArgumentException("To create a search entry resolver, user filter cannot be empty/blank");
  902. }
  903. val entryResolver = new PooledSearchEntryResolver();
  904. entryResolver.setBaseDn(l.getBaseDn());
  905. entryResolver.setUserFilter(l.getSearchFilter());
  906. entryResolver.setSubtreeSearch(l.isSubtreeSearch());
  907. entryResolver.setConnectionFactory(factory);
  908. if (StringUtils.isNotBlank(l.getDerefAliases())) {
  909. entryResolver.setDerefAliases(DerefAliases.valueOf(l.getDerefAliases()));
  910. }
  911. val handlers = new ArrayList<SearchEntryHandler>();
  912. l.getSearchEntryHandlers().forEach(h -> {
  913. switch (h.getType()) {
  914. case CASE_CHANGE:
  915. val eh = new CaseChangeEntryHandler();
  916. eh.setAttributeNameCaseChange(CaseChangeEntryHandler.CaseChange.valueOf(h.getCasChange().getAttributeNameCaseChange()));
  917. eh.setAttributeNames(h.getCasChange().getAttributeNames().toArray(new String[]{}));
  918. eh.setAttributeValueCaseChange(CaseChangeEntryHandler.CaseChange.valueOf(h.getCasChange().getAttributeValueCaseChange()));
  919. eh.setDnCaseChange(CaseChangeEntryHandler.CaseChange.valueOf(h.getCasChange().getDnCaseChange()));
  920. handlers.add(eh);
  921. break;
  922. case DN_ATTRIBUTE_ENTRY:
  923. val ehd = new DnAttributeEntryHandler();
  924. ehd.setAddIfExists(h.getDnAttribute().isAddIfExists());
  925. ehd.setDnAttributeName(h.getDnAttribute().getDnAttributeName());
  926. handlers.add(ehd);
  927. break;
  928. case MERGE:
  929. val ehm = new MergeAttributeEntryHandler();
  930. ehm.setAttributeNames(h.getMergeAttribute().getAttributeNames().toArray(new String[]{}));
  931. ehm.setMergeAttributeName(h.getMergeAttribute().getMergeAttributeName());
  932. handlers.add(ehm);
  933. break;
  934. case OBJECT_GUID:
  935. handlers.add(new ObjectGuidHandler());
  936. break;
  937. case OBJECT_SID:
  938. handlers.add(new ObjectSidHandler());
  939. break;
  940. case PRIMARY_GROUP:
  941. val ehp = new PrimaryGroupIdHandler();
  942. ehp.setBaseDn(h.getPrimaryGroupId().getBaseDn());
  943. ehp.setGroupFilter(h.getPrimaryGroupId().getGroupFilter());
  944. handlers.add(ehp);
  945. break;
  946. case RANGE_ENTRY:
  947. handlers.add(new RangeEntryHandler());
  948. break;
  949. case RECURSIVE_ENTRY:
  950. handlers.add(new RecursiveEntryHandler(h.getRecursive().getSearchAttribute(),
  951. h.getRecursive().getMergeAttributes().toArray(new String[]{})));
  952. break;
  953. default:
  954. break;
  955. }
  956. });
  957. if (!handlers.isEmpty()) {
  958. LOGGER.debug("Search entry handlers defined for the entry resolver of [{}] are [{}]", l.getLdapUrl(), handlers);
  959. entryResolver.setSearchEntryHandlers(handlers.toArray(new SearchEntryHandler[]{}));
  960. }
  961. entryResolver.setReferralHandler(new SearchReferralHandler());
  962. return entryResolver;
  963. }
  964. }