[java-identity-provider] 01/01: Changes to support the ldaptive v2 API.

Daniel Fisher dfisher at vt.edu
Sat Aug 28 02:07:16 UTC 2021


This is an automated email from the git hooks/post-receive script.

dfisher pushed a commit to branch dev/ldaptive-v2
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=c5de7582dcc298e16819e6d33f5f1a1d7c0cc089

commit c5de7582dcc298e16819e6d33f5f1a1d7c0cc089
Author: Daniel Fisher <dfisher at vt.edu>
AuthorDate: Tue Dec 1 12:00:41 2020 -0500

    Changes to support the ldaptive v2 API.
---
 .../AbstractExecutableSearchFilterBuilder.java     |  19 +-
 .../resolver/dc/ldap/ExecutableSearchFilter.java   |  12 +-
 ...ParameterizedExecutableSearchFilterBuilder.java |   4 +-
 .../dc/ldap/SearchResultMappingStrategy.java       |   6 +-
 .../ldap/StringAttributeValueMappingStrategy.java  |  12 +-
 .../TemplatedExecutableSearchFilterBuilder.java    |  10 +-
 .../dc/ldap/impl/ConnectionFactoryValidator.java   |   8 +-
 .../resolver/dc/ldap/impl/LDAPDataConnector.java   |  45 ++--
 .../dc/ldap/impl/LDAPDataConnectorTest.java        |  39 +--
 .../resolver/dc/ldap/impl/Regressions.java         |  11 +-
 .../dc/ldap/impl/LDAPDataConnectorParser.java      | 290 ++++++++++-----------
 .../dc/ldap/LDAPDataConnectorParserTest.java       | 165 ++++++------
 .../ldap-attribute-resolver-spring-context.xml     |  82 +++---
 ...dap-attribute-resolver-spring-props-context.xml |  89 +++----
 .../authn/AbstractTemplateSearchDnResolver.java    |  16 +-
 .../idp/authn/PooledTemplateSearchDnResolver.java  |  84 ------
 .../idp/authn/TemplateSearchDnResolver.java        |  19 +-
 .../config/LDAPAuthenticationFactoryBean.java      | 162 +++++-------
 .../idp/authn/impl/LDAPCredentialValidator.java    | 123 ++++-----
 .../principal/impl/LDAPPrincipalSerializer.java    |   4 +-
 .../DefaultAuthenticationResultSerializerTest.java |  11 +-
 .../authn/impl/LDAPCredentialValidatorTest.java    |  23 +-
 .../idp/authn/impl/ValidateCredentialsTest.java    |   6 +-
 23 files changed, 537 insertions(+), 703 deletions(-)

diff --git a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java
index ca58514a5..52e0fc60f 100644
--- a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java
+++ b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java
@@ -21,12 +21,11 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.ldaptive.ConnectionFactory;
+import org.ldaptive.FilterTemplate;
 import org.ldaptive.LdapException;
-import org.ldaptive.Response;
 import org.ldaptive.ResultCode;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,7 +48,7 @@ public abstract class AbstractExecutableSearchFilterBuilder extends AbstractInit
      * @return executable search filter
      */
     // Checkstyle: AnonInnerLength OFF
-    protected ExecutableSearchFilter build(@Nonnull final SearchFilter searchFilter) {
+    protected ExecutableSearchFilter build(@Nonnull final FilterTemplate searchFilter) {
         return new ExecutableSearchFilter() {
 
             /** {@inheritDoc} */
@@ -58,20 +57,22 @@ public abstract class AbstractExecutableSearchFilterBuilder extends AbstractInit
             }
 
             /** {@inheritDoc} */
-            @Nonnull public SearchResult execute(@Nonnull final SearchExecutor executor,
+            @Nonnull public SearchResponse execute(@Nonnull final SearchOperation operation,
                     @Nonnull final ConnectionFactory factory) throws LdapException {
-                final Response<SearchResult> response = executor.search(factory, searchFilter);
+                final SearchOperation op = SearchOperation.copy(operation);
+                op.setConnectionFactory(factory);
+                final SearchResponse response = op.execute(searchFilter);
                 log.trace("Search returned response {}", response);
                 if (response.getResultCode() != ResultCode.SUCCESS) {
                     // It's possible for the LDAP to return partial results and report either a size limit or
                     // time limit result code. Throw if we don't receive all results.
                     throw new LdapException("Search operation did not return success: " + response.getResultCode());
                 }
-                return response.getResult();
+                return response;
             }
 
             /** {@inheritDoc} */
-            @Nonnull public SearchFilter getSearchFilter() {
+            @Nonnull public FilterTemplate getSearchFilter() {
                 return searchFilter;
             }
 
diff --git a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java
index 669003250..811ce0d43 100644
--- a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java
+++ b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java
@@ -21,9 +21,9 @@ import javax.annotation.Nonnull;
 
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.LdapException;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
-import org.ldaptive.SearchResult;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 
 import net.shibboleth.idp.attribute.resolver.dc.ExecutableSearch;
 
@@ -33,14 +33,14 @@ public interface ExecutableSearchFilter extends ExecutableSearch {
     /**
      * Performs an LDAP search and returns the results.
      * 
-     * @param executor configured to perform searches
+     * @param operation configured to perform searches
      * @param factory ready-to-use connection factory
      * 
      * @return the result of this search filter
      * 
      * @throws LdapException thrown if there is an error performing the search
      */
-    @Nonnull SearchResult execute(@Nonnull SearchExecutor executor, @Nonnull ConnectionFactory factory)
+    @Nonnull SearchResponse execute(@Nonnull SearchOperation operation, @Nonnull ConnectionFactory factory)
             throws LdapException;
 
     /**
@@ -48,5 +48,5 @@ public interface ExecutableSearchFilter extends ExecutableSearch {
      *
      * @return  search filter
      */
-    @Nonnull SearchFilter getSearchFilter();
+    @Nonnull FilterTemplate getSearchFilter();
 }
\ No newline at end of file
diff --git a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ParameterizedExecutableSearchFilterBuilder.java b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ParameterizedExecutableSearchFilterBuilder.java
index 9e3313ad1..7aea81829 100644
--- a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ParameterizedExecutableSearchFilterBuilder.java
+++ b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ParameterizedExecutableSearchFilterBuilder.java
@@ -22,7 +22,7 @@ import java.util.Map;
 
 import javax.annotation.Nonnull;
 
-import org.ldaptive.SearchFilter;
+import org.ldaptive.FilterTemplate;
 
 import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.resolver.ResolutionException;
@@ -69,7 +69,7 @@ public class ParameterizedExecutableSearchFilterBuilder extends AbstractExecutab
     @Override public ExecutableSearchFilter build(@Nonnull final AttributeResolutionContext resolutionContext,
             @Nonnull final Map<String, List<IdPAttributeValue>> dependencyAttributes) throws ResolutionException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-        final SearchFilter sf = new SearchFilter(searchFilter);
+        final FilterTemplate sf = new FilterTemplate(searchFilter);
         sf.setParameter("principalName", resolutionContext.getPrincipal());
         if (dependencyAttributes != null && !dependencyAttributes.isEmpty()) {
             for (final Map.Entry<String, List<IdPAttributeValue>> entry : dependencyAttributes.entrySet()) {
diff --git a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java
index 00422d07d..465729bfc 100644
--- a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java
+++ b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java
@@ -17,13 +17,13 @@
 
 package net.shibboleth.idp.attribute.resolver.dc.ldap;
 
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchResponse;
 
 import net.shibboleth.idp.attribute.resolver.dc.MappingStrategy;
 
 /**
- * Strategy for mapping from a {@link SearchResult} to a collection of 
+ * Strategy for mapping from a {@link SearchResponse} to a collection of
  * {@link net.shibboleth.idp.attribute.IdPAttribute}s.
  */
-public interface SearchResultMappingStrategy extends MappingStrategy<SearchResult> {
+public interface SearchResultMappingStrategy extends MappingStrategy<SearchResponse> {
 }
\ No newline at end of file
diff --git a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java
index fd88d3a01..a10eb0cfb 100644
--- a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java
+++ b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java
@@ -36,7 +36,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 
 import org.ldaptive.LdapAttribute;
 import org.ldaptive.LdapEntry;
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory;
  * A simple {@link SearchResultMappingStrategy} that iterates over all result entries and includes all attribute values
  * as strings.
  */
-public class StringAttributeValueMappingStrategy extends AbstractMappingStrategy<SearchResult>
+public class StringAttributeValueMappingStrategy extends AbstractMappingStrategy<SearchResponse>
         implements SearchResultMappingStrategy {
 
     /** Class logger. */
@@ -52,21 +52,21 @@ public class StringAttributeValueMappingStrategy extends AbstractMappingStrategy
 
  // Checkstyle: CyclomaticComplexity OFF
     /** {@inheritDoc} */
-    @Override @Nullable public Map<String,IdPAttribute> map(@Nonnull final SearchResult results)
+    @Override @Nullable public Map<String,IdPAttribute> map(@Nonnull final SearchResponse results)
             throws ResolutionException {
         Constraint.isNotNull(results, "Results can not be null");
 
-        if (results.size() == 0) {
+        if (results.entrySize() == 0) {
             log.debug("Results did not contain any entries, nothing to map");
             if (isNoResultAnError()) {
                 throw new NoResultAnErrorResolutionException("No entries returned from search");
             }
             return null;
-        } else if (results.size() > 1 && isMultipleResultsAnError()) {
+        } else if (results.entrySize() > 1 && isMultipleResultsAnError()) {
             throw new MultipleResultAnErrorResolutionException("Multiple entries returned from search");
         }
 
-        final Map<String,IdPAttribute> attributes = new HashMap<>(results.size());
+        final Map<String,IdPAttribute> attributes = new HashMap<>(results.entrySize());
         
         final Map<String,String> aliases = getResultRenamingMap();
 
diff --git a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java
index 000634af9..f38169634 100644
--- a/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java
+++ b/idp-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java
@@ -31,7 +31,7 @@ import org.apache.velocity.app.event.EventCartridge;
 import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
 import org.apache.velocity.context.Context;
 import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.SearchFilter;
+import org.ldaptive.FilterTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -191,7 +191,7 @@ public class TemplatedExecutableSearchFilterBuilder extends AbstractExecutableSe
         }
 
         try {
-            final SearchFilter searchFilter = new SearchFilter(merge(context));
+            final FilterTemplate searchFilter = new FilterTemplate(merge(context));
             return super.build(searchFilter);
         } catch (final VelocityException e) {
             log.error("Error running template: {}", e.getMessage());
@@ -256,7 +256,7 @@ public class TemplatedExecutableSearchFilterBuilder extends AbstractExecutableSe
         }
 
         /**
-         * Returns {@link SearchFilter#encodeValue} if value is a string.
+         * Returns {@link FilterTemplate#encodeValue} if value is a string.
          * 
          * @param value to encode
          *
@@ -264,9 +264,9 @@ public class TemplatedExecutableSearchFilterBuilder extends AbstractExecutableSe
          */
         private Object encode(final Object value) {
             if (value instanceof String){ 
-                return SearchFilter.encodeValue((String) value);
+                return FilterTemplate.encodeValue((String) value);
             } else if (value instanceof byte[]) {
-                return SearchFilter.encodeValue((byte[]) value);
+                return FilterTemplate.encodeValue((byte[]) value);
             }
             return value;
         }
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java
index f0123d613..70368ffd7 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java
@@ -94,9 +94,7 @@ public class ConnectionFactoryValidator extends AbstractInitializableComponent i
 
     /** {@inheritDoc} */
     @Override public void validate() throws ValidationException {
-        Connection connection = null;
-        try {
-            connection = connectionFactory.getConnection();
+        try (Connection connection = connectionFactory.getConnection()) {
             if (connection == null) {
                 log.error("Unable to retrieve connections from configured connection factory");
                 if (isThrowValidateError()) {
@@ -109,10 +107,6 @@ public class ConnectionFactoryValidator extends AbstractInitializableComponent i
             if (isThrowValidateError()) {
                 throw new ValidationException(e);
             }
-        } finally {
-            if (connection != null) {
-                connection.close();
-            }
         }
     }
 }
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
index 1143d71e0..2839583da 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
@@ -27,8 +27,8 @@ import org.ldaptive.Connection;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.LdapException;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 import org.ldaptive.ssl.SSLContextInitializer;
 import org.ldaptive.ssl.SslConfig;
 import org.ldaptive.ssl.X509SSLContextInitializer;
@@ -60,7 +60,7 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
     private ConnectionFactory connectionFactory;
 
     /** For executing LDAP searches. */
-    private SearchExecutor searchExecutor;
+    private SearchOperation searchOperation;
 
     /** Whether the default validator is being used. */
     private boolean defaultValidator = true;
@@ -96,24 +96,24 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
     }
 
     /**
-     * Gets the search executor for executing searches.
+     * Gets the search operation for executing searches.
      * 
-     * @return search executor for executing searches
+     * @return search operation for executing searches
      */
-    public SearchExecutor getSearchExecutor() {
-        return searchExecutor;
+    public SearchOperation getSearchOperation() {
+        return searchOperation;
     }
 
     /**
      * Sets the search executor for executing searches.
      * 
-     * @param executor search executor for executing searches
+     * @param operation search operation for executing searches
      */
-    public void setSearchExecutor(@Nonnull final SearchExecutor executor) {
+    public void setSearchOperation(@Nonnull final SearchOperation operation) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
-        searchExecutor = Constraint.isNotNull(executor, "LDAP search executor can not be null");
+        searchOperation = Constraint.isNotNull(operation, "LDAP search executor can not be null");
     }
 
     /** {@inheritDoc} */
@@ -133,8 +133,8 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
         if (connectionFactory == null) {
             throw new ComponentInitializationException(getLogPrefix() + " No connection factory was configured");
         }
-        if (searchExecutor == null) {
-            throw new ComponentInitializationException(getLogPrefix() + " No search executor was configured");
+        if (searchOperation == null) {
+            throw new ComponentInitializationException(getLogPrefix() + " No search operation was configured");
         }
 
         if (defaultValidator) {
@@ -165,16 +165,9 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
      * @throws ComponentInitializationException if we detect an SSL issue
      */
     private void policeForJVMTrust() throws ComponentInitializationException {
-        Connection conn = null;
         try {
-            conn = connectionFactory.getConnection();
-            if (conn == null) {
-                log.debug("{} No connection to probe", getLogPrefix());
-                return;
-            }
-            final ConnectionConfig connConfig = conn.getConnectionConfig();
+            final ConnectionConfig connConfig = connectionFactory.getConnectionConfig();
             if (connConfig.getUseStartTLS() ||
-                    connConfig.getUseSSL() ||
                     connConfig.getLdapUrl().toLowerCase().contains("ldaps://")) {
                 final SslConfig sslConfig = connConfig.getSslConfig();
                 if (sslConfig != null) {
@@ -188,16 +181,8 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
                     }
                 }
             }
-        } catch (final GeneralSecurityException | LdapException e) {
+        } catch (final GeneralSecurityException e) {
             log.debug("{} Failed to inspect TLS implementation", getLogPrefix(), e);
-        } finally {
-            if (conn != null) {
-                try {
-                    conn.close();
-                } catch (final Exception e) {
-                    log.debug("{} Error closing LDAP connection", getLogPrefix(), e);
-                }
-            }
         }
     }
  // CheckStyle: CyclomaticComplexity ON
@@ -218,7 +203,7 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
             throw new ResolutionException(getLogPrefix() + " Search filter cannot be null");
         }
         try {
-            final SearchResult result = filter.execute(searchExecutor, connectionFactory);
+            final SearchResponse result = filter.execute(searchOperation, connectionFactory);
             log.trace("{} Search returned {}", getLogPrefix(), result);
             return getMappingStrategy().map(result);
         } catch (final LdapException e) {
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
index 72691a8d3..e8be89586 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
@@ -34,7 +34,8 @@ import javax.annotation.Nonnull;
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.SearchExecutor;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchRequest;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -124,10 +125,10 @@ public class LDAPDataConnectorTest {
         connector.setId(TEST_CONNECTOR_NAME);
         final ConnectionFactory connectionFactory = new DefaultConnectionFactory("ldap://localhost:10389");
         connector.setConnectionFactory(connectionFactory);
-        final SearchExecutor searchExecutor = new SearchExecutor();
-        searchExecutor.setBaseDn(TEST_BASE_DN);
-        searchExecutor.setReturnAttributes(TEST_RETURN_ATTRIBUTES);
-        connector.setSearchExecutor(searchExecutor);
+        final SearchOperation searchOperation = new SearchOperation();
+        searchOperation.setRequest(
+            SearchRequest.builder().dn(TEST_BASE_DN).returnAttributes(TEST_RETURN_ATTRIBUTES).build());
+        connector.setSearchOperation(searchOperation);
         connector.setExecutableSearchBuilder(builder == null ? 
                                              newParameterizedExecutableSearchFilterBuilder("(uid={principalName})") : builder);
         connector.setValidator(newConnectionFactoryValidator(connectionFactory));
@@ -156,10 +157,10 @@ public class LDAPDataConnectorTest {
         }
         connector.setFailFastInitialize(true);
 
-        final SearchExecutor searchExecutor = new SearchExecutor();
-        searchExecutor.setBaseDn(TEST_BASE_DN);
-        searchExecutor.setReturnAttributes(TEST_RETURN_ATTRIBUTES);
-        connector.setSearchExecutor(searchExecutor);
+        final SearchOperation searchOperation = new SearchOperation();
+        searchOperation.setRequest(
+            SearchRequest.builder().dn(TEST_BASE_DN).returnAttributes(TEST_RETURN_ATTRIBUTES).build());
+        connector.setSearchOperation(searchOperation);
         try {
             connector.initialize();
             fail("No filter builder");
@@ -198,7 +199,7 @@ public class LDAPDataConnectorTest {
             // OK
         }
         assertEquals(connector.getConnectionFactory(), connectionFactory);
-        assertEquals(connector.getSearchExecutor(), searchExecutor);
+        assertEquals(connector.getSearchOperation(), searchOperation);
         assertEquals(connector.getExecutableSearchBuilder(), requestBuilder);
         assertEquals(connector.getMappingStrategy(), mappingStrategy);
     }
@@ -209,7 +210,7 @@ public class LDAPDataConnectorTest {
 
         final ConnectionFactory connectionFactory = new DefaultConnectionFactory("ldap://localhost:55555");
         connector.setConnectionFactory(connectionFactory);
-        connector.setSearchExecutor(new SearchExecutor());
+        connector.setSearchOperation(new SearchOperation());
         connector.setExecutableSearchBuilder(newParameterizedExecutableSearchFilterBuilder("(uid={principalName})"));
         connector.setFailFastInitialize(true);
 
@@ -268,10 +269,10 @@ public class LDAPDataConnectorTest {
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
         assertEquals(
                 filter.getSearchFilter().format(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
         assertEquals(
                 filter.getResultCacheKey(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
     }
 
     @Test public void escape() throws ComponentInitializationException, ResolutionException {
@@ -282,8 +283,8 @@ public class LDAPDataConnectorTest {
                 TestSources.createResolutionContext("domain\\user*", TestSources.IDP_ENTITY_ID,
                         TestSources.SP_ENTITY_ID);
         final ExecutableSearchFilter filter = builder.build(context, null);
-        assertEquals(filter.getSearchFilter().format(), "(cn=domain\\5cuser\\2a)");
-        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5cuser\\2a)");
+        assertEquals(filter.getSearchFilter().format(), "(cn=domain\\5Cuser\\2A)");
+        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
     }
 
     @Test public void resolveTemplate() throws ComponentInitializationException, ResolutionException {
@@ -342,10 +343,10 @@ public class LDAPDataConnectorTest {
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
         assertEquals(
                 filter.getSearchFilter().format(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
         assertEquals(
                 filter.getResultCacheKey(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
     }
 
     @Test public void escapeTemplate() throws ComponentInitializationException, ResolutionException {
@@ -357,8 +358,8 @@ public class LDAPDataConnectorTest {
                 TestSources.createResolutionContext("domain\\user*", TestSources.IDP_ENTITY_ID,
                         TestSources.SP_ENTITY_ID);
         final ExecutableSearchFilter filter = builder.build(context, null);
-        assertEquals(filter.getSearchFilter().format(), "(cn=domain\\5cuser\\2a)");
-        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5cuser\\2a)");
+        assertEquals(filter.getSearchFilter().format(), "(cn=domain\\5Cuser\\2A)");
+        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
     }
 
     protected void resolve(final ExecutableSearchBuilder<ExecutableSearchFilter> builder) throws ComponentInitializationException,
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
index f3c22c5ef..6dd280240 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
@@ -25,7 +25,8 @@ import java.util.Map;
 
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.SearchExecutor;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchRequest;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -108,10 +109,10 @@ public class Regressions {
         connector.setId(TEST_CONNECTOR_NAME);
         final ConnectionFactory connectionFactory = new DefaultConnectionFactory("ldap://localhost:10390");
         connector.setConnectionFactory(connectionFactory);
-        final SearchExecutor searchExecutor = new SearchExecutor();
-        searchExecutor.setBaseDn(TEST_BASE_DN);
-        searchExecutor.setReturnAttributes(TEST_RETURN_ATTRIBUTES);
-        connector.setSearchExecutor(searchExecutor);
+        final SearchOperation searchOperation = new SearchOperation();
+        searchOperation.setRequest(
+            SearchRequest.builder().dn(TEST_BASE_DN).returnAttributes(TEST_RETURN_ATTRIBUTES).build());
+        connector.setSearchOperation(searchOperation);
         connector.setExecutableSearchBuilder(builder == null ? LDAPDataConnectorTest.newParameterizedExecutableSearchFilterBuilder(
                 "(uid={principalName})") : builder);
         connector.setValidator(LDAPDataConnectorTest.newConnectionFactoryValidator(connectionFactory));
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
index 463c4bcb4..4491107b9 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
@@ -30,24 +30,21 @@ import org.ldaptive.BindConnectionInitializer;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.Credential;
 import org.ldaptive.DefaultConnectionFactory;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.PooledConnectionFactory;
 import org.ldaptive.RandomConnectionStrategy;
 import org.ldaptive.RoundRobinConnectionStrategy;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
+import org.ldaptive.SearchConnectionValidator;
+import org.ldaptive.SearchOperation;
 import org.ldaptive.SearchRequest;
 import org.ldaptive.SearchScope;
 import org.ldaptive.handler.CaseChangeEntryHandler;
 import org.ldaptive.handler.CaseChangeEntryHandler.CaseChange;
 import org.ldaptive.handler.DnAttributeEntryHandler;
-import org.ldaptive.handler.SearchEntryHandler;
-import org.ldaptive.pool.BlockingConnectionPool;
+import org.ldaptive.handler.LdapEntryHandler;
+import org.ldaptive.handler.SearchResultHandler;
 import org.ldaptive.pool.IdlePruneStrategy;
-import org.ldaptive.pool.PoolConfig;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.SearchValidator;
-import org.ldaptive.referral.SearchReferralHandler;
-import org.ldaptive.sasl.DigestMd5Config;
-import org.ldaptive.sasl.GssApiConfig;
+import org.ldaptive.referral.FollowSearchReferralHandler;
 import org.ldaptive.sasl.Mechanism;
 import org.ldaptive.sasl.SaslConfig;
 import org.ldaptive.ssl.AllowAnyHostnameVerifier;
@@ -105,17 +102,17 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
      * </p>
      * 
      * <ul>
-     * <li>{@link SearchExecutor#setTimeLimit(Duration)} defaults to 3s, overridden by the "searchTimeLimit" attribute.
+     * <li>search time limit defaults to 3s, overridden by the "searchTimeLimit" attribute.
      * </li>
-     * <li>{@link SearchExecutor#setSizeLimit(long)} defaults to 1, overridden by the "maxResultSize" attribute.</li>
-     * <li>{@link SearchRequest#setBaseDn(String)} default to "", overridden by the "validateDN" attribute.</li>
-     * <li>{@link SearchFilter#SearchFilter(String)} defaults to "(objectClass=*)", overridden by the "validateFilter"
+     * <li>search size limit defaults to 1, overridden by the "maxResultSize" attribute.</li>
+     * <li>search base dn default to "", overridden by the "validateDN" attribute.</li>
+     * <li>search filter defaults to "(objectClass=*)", overridden by the "validateFilter"
      * attribute.</li>
-     * <li>{@link PoolConfig#setMinPoolSize(int)} defaults to 0 if neither the attribute "poolInitialSize" nor the
+     * <li>pool min size defaults to 0 if neither the attribute "poolInitialSize" nor the
      * attribute "minPoolSize" are set.</li>
-     * <li>{@link PoolConfig#setMaxPoolSize(int)} defaults to 3 if neither the attribute "poolMaxIdleSize" nor the
+     * <li>pool max size defaults to 3 if neither the attribute "poolMaxIdleSize" nor the
      * attribute "maxPoolSize" are set.</li>
-     * <li>{@link PoolConfig#setValidatePeriod(Duration)} defaults to 30m, overridden by the attribute
+     * <li>pool validation period defaults to 30m, overridden by the attribute
      * "validateTimerPeriod"</li>
      * </ul>
      * 
@@ -146,10 +143,6 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         
         final V2Parser v2Parser = new V2Parser(config, getLogPrefix());
 
-        final BeanDefinitionBuilder connectionFactory =
-                BeanDefinitionBuilder.genericBeanDefinition(DefaultConnectionFactory.class);
-        connectionFactory.addConstructorArgValue(v2Parser.createConnectionConfig(parserContext));
-
         final String searchBuilderID = v2Parser.getBeanSearchBuilderID();
         if (searchBuilderID != null) {
             builder.addPropertyReference("executableSearchBuilder", searchBuilderID);
@@ -160,18 +153,19 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             }
         }
 
-        final BeanDefinition connectionPool = v2Parser.createConnectionPool(connectionFactory.getBeanDefinition());
-        BeanDefinitionBuilder pooledConnectionFactory = null;
-        if (connectionPool != null) {
-            pooledConnectionFactory = BeanDefinitionBuilder.genericBeanDefinition(PooledConnectionFactory.class);
-            pooledConnectionFactory.addConstructorArgValue(connectionPool);
-            builder.addPropertyValue("connectionFactory", pooledConnectionFactory.getBeanDefinition());
+        final BeanDefinitionBuilder connectionFactory =
+                BeanDefinitionBuilder.genericBeanDefinition(DefaultConnectionFactory.class);
+        connectionFactory.addConstructorArgValue(v2Parser.createConnectionConfig(parserContext));
+
+        final BeanDefinition pooledConnectionFactory = v2Parser.createPooledConnectionFactory(parserContext);
+        if (pooledConnectionFactory != null) {
+            builder.addPropertyValue("connectionFactory", pooledConnectionFactory);
         } else {
             builder.addPropertyValue("connectionFactory", connectionFactory.getBeanDefinition());
         }
 
-        final BeanDefinition searchExecutor = v2Parser.createSearchExecutor();
-        builder.addPropertyValue("searchExecutor", searchExecutor);
+        final BeanDefinition searchOperation = v2Parser.createSearchOperation();
+        builder.addPropertyValue("searchOperation", searchOperation);
 
         final String mappingStrategyID = AttributeSupport.getAttributeValue(config, new QName("mappingStrategyRef"));
         if (mappingStrategyID != null) {
@@ -188,8 +182,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             builder.addPropertyReference("validator", validatorID);
         } else {
             if (pooledConnectionFactory != null) {
-                builder.addPropertyValue("validator",
-                        v2Parser.createValidator(pooledConnectionFactory.getBeanDefinition()));
+                builder.addPropertyValue("validator", v2Parser.createValidator(pooledConnectionFactory));
             } else {
                 builder.addPropertyValue("validator", v2Parser.createValidator(connectionFactory.getBeanDefinition()));
             }
@@ -324,7 +317,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 connectionInitializer.addPropertyValue("bindSaslConfig", saslConfig);
             }
             if (principal != null || principalCredential != null || saslConfig != null) {
-                connectionConfig.addPropertyValue("connectionInitializer", connectionInitializer.getBeanDefinition());
+                connectionConfig.addPropertyValue("connectionInitializers", connectionInitializer.getBeanDefinition());
             }
             final String connectionStrategy = AttributeSupport.getAttributeValue(
                 configElement, new QName("connectionStrategy"));
@@ -485,12 +478,12 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         }
 
         /**
-         * Creates a new search executor bean definition from a v2 XML configuration.
+         * Creates a new search operation bean definition from a v2 XML configuration.
          *
-         * @return search executor bean definition
+         * @return search operation bean definition
          */
         // CheckStyle: CyclomaticComplexity|MethodLength OFF
-        @Nonnull public BeanDefinition createSearchExecutor() {
+        @Nonnull public BeanDefinition createSearchOperation() {
             final String baseDn = AttributeSupport.getAttributeValue(configElement, new QName("baseDN"));
             final String searchScope = AttributeSupport.getAttributeValue(configElement, new QName("searchScope"));
             final String derefAliases = AttributeSupport.getAttributeValue(configElement, new QName("derefAliases"));
@@ -502,38 +495,41 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             final String lowercaseAttributeNames =
                     AttributeSupport.getAttributeValue(configElement, new QName("lowercaseAttributeNames"));
 
-            final BeanDefinitionBuilder searchExecutor =
-                    BeanDefinitionBuilder.genericBeanDefinition(SearchExecutor.class);
+            final BeanDefinitionBuilder searchRequest =
+                    BeanDefinitionBuilder.genericBeanDefinition(SearchRequest.class);
             if (baseDn != null) {
-                searchExecutor.addPropertyValue("baseDn", baseDn);
+                searchRequest.addPropertyValue("baseDn", baseDn);
             }
             if (searchScope != null) {
-                searchExecutor.addPropertyValue("searchScope", searchScope);
+                searchRequest.addPropertyValue("searchScope", searchScope);
             }
             if (derefAliases != null) {
-                searchExecutor.addPropertyValue("derefAliases", derefAliases);
+                searchRequest.addPropertyValue("derefAliases", derefAliases);
             }
+
+            final BeanDefinitionBuilder searchOperation =
+                    BeanDefinitionBuilder.genericBeanDefinition(SearchOperation.class);
             if (followReferrals != null) {
                 final BeanDefinitionBuilder handler =
-                    BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildReferralHandler");
+                    BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildReferralHandlers");
                 handler.addConstructorArgValue(followReferrals);
-                searchExecutor.addPropertyValue("referralHandler", handler.getBeanDefinition());
+                searchOperation.addPropertyValue("searchResultHandlers", handler.getBeanDefinition());
             }
             if (searchTimeLimit != null) {
-                searchExecutor.addPropertyValue("timeLimit", searchTimeLimit);
+                searchRequest.addPropertyValue("timeLimit", searchTimeLimit);
             } else {
-                searchExecutor.addPropertyValue("timeLimit", Duration.ofSeconds(3));
+                searchRequest.addPropertyValue("timeLimit", Duration.ofSeconds(3));
             }
             if (maxResultSize != null) {
-                searchExecutor.addPropertyValue("sizeLimit", maxResultSize);
+                searchRequest.addPropertyValue("sizeLimit", maxResultSize);
             } else {
-                searchExecutor.addPropertyValue("sizeLimit", 1);
+                searchRequest.addPropertyValue("sizeLimit", 1);
             }
 
             final BeanDefinitionBuilder handlers =
                     BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildSearchEntryHandlers");
             handlers.addConstructorArgValue(lowercaseAttributeNames);
-            searchExecutor.addPropertyValue("searchEntryHandlers", handlers.getBeanDefinition());
+            searchOperation.addPropertyValue("entryHandlers", handlers.getBeanDefinition());
 
             final List<Element> returnAttrsElements = ElementSupport.getChildElementsByTagNameNS(configElement, 
                     AttributeResolverNamespaceHandler.NAMESPACE, "ReturnAttributes");
@@ -548,7 +544,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 final BeanDefinitionBuilder returnAttrs =
                         BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildStringList");
                 returnAttrs.addConstructorArgValue(ElementSupport.getElementContentAsString(returnAttrsElement));
-                searchExecutor.addPropertyValue("returnAttributes", returnAttrs.getBeanDefinition());
+                searchRequest.addPropertyValue("returnAttributes", returnAttrs.getBeanDefinition());
             }
 
             final List<Element> binaryAttrsElements = ElementSupport.getChildElementsByTagNameNS(configElement,
@@ -564,10 +560,11 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 final BeanDefinitionBuilder binaryAttrs =
                   BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildStringList");
                 binaryAttrs.addConstructorArgValue(ElementSupport.getElementContentAsString(binaryAttrsElement));
-                searchExecutor.addPropertyValue("binaryAttributes", binaryAttrs.getBeanDefinition());
+                searchRequest.addPropertyValue("binaryAttributes", binaryAttrs.getBeanDefinition());
             }
 
-            return searchExecutor.getBeanDefinition();
+            searchOperation.addPropertyValue("request", searchRequest.getBeanDefinition());
+            return searchOperation.getBeanDefinition();
         }
         // CheckStyle: CyclomaticComplexity|MethodLength ON
 
@@ -592,14 +589,13 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         // CheckStyle: CyclomaticComplexity ON
 
         /**
-         * Creates a new connection pool bean definition from a v2 XML configuration.
-         * 
-         * @param connectionFactory used by the connection pool
-         * 
-         * @return connection pool bean definition
+         * Creates a new pooled connection factory bean definition from a v2 XML configuration.
+         *
+         * @param parserContext bean definition parsing context
+         * @return pooled connection factory bean definition
          */
         // CheckStyle: MethodLength OFF
-        @Nullable public BeanDefinition createConnectionPool(final BeanDefinition connectionFactory) {
+        @Nullable public BeanDefinition createPooledConnectionFactory(@Nonnull final ParserContext parserContext) {
 
             final Element poolConfigElement = getConnectionPoolElement();
             if (null == poolConfigElement) {
@@ -617,11 +613,12 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             final String expirationTime =
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("expirationTime"));
 
-            final BeanDefinitionBuilder pool =
-                    BeanDefinitionBuilder.genericBeanDefinition(BlockingConnectionPool.class);
-            pool.addPropertyValue("name", "resolver-pool");
+            final BeanDefinitionBuilder connectionFactory =
+                    BeanDefinitionBuilder.genericBeanDefinition(PooledConnectionFactory.class);
+            connectionFactory.addPropertyValue("name", "resolver-pool");
+            connectionFactory.addPropertyValue("connectionConfig", createConnectionConfig(parserContext));
             if (blockWaitTime != null) {
-                pool.addPropertyValue("blockWaitTime", blockWaitTime);
+                connectionFactory.addPropertyValue("blockWaitTime", blockWaitTime);
             }
             if (expirationTime != null) {
                 final String prunePeriod =
@@ -632,85 +629,60 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                     strategy.addConstructorArgValue(prunePeriod);
                 }
                 strategy.addConstructorArgValue(expirationTime);
-                pool.addPropertyValue("pruneStrategy", strategy.getBeanDefinition());
+                connectionFactory.addPropertyValue("pruneStrategy", strategy.getBeanDefinition());
+            }
+
+            final String minPoolSize = AttributeSupport.getAttributeValue(poolConfigElement, new QName("minPoolSize"));
+            final String maxPoolSize = AttributeSupport.getAttributeValue(poolConfigElement, new QName("maxPoolSize"));
+            final String validatePeriodically =
+                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validatePeriodically"));
+            final String validateOnCheckout =
+                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateOnCheckout"));
+
+            if (minPoolSize == null) {
+                connectionFactory.addPropertyValue("minPoolSize", 0);
+            } else {
+                connectionFactory.addPropertyValue("minPoolSize", minPoolSize);
+            }
+            if (maxPoolSize == null) {
+                connectionFactory.addPropertyValue("maxPoolSize", 3);
+            } else {
+                connectionFactory.addPropertyValue("maxPoolSize", maxPoolSize);
+            }
+            if (validatePeriodically != null) {
+                connectionFactory.addPropertyValue("validatePeriodically", validatePeriodically);
+            }
+            if (validateOnCheckout != null) {
+                connectionFactory.addPropertyValue("validateOnCheckOut", validateOnCheckout);
             }
-            pool.addPropertyValue("poolConfig", createPoolConfig());
 
             final BeanDefinitionBuilder validator =
                     BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildSearchValidator");
-            validator.addConstructorArgValue(
-                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validatePeriodically")));
             validator.addConstructorArgValue(
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateDN")));
             validator.addConstructorArgValue(
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateFilter")));
-            pool.addPropertyValue("validator", validator.getBeanDefinition());
+            validator.addConstructorArgValue(
+                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateTimerPeriod")));
+            connectionFactory.addPropertyValue("validator", validator.getBeanDefinition());
 
-            pool.addPropertyValue("connectionFactory", connectionFactory);
             final String failFastInitialize =
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("failFastInitialize"));
             if (failFastInitialize != null) {
                 // V4 Deprecation
                 DeprecationSupport.warnOnce(ObjectType.ATTRIBUTE, "failfastInitialize (on a ConnectionPool element)",
                         null, "failfastInitialize (on a DataConnector)");
-                pool.addPropertyValue("failFastInitialize", failFastInitialize);
+                connectionFactory.addPropertyValue("failFastInitialize", failFastInitialize);
             } else if (configElement.hasAttributeNS(null, ATTR_FAIL_FAST)) {
-                pool.addPropertyValue("failFastInitialize",
+                connectionFactory.addPropertyValue("failFastInitialize",
                         StringSupport.trimOrNull(configElement.getAttributeNS(null, ATTR_FAIL_FAST)));
             }
-            pool.setInitMethodName("initialize");
-            return pool.getBeanDefinition();
+            connectionFactory.setInitMethodName("initialize");
+            return connectionFactory.getBeanDefinition();
         }
 
         // CheckStyle: MethodLength ON
 
-        /**
-         * Creates a new pool config bean definition from a v2 XML configuration.
-         * 
-         * @return pool config bean definition
-         */
-        @Nullable protected BeanDefinition createPoolConfig() {
-            final Element poolConfigElement = getConnectionPoolElement();
-            if (poolConfigElement == null) {
-                return null;
-            }
-
-            final String minPoolSize = AttributeSupport.getAttributeValue(poolConfigElement, new QName("minPoolSize"));
-            final String maxPoolSize = AttributeSupport.getAttributeValue(poolConfigElement, new QName("maxPoolSize"));
-            final String validatePeriodically =
-                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validatePeriodically"));
-            final String validateTimerPeriod =
-                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateTimerPeriod"));
-            final String validateOnCheckout =
-                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateOnCheckout"));
-
-            final BeanDefinitionBuilder poolConfig = BeanDefinitionBuilder.genericBeanDefinition(PoolConfig.class);
-            if (minPoolSize == null) {
-                poolConfig.addPropertyValue("minPoolSize", 0);
-            } else {
-                poolConfig.addPropertyValue("minPoolSize", minPoolSize);
-            }
-            if (maxPoolSize == null) {
-                poolConfig.addPropertyValue("maxPoolSize", 3);
-            } else {
-                poolConfig.addPropertyValue("maxPoolSize", maxPoolSize);
-            }
-            if (validatePeriodically != null) {
-                poolConfig.addPropertyValue("validatePeriodically", validatePeriodically);
-            }
-            if (validateTimerPeriod != null) {
-                poolConfig.addPropertyValue("validatePeriod", validateTimerPeriod);
-            } else {
-                poolConfig.addPropertyValue("validatePeriod", Duration.ofMinutes(30));
-            }
-            if (validateOnCheckout != null)  {
-                poolConfig.addPropertyValue("validateOnCheckOut", validateOnCheckout);
-            } else {
-                poolConfig.addPropertyValue("validateOnCheckOut", false);
-            }
-            return poolConfig.getBeanDefinition();
-        }
-
         /**
          * Creates a new sasl config bean definition from a v2 XML configuration.
          *
@@ -735,41 +707,41 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             final List<Element> saslProperties = ElementSupport.getChildElementsByTagNameNS(saslConfigElement,
                     AttributeResolverNamespaceHandler.NAMESPACE, "SASLProperty");
 
-            final BeanDefinitionBuilder saslConfig;
-            if ("DIGEST_MD5".equals(mechanism)) {
-                saslConfig = BeanDefinitionBuilder.genericBeanDefinition(DigestMd5Config.class);
-                if (realm != null) {
-                    saslConfig.addPropertyValue("realm", realm);
-                }
-            } else if ("GSSAPI".equals(mechanism)) {
-                saslConfig = BeanDefinitionBuilder.genericBeanDefinition(GssApiConfig.class);
-                if (realm != null) {
-                    saslConfig.addPropertyValue("realm", realm);
-                }
-            } else {
-                saslConfig = BeanDefinitionBuilder.genericBeanDefinition(SaslConfig.class);
-                saslConfig.addPropertyValue("mechanism", mechanism);
-            }
+            final BeanDefinitionBuilder saslConfig = BeanDefinitionBuilder.genericBeanDefinition(SaslConfig.class);
+            saslConfig.addPropertyValue("mechanism", mechanism);
             if (authorizationId != null) {
                 saslConfig.addPropertyValue("authorizationId", authorizationId);
             }
+            if (realm != null) {
+                saslConfig.addPropertyValue("realm", realm);
+            }
 
             if (!saslProperties.isEmpty()) {
                 for (final Element property : saslProperties) {
                     final String name = AttributeSupport.getAttributeValue(property, null, "name");
                     final String value = AttributeSupport.getAttributeValue(property, null, "value");
                     if ("javax.security.sasl.qop".equals(name)) {
-                        if ("auth".equalsIgnoreCase(value)) {
-                            saslConfig.addPropertyValue("qualityOfProtection", "AUTH");
-                        } else if ("auth-int".equalsIgnoreCase(value)) {
-                            saslConfig.addPropertyValue("qualityOfProtection", "AUTH_INT");
-                        } else if ("auth-conf".equalsIgnoreCase(value)) {
-                            saslConfig.addPropertyValue("qualityOfProtection", "AUTH_CONF");
-                        } else {
-                            saslConfig.addPropertyValue("qualityOfProtection", value);
+                        final String[] splitValues = value.split(",");
+                        final String[] values = new String[splitValues.length];
+                        for (int i = 0; i < splitValues.length; i++) {
+                            if ("auth".equalsIgnoreCase(splitValues[i].trim())) {
+                                values[i] = "AUTH";
+                            } else if ("auth-int".equalsIgnoreCase(splitValues[i].trim())) {
+                                values[i] = "AUTH_INT";
+                            } else if ("auth-conf".equalsIgnoreCase(splitValues[i].trim())) {
+                                values[i] = "AUTH_CONF";
+                            } else {
+                                values[i] = splitValues[i].trim();
+                            }
                         }
+                        saslConfig.addPropertyValue("qualityOfProtection", values);
                     } else if ("javax.security.sasl.strength".equals(name)) {
-                        saslConfig.addPropertyValue("securityStrength", value);
+                        final String[] splitValues = value.split(",");
+                        final String[] values = new String[splitValues.length];
+                        for (int i = 0; i < splitValues.length; i++) {
+                            values[i] = splitValues[i].trim();
+                        }
+                        saslConfig.addPropertyValue("securityStrength", values);
                     } else if ("javax.security.sasl.server.authentication".equals(name)) {
                         saslConfig.addPropertyValue("mutualAuthentication", value);
                     }
@@ -865,19 +837,18 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         }
 
         /**
-         * Returns a search validator or null if validatePeriodically is false.
+         * Returns a search validator.
          *
-         * @param validatePeriodically whether to create a search validator
          * @param validateDN baseDN to search on
          * @param validateFilter to search with
+         * @param validatePeriod period over which to validate connection when periodic validation in enabled
          *
          * @return  search validator or null
          */
-        @Nullable public static SearchValidator buildSearchValidator(@Nullable final String validatePeriodically,
-                @Nullable final String validateDN, @Nullable final String validateFilter) {
-            if (!Boolean.valueOf(validatePeriodically)) {
-                return null;
-            }
+        @Nullable public static SearchConnectionValidator buildSearchValidator(
+                @Nullable final String validateDN,
+                @Nullable final String validateFilter,
+                @Nullable final String validatePeriod) {
             final SearchRequest searchRequest = new SearchRequest();
             searchRequest.setReturnAttributes("1.1");
             searchRequest.setSearchScope(SearchScope.OBJECT);
@@ -887,14 +858,17 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             } else {
                 searchRequest.setBaseDn("");
             }
-            final SearchFilter searchFilter = new SearchFilter();
+            final FilterTemplate searchFilter = new FilterTemplate();
             if (validateFilter != null) {
                 searchFilter.setFilter(validateFilter);
             } else {
                 searchFilter.setFilter("(objectClass=*)");
             }
-            searchRequest.setSearchFilter(searchFilter);
-            final SearchValidator validator = new SearchValidator();
+            searchRequest.setFilter(searchFilter);
+            final SearchConnectionValidator validator = new SearchConnectionValidator();
+            if (validatePeriod != null) {
+                validator.setValidatePeriod(Duration.parse(validatePeriod));
+            }
             validator.setSearchRequest(searchRequest);
             return validator;
         }
@@ -924,9 +898,9 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
          * @param lowercaseAttributeNames boolean string value
          * @return list of search entry handlers
          */
-        @Nonnull public static List<SearchEntryHandler> buildSearchEntryHandlers(
+        @Nonnull public static List<LdapEntryHandler> buildSearchEntryHandlers(
                 @Nullable final String lowercaseAttributeNames) {
-            final List<SearchEntryHandler> handlers = new ArrayList<>();
+            final List<LdapEntryHandler> handlers = new ArrayList<>();
             handlers.add(new DnAttributeEntryHandler());
             if (Boolean.valueOf(lowercaseAttributeNames)) {
                 final CaseChangeEntryHandler entryHandler = new CaseChangeEntryHandler();
@@ -937,16 +911,18 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         }
 
         /**
-         * Returns a search referral handler or null if followReferrals is false.
+         * Returns search referral handlers or null if followReferrals is false.
          *
          * @param followReferrals whether to create a search referral handler
          *
-         * @return  search referral handler or null
+         * @return  search result handlers or null
          */
-        @Nonnull public static SearchReferralHandler buildReferralHandler(
+        @Nonnull public static List<SearchResultHandler> buildReferralHandlers(
                 @Nullable final String followReferrals) {
+            final List<SearchResultHandler> handlers = new ArrayList<>();
             if (followReferrals != null && Boolean.valueOf(followReferrals)) {
-                return new SearchReferralHandler();
+                handlers.add(new FollowSearchReferralHandler());
+                return handlers;
             }
             return null;
         }
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java
index d77dabee0..3f0d74480 100644
--- a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java
@@ -35,22 +35,25 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 import org.ldaptive.BindConnectionInitializer;
+import org.ldaptive.BindResponse;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.pool.BlockingConnectionPool;
+import org.ldaptive.PooledConnectionFactory;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.filter.EqualityFilter;
 import org.ldaptive.pool.IdlePruneStrategy;
-import org.ldaptive.pool.PoolConfig;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.SearchValidator;
-import org.ldaptive.referral.SearchReferralHandler;
-import org.ldaptive.sasl.DigestMd5Config;
+import org.ldaptive.SearchConnectionValidator;
+import org.ldaptive.referral.FollowSearchReferralHandler;
+import org.ldaptive.sasl.DefaultSaslClientRequest;
+import org.ldaptive.sasl.DigestMD5BindRequest;
 import org.ldaptive.sasl.Mechanism;
 import org.ldaptive.sasl.QualityOfProtection;
+import org.ldaptive.sasl.SaslClient;
 import org.ldaptive.sasl.SaslConfig;
 import org.ldaptive.sasl.SecurityStrength;
 import org.ldaptive.ssl.CredentialConfig;
 import org.ldaptive.ssl.SslConfig;
+import org.ldaptive.transport.TransportConnection;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
 import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.core.io.ClassPathResource;
@@ -102,7 +105,17 @@ public class LDAPDataConnectorParserTest {
     private InMemoryDirectoryServer directoryServer;
     
     private GenericApplicationContext pendingTeardownContext = null;
-    
+
+    /** Override the default SASL client for testing. */
+    public static class TestSaslClient implements SaslClient<DefaultSaslClientRequest>
+    {
+
+        @Override
+        public BindResponse bind(final TransportConnection conn, final DefaultSaslClientRequest request) {
+            return BindResponse.builder().resultCode(org.ldaptive.ResultCode.SUCCESS).build();
+        }
+    }
+
     @AfterMethod public void tearDownTestContext() {
         if (null == pendingTeardownContext ) {
             return;
@@ -165,6 +178,9 @@ public class LDAPDataConnectorParserTest {
         directoryServer.importFromLDIF(true,
                 "src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldapDataConnectorTest.ldif");
         directoryServer.startListening();
+        System.setProperty(
+                "org.ldaptive.sasl.defaultSaslClient",
+                TestSaslClient.class.getName());
     }
 
     /**
@@ -172,6 +188,7 @@ public class LDAPDataConnectorParserTest {
      */
     @AfterClass public void teardownDirectoryServer() {
         directoryServer.shutDown(true);
+        System.clearProperty("org.ldaptive.sasl.defaultSaslClient");
     }
 
     @Test public void v2Config() throws Exception {
@@ -235,9 +252,8 @@ public class LDAPDataConnectorParserTest {
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
         assertEquals(connConfig.getLdapUrl(), "ldap://localhost:10389");
-        assertFalse(connConfig.getUseSSL());
         assertFalse(connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertEquals(connInitializer.getBindDn(), "cn=Directory Manager");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
         assertEquals(connConfig.getConnectTimeout(), Duration.ofSeconds(3));
@@ -248,12 +264,12 @@ public class LDAPDataConnectorParserTest {
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(3));
-        assertNull(searchExecutor.getReferralHandler());
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertEquals(searchOperation.getRequest().getBaseDn(), "");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(3));
+        assertNull(searchOperation.getSearchResultHandlers());
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
@@ -287,45 +303,42 @@ public class LDAPDataConnectorParserTest {
         assertEquals(Duration.ZERO, dataConnector.getNoRetryDelay());
         final PooledConnectionFactory connFactory = (PooledConnectionFactory) dataConnector.getConnectionFactory();
         assertNotNull(connFactory);
-        final BlockingConnectionPool connPool = (BlockingConnectionPool) connFactory.getConnectionPool();
-        assertNotNull(connPool);
-        assertNull(connPool.getBlockWaitTime());
-        assertEquals("resolver-pool", connPool.getName());
-        final PoolConfig poolConfig = connPool.getPoolConfig();
-        assertNotNull(poolConfig);
-        assertEquals(poolConfig.getMinPoolSize(), 0);
-        assertEquals(poolConfig.getMaxPoolSize(), 3);
-        assertFalse(poolConfig.isValidatePeriodically());
-        assertEquals(poolConfig.getValidatePeriod(), Duration.ofMinutes(30));
-        assertTrue(connPool.getFailFastInitialize());
-        assertNull(connPool.getValidator());
-
-        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connPool.getPruneStrategy();
+        // note that default value changed from null to PT1M
+        assertEquals(connFactory.getBlockWaitTime(), Duration.ofMinutes(1));
+        assertEquals("resolver-pool", connFactory.getName());
+        assertEquals(connFactory.getMinPoolSize(), 0);
+        assertEquals(connFactory.getMaxPoolSize(), 3);
+        assertFalse(connFactory.isValidatePeriodically());
+        // note that pooled connection factories have a validator by default
+        assertNotNull(connFactory.getValidator());
+        assertEquals(connFactory.getValidator().getValidatePeriod(), Duration.ofMinutes(30));
+        assertTrue(connFactory.getFailFastInitialize());
+
+        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connFactory.getPruneStrategy();
         assertNotNull(pruneStrategy);
         assertEquals(pruneStrategy.getPrunePeriod(), Duration.ofMinutes(5));
         assertEquals(pruneStrategy.getIdleTime(), Duration.ofMinutes(10));
 
-        final ConnectionConfig connConfig = connPool.getConnectionFactory().getConnectionConfig();
+        final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
         assertEquals(connConfig.getLdapUrl(), "ldap://localhost:10389");
-        assertFalse(connConfig.getUseSSL());
         assertFalse(connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertEquals(connInitializer.getBindDn(), "cn=Directory Manager");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
         assertEquals(connConfig.getConnectTimeout(), Duration.ofSeconds(3));
         assertEquals(connConfig.getResponseTimeout(), Duration.ofSeconds(3));
 
-        final SslConfig sslConfig = connPool.getConnectionFactory().getConnectionConfig().getSslConfig();
+        final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
         assertNotNull(sslConfig);
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(3));
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertEquals(searchOperation.getRequest().getBaseDn(), "");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(3));
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
@@ -383,7 +396,7 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         assertEquals(connInitializer.getBindDn(), "manager at shibboleth.net");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
@@ -392,9 +405,9 @@ public class LDAPDataConnectorParserTest {
         assertEquals(saslConfig.getMechanism(), Mechanism.DIGEST_MD5);
         assertEquals(saslConfig.getAuthorizationId(), "authzID");
         assertEquals(saslConfig.getMutualAuthentication(), Boolean.TRUE);
-        assertEquals(saslConfig.getQualityOfProtection(), QualityOfProtection.AUTH_INT);
-        assertEquals(saslConfig.getSecurityStrength(), SecurityStrength.HIGH);
-        assertEquals(((DigestMd5Config) saslConfig).getRealm(), "shibboleth.net");
+        assertEquals(saslConfig.getQualityOfProtection()[0], QualityOfProtection.AUTH_INT);
+        assertEquals(saslConfig.getSecurityStrength()[0], SecurityStrength.HIGH);
+        assertEquals(saslConfig.getRealm(), "shibboleth.net");
     }
 
     @Test public void v2AuthenticationTypeConfig() throws Exception {
@@ -408,14 +421,14 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         assertEquals(connInitializer.getBindDn(), "manager at shibboleth.net");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
         final SaslConfig saslConfig = connInitializer.getBindSaslConfig();
         assertNotNull(saslConfig);
         assertEquals(saslConfig.getMechanism(), Mechanism.DIGEST_MD5);
-        assertEquals(saslConfig.getAuthorizationId(), "");
+        assertNull(saslConfig.getAuthorizationId());
         assertNull(saslConfig.getMutualAuthentication());
         assertNull(saslConfig.getQualityOfProtection());
         assertNull(saslConfig.getSecurityStrength());
@@ -432,17 +445,18 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         final SaslConfig saslConfig = connInitializer.getBindSaslConfig();
         assertNull(saslConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(3));
-        final SearchReferralHandler referralHandler = (SearchReferralHandler) searchExecutor.getReferralHandler();
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertNotNull(searchOperation.getRequest());
+        assertEquals(searchOperation.getRequest().getBaseDn(), "");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(3));
+        final FollowSearchReferralHandler referralHandler = (FollowSearchReferralHandler) searchOperation.getSearchResultHandlers()[0];
         assertNotNull(referralHandler);
     }
 
@@ -457,7 +471,7 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         assertNull(connInitializer.getBindDn());
         assertNull(connInitializer.getBindCredential());
@@ -589,50 +603,45 @@ public class LDAPDataConnectorParserTest {
 
         final PooledConnectionFactory connFactory = (PooledConnectionFactory) dataConnector.getConnectionFactory();
         assertNotNull(connFactory);
-        final BlockingConnectionPool connPool = (BlockingConnectionPool) connFactory.getConnectionPool();
-        assertNotNull(connPool);
-        assertEquals(connPool.getBlockWaitTime(), Duration.ofSeconds(5));
-        assertEquals(connPool.getName(), "resolver-pool");
-        final PoolConfig poolConfig = connPool.getPoolConfig();
-        assertNotNull(poolConfig);
-        assertEquals(poolConfig.getMinPoolSize(), 5);
-        assertEquals(poolConfig.getMaxPoolSize(), 10);
-        assertTrue(poolConfig.isValidatePeriodically());
-        assertEquals(poolConfig.getValidatePeriod(), Duration.ofMinutes(15));
-        assertFalse(connPool.getFailFastInitialize());
-
-        final SearchValidator searchValidator = (SearchValidator) connPool.getValidator();
+        assertEquals(connFactory.getBlockWaitTime(), Duration.ofSeconds(5));
+        assertEquals(connFactory.getName(), "resolver-pool");
+        assertEquals(connFactory.getMinPoolSize(), 5);
+        assertEquals(connFactory.getMaxPoolSize(), 10);
+        assertTrue(connFactory.isValidatePeriodically());
+        assertEquals(connFactory.getValidator().getValidatePeriod(), Duration.ofMinutes(15));
+        assertFalse(connFactory.getFailFastInitialize());
+
+        final SearchConnectionValidator searchValidator = (SearchConnectionValidator) connFactory.getValidator();
         assertNotNull(searchValidator);
         assertEquals(searchValidator.getSearchRequest().getBaseDn(), "dc=shibboleth,dc=net");
-        assertEquals(searchValidator.getSearchRequest().getSearchFilter().getFilter(), "(ou=people)");
+        assertEquals(searchValidator.getSearchRequest().getFilter(), new EqualityFilter("ou", "people"));
 
-        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connPool.getPruneStrategy();
+        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connFactory.getPruneStrategy();
         assertNotNull(pruneStrategy);
         assertEquals(pruneStrategy.getPrunePeriod(), Duration.ofMinutes(5));
         assertEquals(pruneStrategy.getIdleTime(), Duration.ofMinutes(10));
 
-        final ConnectionConfig connConfig = connPool.getConnectionFactory().getConnectionConfig();
+        final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
         assertEquals(connConfig.getLdapUrl(), "ldap://localhost:10389");
-        assertFalse(connConfig.getUseSSL());
         assertTrue(connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertEquals(connInitializer.getBindDn(), "cn=Directory Manager");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
         assertEquals(connConfig.getConnectTimeout(), Duration.ofSeconds(2));
         assertEquals(connConfig.getResponseTimeout(), Duration.ofSeconds(4));
 
-        final SslConfig sslConfig = connPool.getConnectionFactory().getConnectionConfig().getSslConfig();
+        final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
         assertNotNull(sslConfig);
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "ou=people,dc=shibboleth,dc=net");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(7));
-        assertNull(searchExecutor.getReferralHandler());
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertEquals(searchOperation.getRequest().getBaseDn(), "ou=people,dc=shibboleth,dc=net");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(7));
+        assertNull(searchOperation.getSearchResultHandlers());
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
index 932903987..e156f4a73 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
@@ -2,53 +2,53 @@
 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
-    <bean id="connectionFactory" class="org.ldaptive.pool.PooledConnectionFactory">
-        <property name="connectionPool">
-            <bean class="org.ldaptive.pool.BlockingConnectionPool" init-method="initialize" p:blockWaitTime="PT5S" p:failFastInitialize="false" p:name="resolver-pool">
-                <constructor-arg index="0">
-                    <bean class="org.ldaptive.pool.PoolConfig" p:minPoolSize="5" p:maxPoolSize="10"
-                        p:validatePeriodically="true" p:validatePeriod="PT15M" />
-                </constructor-arg>
-                <constructor-arg index="1">
-                    <bean class="org.ldaptive.DefaultConnectionFactory">
-                        <property name="connectionConfig">
-                            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="ldap://localhost:10389"
-                                p:useStartTLS="true" p:connectTimeout="PT2S" p:responseTimeout="PT4S">
-                                <property name="connectionInitializer">
-                                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="cn=Directory Manager"
-                                        p:bindCredential="password" />
-                                </property>
-                                <property name="sslConfig">
-                                    <bean class="org.ldaptive.ssl.SslConfig">
-                                        <property name="credentialConfig">
-                                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
-                                                p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
-                                                p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
-                                                p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
-                                        </property>
-                                    </bean>
-                                </property>
-                            </bean>
-                        </property>
-                    </bean>
-                </constructor-arg>
-                <property name="validator">
-                    <bean class="org.ldaptive.pool.SearchValidator">
-                        <property name="searchRequest">
-                            <bean class="org.ldaptive.SearchRequest">
-                                <constructor-arg value="dc=shibboleth,dc=net" />
-                                <constructor-arg value="(ou=people)" />
-                            </bean>
+    <bean id="connectionFactory" class="org.ldaptive.PooledConnectionFactory" init-method="initialize"
+          p:blockWaitTime="PT5S"
+          p:failFastInitialize="false"
+          p:name="resolver-pool"
+          p:minPoolSize="5"
+          p:maxPoolSize="10"
+          p:validatePeriodically="true">
+        <property name="connectionConfig">
+            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="ldap://localhost:10389"
+                  p:useStartTLS="true"
+                  p:connectTimeout="PT2S" p:responseTimeout="PT4S">
+                <property name="connectionInitializers">
+                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="cn=Directory Manager"
+                          p:bindCredential="password" />
+                </property>
+                <property name="sslConfig">
+                    <bean class="org.ldaptive.ssl.SslConfig">
+                        <property name="credentialConfig">
+                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
+                                  p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
+                                  p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
+                                  p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
                         </property>
                     </bean>
                 </property>
-                <property name="pruneStrategy">
-                    <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="PT5M" p:idleTime="PT10M" />
+            </bean>
+        </property>
+        <property name="validator">
+            <bean class="org.ldaptive.SearchConnectionValidator" p:validatePeriod="PT15M">
+                <property name="searchRequest">
+                    <bean class="org.ldaptive.SearchRequest">
+                        <constructor-arg value="dc=shibboleth,dc=net" />
+                        <constructor-arg value="(ou=people)" />
+                    </bean>
                 </property>
             </bean>
         </property>
+        <property name="pruneStrategy">
+            <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="PT5M"
+                  p:idleTime="PT10M" />
+        </property>
+    </bean>
+    <bean class="org.ldaptive.SearchOperation">
+        <property name="request">
+            <bean class="org.ldaptive.SearchRequest" p:baseDn="ou=people,dc=shibboleth,dc=net" p:returnAttributes="uid,homephone,mail" p:timeLimit="PT7S" />
+        </property>
     </bean>
-    <bean class="org.ldaptive.SearchExecutor" p:baseDn="ou=people,dc=shibboleth,dc=net" p:returnAttributes="uid,homephone,mail" p:timeLimit="PT7S" />
     <bean id="cache" class="com.google.common.cache.Cache" factory-bean="cacheBuilderExternal" factory-method="build" />
     <bean id="filter" class="net.shibboleth.idp.attribute.resolver.dc.ldap.TemplatedExecutableSearchFilterBuilder"
         p:templateText="(uid=${resolutionContext.principal})" p:velocityEngine-ref="shibboleth.VelocityEngine"
@@ -62,5 +62,5 @@
         </property>
     </bean>
     <bean id="validator" class="net.shibboleth.idp.attribute.resolver.dc.ldap.impl.ConnectionFactoryValidator"
-      p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
+        p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
 </beans>
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
index c2fd91d9a..e11b6a604 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
@@ -4,57 +4,53 @@
     xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
-    <bean id="connectionFactory" class="org.ldaptive.pool.PooledConnectionFactory">
-        <property name="connectionPool">
-            <bean class="org.ldaptive.pool.BlockingConnectionPool" init-method="initialize"
-                p:blockWaitTime="%{connectionPool.blockWaitTime}" p:failFastInitialize="%{connectionPool.failFastInitialize}" p:name="resolver-pool">
-                <constructor-arg index="0">
-                    <bean class="org.ldaptive.pool.PoolConfig" p:minPoolSize="%{connectionPool.minPoolSize}"
-                        p:maxPoolSize="%{connectionPool.maxPoolSize}" p:validatePeriodically="%{connectionPool.validatePeriodically}"
-                        p:validatePeriod="%{connectionPool.validatePeriod}" />
-                </constructor-arg>
-                <constructor-arg index="1">
-                    <bean class="org.ldaptive.DefaultConnectionFactory">
-                        <property name="connectionConfig">
-                            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="%{connectionConfig.ldapURL}"
-                                p:useStartTLS="%{connectionConfig.useStartTLS}"
-                                p:connectTimeout="%{connectionConfig.connectTimeout}" p:responseTimeout="%{connectionConfig.responseTimeout}">
-                                <property name="connectionInitializer">
-                                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="%{connectionConfig.bindDn}"
-                                        p:bindCredential="%{connectionConfig.bindCredential}" />
-                                </property>
-                                <property name="sslConfig">
-                                    <bean class="org.ldaptive.ssl.SslConfig">
-                                        <property name="credentialConfig">
-                                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
-                                                p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
-                                                p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
-                                                p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
-                                        </property>
-                                    </bean>
-                                </property>
-                            </bean>
-                        </property>
-                    </bean>
-                </constructor-arg>
-                <property name="validator">
-                    <bean class="org.ldaptive.pool.SearchValidator">
-                        <property name="searchRequest">
-                            <bean class="org.ldaptive.SearchRequest">
-                                <constructor-arg value="%{connectionPool.validator.baseDn}" />
-                                <constructor-arg value="%{connectionPool.validator.filter}" />
-                            </bean>
+    <bean id="connectionFactory" class="org.ldaptive.PooledConnectionFactory" init-method="initialize"
+          p:blockWaitTime="%{connectionPool.blockWaitTime}"
+          p:failFastInitialize="%{connectionPool.failFastInitialize}"
+          p:name="resolver-pool"
+          p:minPoolSize="%{connectionPool.minPoolSize}"
+          p:maxPoolSize="%{connectionPool.maxPoolSize}"
+          p:validatePeriodically="%{connectionPool.validatePeriodically}">
+        <property name="connectionConfig">
+            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="%{connectionConfig.ldapURL}"
+                  p:useStartTLS="%{connectionConfig.useStartTLS}"
+                  p:connectTimeout="%{connectionConfig.connectTimeout}" p:responseTimeout="%{connectionConfig.responseTimeout}">
+                <property name="connectionInitializers">
+                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="%{connectionConfig.bindDn}"
+                          p:bindCredential="%{connectionConfig.bindCredential}" />
+                </property>
+                <property name="sslConfig">
+                    <bean class="org.ldaptive.ssl.SslConfig">
+                        <property name="credentialConfig">
+                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
+                                  p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
+                                  p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
+                                  p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
                         </property>
                     </bean>
                 </property>
-                <property name="pruneStrategy">
-                    <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="%{connectionPool.pruneStrategy.prunePeriod}"
-                        p:idleTime="%{connectionPool.pruneStrategy.idleTime}" />
+            </bean>
+        </property>
+        <property name="validator">
+            <bean class="org.ldaptive.SearchConnectionValidator" p:validatePeriod="%{connectionPool.validatePeriod}">
+                <property name="searchRequest">
+                    <bean class="org.ldaptive.SearchRequest">
+                        <constructor-arg value="%{connectionPool.validator.baseDn}" />
+                        <constructor-arg value="%{connectionPool.validator.filter}" />
+                    </bean>
                 </property>
             </bean>
         </property>
+        <property name="pruneStrategy">
+            <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="%{connectionPool.pruneStrategy.prunePeriod}"
+                  p:idleTime="%{connectionPool.pruneStrategy.idleTime}" />
+        </property>
+    </bean>
+    <bean class="org.ldaptive.SearchOperation">
+        <property name="request">
+            <bean class="org.ldaptive.SearchRequest" p:baseDn="%{search.baseDn}" p:returnAttributes="%{search.returnAttributes}" p:timeLimit="%{search.timeLimit}" />
+        </property>
     </bean>
-    <bean class="org.ldaptive.SearchExecutor" p:baseDn="%{search.baseDn}" p:returnAttributes="%{search.returnAttributes}" p:timeLimit="%{search.timeLimit}" />
     <bean id="cacheBuilder" class="com.google.common.cache.CacheBuilder" factory-method="from">
         <constructor-arg value="%{cache.cacheBuilderSpec}" />
     </bean>
@@ -70,7 +66,6 @@
             </map>
         </property>
     </bean>
-        <bean id="validator" class="net.shibboleth.idp.attribute.resolver.dc.ldap.impl.ConnectionFactoryValidator"
-              p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
-    
+    <bean id="validator" class="net.shibboleth.idp.attribute.resolver.dc.ldap.impl.ConnectionFactoryValidator"
+        p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
 </beans>
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
index 40497cfe3..66eaa9d4e 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
@@ -29,14 +29,14 @@ import org.apache.velocity.app.event.EventCartridge;
 import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
 import org.apache.velocity.context.Context;
 import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.SearchFilter;
-import org.ldaptive.auth.AbstractSearchDnResolver;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.auth.SearchDnResolver;
 import org.ldaptive.auth.User;
 
 /**
  * Base class for {@link Template} based search dn resolvers.
  */
-public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnResolver {
+public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver {
 
     /** Template. */
     private final Template template;
@@ -66,8 +66,8 @@ public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnR
         return template;
     }
 
-    @Override protected SearchFilter createSearchFilter(final User user) {
-        final SearchFilter filter = new SearchFilter();
+    @Override protected FilterTemplate createFilterTemplate(final User user) {
+        final FilterTemplate filter = new FilterTemplate();
         if (user != null && user.getContext() != null) {
             final VelocityContext context = (VelocityContext) user.getContext();
             final EventCartridge cartridge = new EventCartridge();
@@ -116,7 +116,7 @@ public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnR
         }
 
         /**
-         * Returns {@link SearchFilter#encodeValue} if value is a string or byte array.
+         * Returns {@link FilterTemplate#encodeValue} if value is a string or byte array.
          * 
          * @param value to encode
          *
@@ -124,9 +124,9 @@ public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnR
          */
         private Object encode(final Object value) {
             if (value instanceof String){ 
-                return SearchFilter.encodeValue((String) value);
+                return FilterTemplate.encodeValue((String) value);
             } else if (value instanceof byte[]) {
-                return SearchFilter.encodeValue((byte[]) value);
+                return FilterTemplate.encodeValue((byte[]) value);
             }
             return value;
         }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/PooledTemplateSearchDnResolver.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/PooledTemplateSearchDnResolver.java
deleted file mode 100644
index 93324ac14..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/PooledTemplateSearchDnResolver.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.authn;
-
-import java.util.Arrays;
-import org.apache.velocity.app.VelocityEngine;
-import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.Connection;
-import org.ldaptive.LdapException;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.PooledConnectionFactoryManager;
-
-/**
- * {@link net.shibboleth.utilities.java.support.velocity.Template}-based pooled search dn resolver.
- */
-public class PooledTemplateSearchDnResolver extends AbstractTemplateSearchDnResolver
-        implements PooledConnectionFactoryManager {
-
-    /** Connection factory. */
-    private PooledConnectionFactory factory;
-
-    /**
-     * Creates a new pooled template search DN resolver.
-     *
-     * @param engine velocity engine
-     * @param filter filter template
-     *
-     * @throws VelocityException if velocity is not configured properly or the filter template is invalid
-     */
-    public PooledTemplateSearchDnResolver(final VelocityEngine engine, final String filter) throws VelocityException {
-        super(engine, filter);
-    }
-
-    /**
-     * Creates a new pooled template search DN resolver.
-     *
-     * @param cf connection factory
-     * @param engine velocity engine
-     * @param filter filter template
-     *
-     * @throws VelocityException if velocity is not configured properly or the filter template is invalid
-     */
-    public PooledTemplateSearchDnResolver(final PooledConnectionFactory cf, final VelocityEngine engine,
-            final String filter) throws VelocityException {
-        super(engine, filter);
-        setConnectionFactory(cf);
-    }
-
-    @Override public PooledConnectionFactory getConnectionFactory() {
-        return factory;
-    }
-
-    @Override public void setConnectionFactory(final PooledConnectionFactory cf) {
-        factory = cf;
-    }
-
-    @Override protected Connection getConnection() throws LdapException {
-        return factory.getConnection();
-    }
-
-    @Override public String toString() {
-        return String.format(
-                "[%s@%d::factory=%s, templateName=%s, baseDn=%s, userFilter=%s, userFilterParameters=%s, "
-                        + "allowMultipleDns=%s, subtreeSearch=%s, derefAliases=%s]",
-                getClass().getName(), hashCode(), factory, getTemplate().getTemplateName(), getBaseDn(),
-                getUserFilter(), Arrays.toString(getUserFilterParameters()), getAllowMultipleDns(), getSubtreeSearch(),
-                getDerefAliases());
-    }
-}
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java
index b09c59ad6..af8910d23 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java
@@ -30,9 +30,6 @@ import org.ldaptive.LdapException;
  */
 public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver implements ConnectionFactoryManager {
 
-    /** Connection factory. */
-    private ConnectionFactory factory;
-
     /**
      * Creates a new template search DN resolver.
      *
@@ -60,25 +57,11 @@ public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver i
         setConnectionFactory(cf);
     }
 
-    @Override public ConnectionFactory getConnectionFactory() {
-        return factory;
-    }
-
-    @Override public void setConnectionFactory(final ConnectionFactory cf) {
-        factory = cf;
-    }
-
-    @Override protected Connection getConnection() throws LdapException {
-        final Connection conn = factory.getConnection();
-        conn.open();
-        return conn;
-    }
-
     @Override public String toString() {
         return String.format(
                 "[%s@%d::factory=%s, templateName=%s, baseDn=%s, userFilter=%s, userFilterParameters=%s, "
                         + "allowMultipleDns=%s, subtreeSearch=%s, derefAliases=%s]",
-                getClass().getName(), hashCode(), factory, getTemplate().getTemplateName(), getBaseDn(),
+                getClass().getName(), hashCode(), getConnectionFactory(), getTemplate().getTemplateName(), getBaseDn(),
                 getUserFilter(), Arrays.toString(getUserFilterParameters()), getAllowMultipleDns(), getSubtreeSearch(),
                 getDerefAliases());
     }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/LDAPAuthenticationFactoryBean.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/LDAPAuthenticationFactoryBean.java
index a1078a096..63c8b5f2d 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/LDAPAuthenticationFactoryBean.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/LDAPAuthenticationFactoryBean.java
@@ -20,31 +20,28 @@ package net.shibboleth.idp.authn.config;
 import java.time.Duration;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import net.shibboleth.idp.authn.PooledTemplateSearchDnResolver;
+
 import net.shibboleth.idp.authn.TemplateSearchDnResolver;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.primitive.DeprecationSupport;
-import net.shibboleth.utilities.java.support.primitive.DeprecationSupport.ObjectType;
 
 import org.apache.velocity.app.VelocityEngine;
 import org.ldaptive.ActivePassiveConnectionStrategy;
 import org.ldaptive.BindConnectionInitializer;
-import org.ldaptive.BindRequest;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.ConnectionInitializer;
 import org.ldaptive.Credential;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.LdapURL;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.PooledConnectionFactory;
 import org.ldaptive.RandomConnectionStrategy;
 import org.ldaptive.RoundRobinConnectionStrategy;
-import org.ldaptive.SearchFilter;
+import org.ldaptive.SearchConnectionValidator;
 import org.ldaptive.SearchRequest;
 import org.ldaptive.SearchScope;
+import org.ldaptive.SimpleBindRequest;
 import org.ldaptive.auth.Authenticator;
-import org.ldaptive.auth.BindAuthenticationHandler;
+import org.ldaptive.auth.SimpleBindAuthenticationHandler;
 import org.ldaptive.auth.FormatDnResolver;
-import org.ldaptive.auth.PooledBindAuthenticationHandler;
-import org.ldaptive.auth.PooledSearchEntryResolver;
 import org.ldaptive.auth.SearchEntryResolver;
 import org.ldaptive.auth.ext.ActiveDirectoryAuthenticationResponseHandler;
 import org.ldaptive.auth.ext.EDirectoryAuthenticationResponseHandler;
@@ -52,13 +49,9 @@ import org.ldaptive.auth.ext.FreeIPAAuthenticationResponseHandler;
 import org.ldaptive.auth.ext.PasswordExpirationAuthenticationResponseHandler;
 import org.ldaptive.auth.ext.PasswordPolicyAuthenticationRequestHandler;
 import org.ldaptive.auth.ext.PasswordPolicyAuthenticationResponseHandler;
-import org.ldaptive.pool.BindPassivator;
-import org.ldaptive.pool.BlockingConnectionPool;
+import org.ldaptive.pool.BindConnectionPassivator;
 import org.ldaptive.pool.IdlePruneStrategy;
-import org.ldaptive.pool.Passivator;
-import org.ldaptive.pool.PoolConfig;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.SearchValidator;
+import org.ldaptive.pool.ConnectionPassivator;
 import org.ldaptive.ssl.AllowAnyHostnameVerifier;
 import org.ldaptive.ssl.CredentialConfig;
 import org.ldaptive.ssl.SslConfig;
@@ -197,9 +190,6 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
   /** Whether to use startTLS for connections. */
   private boolean useStartTLS;
 
-  /** Whether to use LDAPS for connections. */
-  private boolean useSSL;
-
   /** Whether to use the allow-all hostname verifier. */
   private boolean disableHostnameVerification;
 
@@ -322,10 +312,6 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
     useStartTLS = b;
   }
 
-  public void setUseSSL(final boolean b) {
-    useSSL = b;
-  }
-  
   public void setDisableHostnameVerification(final boolean b) {
       disableHostnameVerification = b;
   }
@@ -515,25 +501,26 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
     }
     config.setSslConfig(createSslConfig());
     if (initializer != null) {
-      config.setConnectionInitializer(initializer);
+        config.setConnectionInitializers(initializer);
     }
     return config;
   }
 
   /**
-   * Returns a new blocking connection pool. Wires a {@link SearchValidator} by default.
+   * Returns a new pooled connection factory. Wires a {@link SearchConnectionValidator} by default.
    *
    * @param name of the connection pool
    * @param config to assign to the pool
    *
    * @return new blocking connection pool
    */
-  protected BlockingConnectionPool createConnectionPool(final String name, final ConnectionConfig config) {
-    return createConnectionPool(name, config, new SearchValidator());
+  protected PooledConnectionFactory createPooledConnectionFactory(final String name, final ConnectionConfig config) {
+      return createPooledConnectionFactory(
+          name, config, SearchConnectionValidator.builder().period(validatePeriod).build());
   }
 
   /**
-   * Returns a new blocking connection pool using the supplied search validator.
+   * Returns a new pooled connection factory using the supplied search validator.
    *
    * @param name of the connection pool
    * @param config to assign to the pool
@@ -541,9 +528,9 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
    *
    * @return new blocking connection pool
    */
-  protected BlockingConnectionPool createConnectionPool(final String name, final ConnectionConfig config,
-                                                        final SearchValidator validator) {
-    return createConnectionPool(name, config, validator, null);
+  protected PooledConnectionFactory createPooledConnectionFactory(final String name, final ConnectionConfig config,
+                                                        final SearchConnectionValidator validator) {
+      return createPooledConnectionFactory(name, config, validator, null);
   }
 
   /**
@@ -557,28 +544,28 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
    *
    * @return new blocking connection pool
    */
-  protected BlockingConnectionPool createConnectionPool(final String name, final ConnectionConfig config,
-                                                        final SearchValidator validator, final Passivator passivator) {
-    final PoolConfig poolConfig = new PoolConfig();
-    poolConfig.setMinPoolSize(minPoolSize);
-    poolConfig.setMaxPoolSize(maxPoolSize);
-    poolConfig.setValidateOnCheckOut(validateOnCheckout);
-    poolConfig.setValidatePeriodically(validatePeriodically);
-    poolConfig.setValidatePeriod(validatePeriod);
-    final BlockingConnectionPool pool = new BlockingConnectionPool();
-    pool.setName(name);
-    pool.setBlockWaitTime(blockWaitTime);
-    pool.setPoolConfig(poolConfig);
-    pool.setPruneStrategy(new IdlePruneStrategy(prunePeriod, idleTime));
-    pool.setValidator(validator);
-    pool.setPassivator(passivator);
-    pool.setFailFastInitialize(false);
-    pool.setConnectionFactory(new DefaultConnectionFactory(config));
-    pool.initialize();
-    return pool;
-  }
-
-  protected SearchValidator createSearchValidator(final String baseDn, final String filter) {
+  protected PooledConnectionFactory createPooledConnectionFactory(
+        final String name, final ConnectionConfig config,
+        final SearchConnectionValidator validator, final ConnectionPassivator passivator) {
+      final PooledConnectionFactory factory = new PooledConnectionFactory();
+      factory.setConnectionConfig(config);
+      factory.setMinPoolSize(minPoolSize);
+      factory.setMaxPoolSize(maxPoolSize);
+      factory.setValidateOnCheckOut(validateOnCheckout);
+      factory.setValidatePeriodically(validatePeriodically);
+      factory.setName(name);
+      factory.setBlockWaitTime(blockWaitTime);
+      factory.setPruneStrategy(new IdlePruneStrategy(prunePeriod, idleTime));
+      factory.setValidator(validator);
+      if (passivator != null) {
+          factory.setPassivator(passivator);
+      }
+      factory.setFailFastInitialize(false);
+      factory.initialize();
+      return factory;
+  }
+
+  protected SearchConnectionValidator createSearchValidator(final String baseDn, final String filter) {
     final SearchRequest searchRequest = new SearchRequest();
     searchRequest.setReturnAttributes("1.1");
     searchRequest.setSearchScope(SearchScope.OBJECT);
@@ -588,22 +575,22 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
     } else {
       searchRequest.setBaseDn("");
     }
-    final SearchFilter searchFilter = new SearchFilter();
+    final FilterTemplate searchFilter = new FilterTemplate();
     if (filter != null) {
       searchFilter.setFilter(filter);
     } else {
       searchFilter.setFilter("(objectClass=*)");
     }
-    searchRequest.setSearchFilter(searchFilter);
-    return new SearchValidator(searchRequest);
+    searchRequest.setFilter(searchFilter);
+    return SearchConnectionValidator.builder().request(searchRequest).period(validatePeriod).build();
   }
 
-  protected Passivator createPoolPassivator(final PassivatorType type) {
+  protected ConnectionPassivator createPoolPassivator(final PassivatorType type) {
     switch(type) {
       case BIND:
-        return new BindPassivator(new BindRequest(bindDn, new Credential(bindDnCredential)));
+        return new BindConnectionPassivator(new SimpleBindRequest(bindDn, new Credential(bindDnCredential)));
       case ANONYMOUS_BIND:
-        return new BindPassivator();
+        return new BindConnectionPassivator();
       case NONE:
       default:
         return null;
@@ -613,30 +600,18 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
 // Checkstyle: CyclomaticComplexity|MethodLength OFF
   @Override
   protected Authenticator createInstance() throws Exception {
-    // check for deprecated useSSL property
-    if (useSSL) {
-      DeprecationSupport.warn(ObjectType.PROPERTY, "useSSL", "LDAP authentication",
-              "use of ldaps:// scheme in connection URL");
-      final LdapURL url = new LdapURL(ldapUrl);
-      for (final String s : url.getHostnamesWithSchemeAndPort()) {
-        if (!s.startsWith("ldaps://")) {
-          throw new IllegalArgumentException("useSSL property specified but URL scheme is not ldaps:// for " + s);
-        }
-      }
-    }
     final Authenticator authenticator = new Authenticator();
     if (disablePooling) {
       authenticator.setAuthenticationHandler(
-        new BindAuthenticationHandler(new DefaultConnectionFactory(createConnectionConfig())));
+        new SimpleBindAuthenticationHandler(new DefaultConnectionFactory(createConnectionConfig())));
     } else {
       authenticator.setAuthenticationHandler(
-        new PooledBindAuthenticationHandler(
-          new PooledConnectionFactory(
-            createConnectionPool(
+        new SimpleBindAuthenticationHandler(
+          createPooledConnectionFactory(
               "bind-pool",
               createConnectionConfig(),
               createSearchValidator(validateDn, validateFilter),
-              createPoolPassivator(bindPoolPassivatorType)))));
+              createPoolPassivator(bindPoolPassivatorType))));
     }
     switch(authenticatorType) {
     case BIND_SEARCH:
@@ -650,16 +625,15 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
             createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential)))));
         authenticator.setDnResolver(bindSearchDnResolver);
       } else {
-        final PooledTemplateSearchDnResolver bindSearchDnResolver =
-          new PooledTemplateSearchDnResolver(velocityEngine, userFilter);
+        final TemplateSearchDnResolver bindSearchDnResolver =
+          new TemplateSearchDnResolver(velocityEngine, userFilter);
         bindSearchDnResolver.setBaseDn(baseDn);
         bindSearchDnResolver.setSubtreeSearch(subtreeSearch);
         bindSearchDnResolver.setConnectionFactory(
-          new PooledConnectionFactory(
-            createConnectionPool(
+          createPooledConnectionFactory(
               "dn-search-pool",
               createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential))),
-              createSearchValidator(validateDn, validateFilter))));
+              createSearchValidator(validateDn, validateFilter)));
         authenticator.setDnResolver(bindSearchDnResolver);
       }
       authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
@@ -671,7 +645,7 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
     case AD:
       authenticator.setDnResolver(new FormatDnResolver(dnFormat));
       authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
-      authenticator.setAuthenticationResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler());
+      authenticator.setResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler());
       break;
     case ANON_SEARCH:
       if (disablePooling) {
@@ -682,16 +656,15 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
         anonSearchDnResolver.setConnectionFactory(new DefaultConnectionFactory(createConnectionConfig()));
         authenticator.setDnResolver(anonSearchDnResolver);
       } else {
-        final PooledTemplateSearchDnResolver anonSearchDnResolver =
-          new PooledTemplateSearchDnResolver(velocityEngine, userFilter);
+        final TemplateSearchDnResolver anonSearchDnResolver =
+          new TemplateSearchDnResolver(velocityEngine, userFilter);
         anonSearchDnResolver.setBaseDn(baseDn);
         anonSearchDnResolver.setSubtreeSearch(subtreeSearch);
         anonSearchDnResolver.setConnectionFactory(
-          new PooledConnectionFactory(
-            createConnectionPool(
+          createPooledConnectionFactory(
               "dn-search-pool",
               createConnectionConfig(),
-              createSearchValidator(validateDn, validateFilter))));
+              createSearchValidator(validateDn, validateFilter)));
         authenticator.setDnResolver(anonSearchDnResolver);
       }
       authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
@@ -708,28 +681,27 @@ public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authentic
             createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential)))));
         authenticator.setEntryResolver(searchEntryResolver);
       } else {
-        final PooledSearchEntryResolver searchEntryResolver = new PooledSearchEntryResolver();
+        final SearchEntryResolver searchEntryResolver = new SearchEntryResolver();
         searchEntryResolver.setConnectionFactory(
-          new PooledConnectionFactory(
-            createConnectionPool(
+          createPooledConnectionFactory(
               "entry-search-pool",
               createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential))),
-              createSearchValidator(validateDn, validateFilter))));
+              createSearchValidator(validateDn, validateFilter)));
         authenticator.setEntryResolver(searchEntryResolver);
       }
     }
 
     if (usePasswordPolicy) {
-      authenticator.setAuthenticationRequestHandlers(new PasswordPolicyAuthenticationRequestHandler());
-      authenticator.setAuthenticationResponseHandlers(new PasswordPolicyAuthenticationResponseHandler());
+      authenticator.setRequestHandlers(new PasswordPolicyAuthenticationRequestHandler());
+      authenticator.setResponseHandlers(new PasswordPolicyAuthenticationResponseHandler());
     } else if (usePasswordExpiration) {
-      authenticator.setAuthenticationResponseHandlers(new PasswordExpirationAuthenticationResponseHandler());
+      authenticator.setResponseHandlers(new PasswordExpirationAuthenticationResponseHandler());
     } else if (isActiveDirectory) {
-      authenticator.setAuthenticationResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler());
+      authenticator.setResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler());
     } else if (isEDirectory) {
-      authenticator.setAuthenticationResponseHandlers(new EDirectoryAuthenticationResponseHandler());
+      authenticator.setResponseHandlers(new EDirectoryAuthenticationResponseHandler());
     } else if (isFreeIPA) {
-      authenticator.setAuthenticationResponseHandlers(new FreeIPAAuthenticationResponseHandler());
+      authenticator.setResponseHandlers(new FreeIPAAuthenticationResponseHandler());
     }
     return authenticator;
   }
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
index 51f6c91aa..95b9babb7 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
@@ -35,6 +35,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 import org.apache.velocity.VelocityContext;
+import org.ldaptive.ConnectException;
 import org.ldaptive.Credential;
 import org.ldaptive.LdapException;
 import org.ldaptive.ResultCode;
@@ -140,71 +141,75 @@ public class LDAPCredentialValidator extends AbstractUsernamePasswordCredentialV
             @Nullable final ErrorHandler errorHandler) throws Exception {
         
         final String username = usernamePasswordContext.getTransformedUsername();
-        
-        String eventToSignal = AuthnEventIds.INVALID_CREDENTIALS;
-        
-        // The error handling is squonky. We log at info to generically record the failure.
-        // Known conditions are not explicitly logged but are wrapped with an exception and
-        // reported out to the caller. Last ditch, an exception is logged on warn and then
-        // reported out.
-        
+
+        log.debug("{} Attempting to authenticate user {}", getLogPrefix(), username);
+        final VelocityContext context = new VelocityContext();
+        context.put("usernamePasswordContext", usernamePasswordContext);
+        final char[] password = passwordLookupStrategy != null ?
+                passwordLookupStrategy.apply(profileRequestContext) :
+                usernamePasswordContext.getPassword().toCharArray();
+        final AuthenticationRequest request = new AuthenticationRequest(
+                new User(username, context), new Credential(password), returnAttributes);
+        final AuthenticationResponse response;
         try {
-            log.debug("{} Attempting to authenticate user {}", getLogPrefix(), username);
-            final VelocityContext context = new VelocityContext();
-            context.put("usernamePasswordContext", usernamePasswordContext);
-            final char[] password = passwordLookupStrategy != null ?
-                    passwordLookupStrategy.apply(profileRequestContext) :
-                        usernamePasswordContext.getPassword().toCharArray();
-            final AuthenticationRequest request = new AuthenticationRequest(
-                    new User(username, context), new Credential(password), returnAttributes);
-            final AuthenticationResponse response = authenticator.authenticate(request);
-            log.trace("{} Authentication response {}", getLogPrefix(), response);
-            if (response.getResult()) {
-                log.info("{} Login by '{}' succeeded", getLogPrefix(), username);
-                authenticationContext.getSubcontext(
-                        LDAPResponseContext.class, true).setAuthenticationResponse(response);
-                if (response.getAccountState() != null) {
-                    final AccountState.Error error = response.getAccountState().getError();
-                    if (warningHandler != null) {
-                        warningHandler.handleWarning(
-                                profileRequestContext,
-                                authenticationContext,
-                                String.format("%s:%s:%s", error != null ? error : "ACCOUNT_WARNING",
-                                        response.getResultCode(), response.getMessage()),
-                                AuthnEventIds.ACCOUNT_WARNING);
-                    }
-                }
-                return populateSubject(usernamePasswordContext, response);
-            }
-            
-            authenticationContext.getSubcontext(
-                    LDAPResponseContext.class, true).setAuthenticationResponse(response);
-            if (AuthenticationResultCode.DN_RESOLUTION_FAILURE == response.getAuthenticationResultCode()
-                    || AuthenticationResultCode.INVALID_CREDENTIAL == response.getAuthenticationResultCode()) {
-                throw new LdapException(
-                        String.format("%s:%s", response.getAuthenticationResultCode(), response.getMessage()));
-            } else if (response.getAccountState() != null) {
-                final AccountState state = response.getAccountState();
-                eventToSignal = AuthnEventIds.ACCOUNT_ERROR;
-                throw new LdapException(
-                        String.format("%s:%s:%s", state.getError(), response.getResultCode(), response.getMessage())
-                        );
-            } else if (response.getResultCode() == ResultCode.INVALID_CREDENTIALS) {
-                throw new LdapException(String.format("%s:%s", response.getResultCode(), response.getMessage()));
-            } else {
-                eventToSignal = AuthnEventIds.AUTHN_EXCEPTION;
-                final LdapException e =
-                        new LdapException(response.getMessage(), response.getResultCode(), response.getMatchedDn(),
-                        response.getControls(), response.getReferralURLs(), response.getMessageId());
-                throw e;
-            }
+            // authenticator should only throw for communication errors
+            response = authenticator.authenticate(request);
         } catch (final LdapException e) {
-            log.info("{} Login by '{}' failed", getLogPrefix(), username, e);
+            log.error("{} Error attempting LDAP authentication for '{}'", getLogPrefix(), username, e);
             if (errorHandler != null) {
-                errorHandler.handleError(profileRequestContext, authenticationContext, e, eventToSignal);
+                errorHandler.handleError(
+                    profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
             }
             throw e;
         }
+
+        log.trace("{} Authentication response {}", getLogPrefix(), response);
+        if (response.isSuccess()) {
+            log.info("{} Login by '{}' succeeded", getLogPrefix(), username);
+            authenticationContext.getSubcontext(
+                    LDAPResponseContext.class, true).setAuthenticationResponse(response);
+            if (response.getAccountState() != null) {
+                final AccountState.Error error = response.getAccountState().getError();
+                if (warningHandler != null) {
+                    warningHandler.handleWarning(
+                        profileRequestContext,
+                        authenticationContext,
+                        String.format("%s:%s:%s", error != null ? error : "ACCOUNT_WARNING",
+                            response.getResultCode(), response.getDiagnosticMessage()),
+                        AuthnEventIds.ACCOUNT_WARNING);
+                }
+            }
+            return populateSubject(usernamePasswordContext, response);
+        }
+
+        String eventToSignal;
+        LdapException authException;
+        authenticationContext.getSubcontext(
+            LDAPResponseContext.class, true).setAuthenticationResponse(response);
+        if (AuthenticationResultCode.DN_RESOLUTION_FAILURE == response.getAuthenticationResultCode()
+                || AuthenticationResultCode.INVALID_CREDENTIAL == response.getAuthenticationResultCode()) {
+            eventToSignal = AuthnEventIds.INVALID_CREDENTIALS;
+            authException = new LdapException(
+                String.format("%s:%s", response.getAuthenticationResultCode(), response.getDiagnosticMessage()));
+        } else if (response.getAccountState() != null) {
+            final AccountState state = response.getAccountState();
+            eventToSignal = AuthnEventIds.ACCOUNT_ERROR;
+            authException = new LdapException(
+                String.format("%s:%s:%s", state.getError(), response.getResultCode(), response.getDiagnosticMessage()));
+        } else if (response.getResultCode() == ResultCode.INVALID_CREDENTIALS) {
+            eventToSignal = AuthnEventIds.INVALID_CREDENTIALS;
+            authException  = new LdapException(
+                String.format("%s:%s", response.getResultCode(), response.getDiagnosticMessage()));
+        } else {
+            eventToSignal = AuthnEventIds.AUTHN_EXCEPTION;
+            authException = new LdapException(response);
+        }
+
+        log.info("{} Login by '{}' failed", getLogPrefix(), username, authException);
+        if (errorHandler != null) {
+            errorHandler.handleError(profileRequestContext, authenticationContext, authException, eventToSignal);
+        }
+        throw authException;
     }
 // Checkstyle: CyclomaticComplexity ON
 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
index a81ea1d65..c97b159d0 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
@@ -135,9 +135,9 @@ public class LDAPPrincipalSerializer extends AbstractPrincipalSerializer<String>
                             } else {
                                 final LdapAttribute attr = new LdapAttribute(e.getKey());
                                 for (final JsonValue v : (JsonArray) e.getValue()) {
-                                    attr.addStringValue(((JsonString) v).getString());
+                                    attr.addStringValues(((JsonString) v).getString());
                                 }
-                                entry.addAttribute(attr);
+                                entry.addAttributes(attr);
                             }
                         }
                     }
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
index db1071f1b..7b174da23 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
@@ -58,7 +58,6 @@ import net.shibboleth.utilities.java.support.test.resource.TestResourceConverter
 
 import org.ldaptive.LdapAttribute;
 import org.ldaptive.LdapEntry;
-import org.ldaptive.SortBehavior;
 import org.ldaptive.jaas.LdapPrincipal;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.testing.RequestContextBuilder;
@@ -339,17 +338,17 @@ public class DefaultAuthenticationResultSerializerTest {
         flowDescriptor.initialize();
         
         final AuthenticationResult result = createResult(flowDescriptor, new Subject());
-        final LdapEntry entry = new LdapEntry(SortBehavior.SORTED);
+        final LdapEntry entry = new LdapEntry();
         entry.setDn("uid=1234,ou=people,dc=shibboleth,dc=net");
-        final LdapAttribute givenName = new LdapAttribute(SortBehavior.SORTED);
+        final LdapAttribute givenName = new LdapAttribute();
         givenName.setName("givenName");
-        givenName.addStringValue("Bob", "Robert");
-        entry.addAttribute(
+        givenName.addStringValues("Bob", "Robert");
+        entry.addAttributes(
                 new LdapAttribute("cn", "Bob Cobb"),
                 givenName,
                 new LdapAttribute("sn", "Cobb"),
                 new LdapAttribute("mail", "bob at shibboleth.net"));
-        result.getSubject().getPrincipals().add(new LdapPrincipal("bob", entry));
+        result.getSubject().getPrincipals().add(new LdapPrincipal("bob", LdapEntry.sort(entry)));
 
         final ProfileRequestContext prc = getProfileRequestContext(Collections.singletonList(flowDescriptor));
         assertTrue(result.getReuseCondition().test(prc));
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
index 3aeb6ed72..d134c37b8 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
@@ -46,7 +46,7 @@ import org.ldaptive.auth.AuthenticationResponse;
 import org.ldaptive.auth.AuthenticationResponseHandler;
 import org.ldaptive.auth.AuthenticationResultCode;
 import org.ldaptive.auth.Authenticator;
-import org.ldaptive.auth.BindAuthenticationHandler;
+import org.ldaptive.auth.SimpleBindAuthenticationHandler;
 import org.ldaptive.auth.SearchDnResolver;
 import org.ldaptive.auth.ext.PasswordPolicyAccountState;
 import org.ldaptive.control.PasswordPolicyControl;
@@ -78,7 +78,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
     private TemplateSearchDnResolver dnResolver;
 
-    private BindAuthenticationHandler authHandler;
+    private SimpleBindAuthenticationHandler authHandler;
 
     private Authenticator authenticator;
 
@@ -106,7 +106,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
                 VelocityEngine.newVelocityEngine(), "(uid=$usernamePasswordContext.username)");
         dnResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
 
-        authHandler = new BindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
+        authHandler = new SimpleBindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
 
         authenticator = new Authenticator(dnResolver, authHandler);
     }
@@ -170,6 +170,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
         final Event event = action.execute(src);
         Assert.assertNull(ac.getAuthenticationResult());
+        Assert.assertNull(ac.getSubcontext(LDAPResponseContext.class));
         Assert.assertNull(ac.getSubcontext(AuthenticationErrorContext.class));
         ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
     }
@@ -230,7 +231,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
         
         validator.setAuthenticator(new Authenticator(dnResolver,
-                new BindAuthenticationHandler(new DefaultConnectionFactory("ldap://unknown:389"))));
+                new SimpleBindAuthenticationHandler(new DefaultConnectionFactory("ldap://unknown:389"))));
         validator.initialize();
         
         action.initialize();
@@ -240,15 +241,10 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         final Event event = action.execute(src);
 
         Assert.assertNull(ac.getAuthenticationResult());
-        LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        Assert.assertNotNull(lrc.getAuthenticationResponse());
-        Assert.assertEquals(lrc.getAuthenticationResponse().getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_FAILURE);
-
+        Assert.assertNull(ac.getSubcontext(LDAPResponseContext.class));
         AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
         Assert.assertNotNull(aec);
         ActionTestingSupport.assertEvent(event, AuthnEventIds.AUTHN_EXCEPTION);
-        System.err.println("EXCEPTIONS:: " + aec.getExceptions());
         Assert.assertEquals(aec.getExceptions().size(), 1);
         Assert.assertEquals(aec.getClassifiedErrors().size(), 0);
     }
@@ -297,6 +293,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
         final Event event = action.execute(src);
         Assert.assertNull(ac.getAuthenticationResult());
+        Assert.assertNull(ac.getSubcontext(LDAPResponseContext.class));
         Assert.assertNull(ac.getSubcontext(AuthenticationErrorContext.class));
         ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
     }
@@ -337,7 +334,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
         Authenticator errorAuthenticator = new Authenticator(dnResolver, authHandler);
-        errorAuthenticator.setAuthenticationResponseHandlers(new AuthenticationResponseHandler() {
+        errorAuthenticator.setResponseHandlers(new AuthenticationResponseHandler() {
             public void handle(AuthenticationResponse response) throws LdapException {
                 response.setAccountState(new PasswordPolicyAccountState(PasswordPolicyControl.Error.PASSWORD_EXPIRED));
             }
@@ -372,7 +369,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
         Authenticator errorAuthenticator = new Authenticator(dnResolver, authHandler);
-        errorAuthenticator.setAuthenticationResponseHandlers(new AuthenticationResponseHandler() {
+        errorAuthenticator.setResponseHandlers(new AuthenticationResponseHandler() {
             public void handle(AuthenticationResponse response) throws LdapException {
                 response.setAccountState(
                         new PasswordPolicyAccountState(PasswordPolicyControl.Error.CHANGE_AFTER_RESET));
@@ -418,7 +415,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
         Authenticator warningAuthenticator = new Authenticator(dnResolver, authHandler);
-        warningAuthenticator.setAuthenticationResponseHandlers(new AuthenticationResponseHandler() {
+        warningAuthenticator.setResponseHandlers(new AuthenticationResponseHandler() {
             public void handle(AuthenticationResponse response) throws LdapException {
                 response.setAccountState(
                         new AccountState(new AccountState.DefaultWarning(ZonedDateTime.now(), 10)));
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java
index d4103cddf..6ee2380e1 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java
@@ -39,7 +39,7 @@ import net.shibboleth.utilities.java.support.velocity.VelocityEngine;
 import org.ldaptive.DefaultConnectionFactory;
 import org.ldaptive.auth.AuthenticationResultCode;
 import org.ldaptive.auth.Authenticator;
-import org.ldaptive.auth.BindAuthenticationHandler;
+import org.ldaptive.auth.SimpleBindAuthenticationHandler;
 import org.ldaptive.jaas.LdapPrincipal;
 import org.springframework.core.io.FileSystemResource;
 import org.springframework.mock.web.MockHttpServletRequest;
@@ -66,7 +66,7 @@ public class ValidateCredentialsTest extends BaseAuthenticationContextTest {
 
     private TemplateSearchDnResolver dnResolver;
 
-    private BindAuthenticationHandler authHandler;
+    private SimpleBindAuthenticationHandler authHandler;
 
     private Authenticator authenticator;
 
@@ -94,7 +94,7 @@ public class ValidateCredentialsTest extends BaseAuthenticationContextTest {
                 VelocityEngine.newVelocityEngine(), "(uid=$usernamePasswordContext.username)");
         dnResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
 
-        authHandler = new BindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
+        authHandler = new SimpleBindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
 
         authenticator = new Authenticator(dnResolver, authHandler);
     }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list