[java-identity-provider] branch main updated: Null cleanup.

Scott Cantor cantor.2 at osu.edu
Mon Jan 23 18:13:23 UTC 2023


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

scantor pushed a commit to branch main
in repository java-identity-provider.

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

The following commit(s) were added to refs/heads/main by this push:
     new c72fde47e Null cleanup.
c72fde47e is described below

commit c72fde47e9ee8c77910cc80cfe81fa47aa1bedce
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jan 23 13:13:20 2023 -0500

    Null cleanup.
---
 .../idp/authn/AbstractCredentialValidator.java     |   68 +-
 .../idp/authn/AbstractExtractionAction.java        |   23 +-
 .../authn/AbstractTemplateSearchDnResolver.java    |   22 +-
 ...bstractUsernamePasswordCredentialValidator.java |   78 +-
 .../idp/authn/AbstractValidationAction.java        |  136 +-
 .../idp/authn/AuthenticationFlowDescriptor.java    |   24 +-
 .../shibboleth/idp/authn/AuthenticationResult.java |    8 +-
 .../idp/authn/ExternalAuthentication.java          |    3 +-
 .../authn/MultiFactorAuthenticationTransition.java |    6 +-
 .../SubjectCanonicalizationFlowDescriptor.java     |    4 +-
 .../idp/authn/TemplateSearchDnResolver.java        |   16 +-
 .../config/LDAPAuthenticationFactoryBean.java      | 1406 +++++++++++---------
 .../navigate/ForceAuthnProfileConfigPredicate.java |    8 +-
 .../idp/authn/context/AuthenticationContext.java   |   17 +-
 .../context/AuthenticationWarningContext.java      |    3 +-
 .../idp/authn/context/LDAPResponseContext.java     |   14 +-
 .../context/MultiFactorAuthenticationContext.java  |    1 +
 .../authn/context/PreferredPrincipalContext.java   |    6 +-
 .../authn/context/RequestedPrincipalContext.java   |   21 +-
 .../navigate/PreviousResultLookupFunction.java     |    8 +-
 .../duo/context/DuoAuthenticationContext.java      |    2 +-
 .../principal/GenericPrincipalSerializer.java      |    2 +-
 .../PrincipalEvalPredicateFactoryRegistry.java     |   25 +-
 .../authn/principal/PrincipalServiceManager.java   |   11 +-
 .../principal/ProxyAuthenticationPrincipal.java    |   13 +-
 .../authn/principal/SealedPrincipalSerializer.java |   20 +-
 .../authn/principal/SimplePrincipalSerializer.java |    6 -
 .../idp/authn/AuthenticationResultTest.java        |   17 -
 .../idp/authn/principal/UsernamePrincipalTest.java |    7 -
 29 files changed, 1043 insertions(+), 932 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractCredentialValidator.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractCredentialValidator.java
index 86f74a2e1..35d12ae50 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractCredentialValidator.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractCredentialValidator.java
@@ -19,7 +19,6 @@ package net.shibboleth.idp.authn;
 
 import java.security.Principal;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -29,9 +28,6 @@ import javax.security.auth.Subject;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Predicates;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
@@ -42,8 +38,11 @@ import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.AbstractIdentifiedInitializableComponent;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * An abstract {@link CredentialValidator} that handles some common behavior.
@@ -67,12 +66,12 @@ public abstract class AbstractCredentialValidator extends AbstractIdentifiedInit
     
     /** Constructor. */
     public AbstractCredentialValidator() {
-        activationCondition = Predicates.alwaysTrue();
+        activationCondition = PredicateSupport.alwaysTrue();
     }
     
     /** {@inheritDoc} */
     @Override
-    public synchronized void setId(final String id) {
+    public synchronized void setId(@Nonnull final String id) {
         super.setId(id);
     }
     
@@ -91,7 +90,7 @@ public abstract class AbstractCredentialValidator extends AbstractIdentifiedInit
     @Override
     @Nonnull @NonnullElements @Unmodifiable @NotLive public <T extends Principal> Set<T> getSupportedPrincipals(
             @Nonnull final Class<T> c) {
-        return customPrincipals != null ? customPrincipals.getPrincipals(c) : Collections.emptySet();
+        return customPrincipals != null ? customPrincipals.getPrincipals(c) : CollectionSupport.emptySet();
     }
     
     /**
@@ -160,8 +159,9 @@ public abstract class AbstractCredentialValidator extends AbstractIdentifiedInit
      * @return the decorated subject
      */
     @Nonnull protected Subject populateSubject(@Nonnull final Subject subject) {
-        if (customPrincipals != null) {
-            subject.getPrincipals().addAll(customPrincipals.getPrincipals());
+        final Subject localCopy = customPrincipals;
+        if (localCopy != null) {
+            subject.getPrincipals().addAll(localCopy.getPrincipals());
         }
         return subject;
     }
@@ -174,7 +174,9 @@ public abstract class AbstractCredentialValidator extends AbstractIdentifiedInit
     @Nonnull @NotEmpty protected String getLogPrefix() {
         if (logPrefix == null) {
             logPrefix = "Credential Validator " + (getId() != null ? getId() : "(unknown)") + ":";
+            return logPrefix;
         }
+        assert logPrefix != null;
         return logPrefix;
     }
     
@@ -191,31 +193,35 @@ public abstract class AbstractCredentialValidator extends AbstractIdentifiedInit
     protected boolean isAcceptable(@Nullable final RequestedPrincipalContext requestedPrincipalCtx,
             @Nullable final Subject subject, @Nonnull @NotEmpty final String configName) {
         
-        if (subject != null && requestedPrincipalCtx != null && requestedPrincipalCtx.getOperator() != null) {
-            log.debug("{} Request contains principal requirements, checking validator '{}' for compatibility",
-                    getLogPrefix(), configName);
-            for (final Principal p : requestedPrincipalCtx.getRequestedPrincipals()) {
-                final PrincipalEvalPredicateFactory factory =
-                        requestedPrincipalCtx.getPrincipalEvalPredicateFactoryRegistry().lookup(
-                                p.getClass(), requestedPrincipalCtx.getOperator());
-                if (factory != null) {
-                    final PrincipalEvalPredicate predicate = factory.getPredicate(p);
-                    final PrincipalSupportingComponent wrapper = new PrincipalSupportingComponent() {
-                        public <T extends Principal> Set<T> getSupportedPrincipals(final Class<T> c) {
-                            return subject.getPrincipals(c);
+        if (subject != null && requestedPrincipalCtx != null) {
+            final String operator = requestedPrincipalCtx.getOperator();
+            if (operator != null) {
+                log.debug("{} Request contains principal requirements, checking validator '{}' for compatibility",
+                        getLogPrefix(), configName);
+                for (final Principal p : requestedPrincipalCtx.getRequestedPrincipals()) {
+                    final PrincipalEvalPredicateFactory factory =
+                            requestedPrincipalCtx.getPrincipalEvalPredicateFactoryRegistry().lookup(
+                                    p.getClass(), operator);
+                    if (factory != null) {
+                        final PrincipalEvalPredicate predicate = factory.getPredicate(p);
+                        final PrincipalSupportingComponent wrapper = new PrincipalSupportingComponent() {
+                            @Nonnull
+                            public <T extends Principal> Set<T> getSupportedPrincipals(@Nonnull final Class<T> c) {
+                                return subject.getPrincipals(c);
+                            }
+                        };
+                        if (predicate.test(wrapper)) {
+                            log.debug("{} Validator '{}' compatible with principal type '{}' and operator '{}'",
+                                    getLogPrefix(), configName, p.getClass(), requestedPrincipalCtx.getOperator());
+                            requestedPrincipalCtx.setMatchingPrincipal(predicate.getMatchingPrincipal());
+                            return true;
                         }
-                    };
-                    if (predicate.test(wrapper)) {
-                        log.debug("{} Validator '{}' compatible with principal type '{}' and operator '{}'",
+                        log.debug("{} Validator '{}' not compatible with principal type '{}' and operator '{}'",
                                 getLogPrefix(), configName, p.getClass(), requestedPrincipalCtx.getOperator());
-                        requestedPrincipalCtx.setMatchingPrincipal(predicate.getMatchingPrincipal());
-                        return true;
+                    } else {
+                        log.debug("{} No comparison logic registered for principal type '{}' and operator '{}'",
+                                getLogPrefix(), p.getClass(), requestedPrincipalCtx.getOperator());
                     }
-                    log.debug("{} Validator '{}' not compatible with principal type '{}' and operator '{}'",
-                            getLogPrefix(), configName, p.getClass(), requestedPrincipalCtx.getOperator());
-                } else {
-                    log.debug("{} No comparison logic registered for principal type '{}' and operator '{}'",
-                            getLogPrefix(), p.getClass(), requestedPrincipalCtx.getOperator());
                 }
             }
             
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractExtractionAction.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractExtractionAction.java
index bd19eed80..744523005 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractExtractionAction.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractExtractionAction.java
@@ -19,7 +19,6 @@ package net.shibboleth.idp.authn;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -28,12 +27,13 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
@@ -61,7 +61,7 @@ public abstract class AbstractExtractionAction extends AbstractAuthenticationAct
     
     /** Constructor. */
     public AbstractExtractionAction() {
-        transforms = Collections.emptyList();
+        transforms = CollectionSupport.emptyList();
         
         uppercase = false;
         lowercase = false;
@@ -83,7 +83,7 @@ public abstract class AbstractExtractionAction extends AbstractAuthenticationAct
                         StringSupport.trimOrNull(p.getSecond()), "Replacement expression cannot be null")));
             }
         } else {
-            transforms = Collections.emptyList();
+            transforms = CollectionSupport.emptyList();
         }
     }
 
@@ -145,12 +145,15 @@ public abstract class AbstractExtractionAction extends AbstractAuthenticationAct
             return s;
         }
         
-        for (final Pair<Pattern,String> p : transforms) {            
-            final Matcher m = p.getFirst().matcher(s);
-            log.debug("{} Applying replacement expression '{}' against input '{}'", getLogPrefix(),
-                    p.getFirst().pattern(), s);
-            s = m.replaceAll(p.getSecond());
-            log.debug("{} Result of replacement is '{}'", getLogPrefix(), s);
+        for (final Pair<Pattern,String> p : transforms) {
+            final Pattern pattern = p.getFirst();
+            if (pattern != null) {
+                final Matcher m = pattern.matcher(s);
+                log.debug("{} Applying replacement expression '{}' against input '{}'", getLogPrefix(),
+                        pattern.pattern(), s);
+                s = m.replaceAll(p.getSecond());
+                log.debug("{} Result of replacement is '{}'", getLogPrefix(), s);
+            }
         }
         
         return s;
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 7b9336e4a..1bcb4a91d 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
@@ -21,6 +21,9 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.app.event.EventCartridge;
@@ -31,6 +34,7 @@ import org.ldaptive.FilterTemplate;
 import org.ldaptive.auth.SearchDnResolver;
 import org.ldaptive.auth.User;
 
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.velocity.Template;
 
 /**
@@ -39,7 +43,7 @@ import net.shibboleth.shared.velocity.Template;
 public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver {
 
     /** Template. */
-    private final Template template;
+    @Nonnull private final Template template;
 
     /** Event handler used for escaping. */
     private ReferenceInsertionEventHandler eventHandler = new EscapingReferenceInsertionEventHandler();
@@ -52,7 +56,8 @@ public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver
      *
      * @throws VelocityException if velocity is not configured properly or the filter template is invalid
      */
-    public AbstractTemplateSearchDnResolver(final VelocityEngine engine, final String filter) throws VelocityException {
+    public AbstractTemplateSearchDnResolver(@Nonnull final VelocityEngine engine,
+            @Nonnull @NotEmpty final String filter) throws VelocityException {
         template = Template.fromTemplate(engine, filter);
         setUserFilter(filter);
     }
@@ -62,11 +67,12 @@ public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver
      *
      * @return template
      */
-    public Template getTemplate() {
+    @Nonnull public Template getTemplate() {
         return template;
     }
 
-    @Override protected FilterTemplate createFilterTemplate(final User user) {
+    @Override
+    @Nonnull protected FilterTemplate createFilterTemplate(final User user) {
         final FilterTemplate filter = new FilterTemplate();
         if (user != null && user.getContext() != null) {
             final VelocityContext context = (VelocityContext) user.getContext();
@@ -95,7 +101,8 @@ public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver
     /** Escapes LDAP attribute values added to the template context. */
     protected static class EscapingReferenceInsertionEventHandler implements ReferenceInsertionEventHandler {
 
-        @Override public Object referenceInsert(final Context context, final String reference, final Object value) {
+        @Override
+        @Nullable public Object referenceInsert(final Context context, final String reference, final Object value) {
             if (value == null) {
                 return null;
             } else if (value instanceof Object[]) {
@@ -122,7 +129,7 @@ public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver
          *
          * @return encoded value
          */
-        private Object encode(final Object value) {
+        @Nullable private Object encode(@Nullable final Object value) {
             if (value instanceof String){ 
                 return FilterTemplate.encodeValue((String) value);
             } else if (value instanceof byte[]) {
@@ -131,4 +138,5 @@ public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver
             return value;
         }
     }
-}
+
+}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordCredentialValidator.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordCredentialValidator.java
index 4d6671548..d16ff8eb3 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordCredentialValidator.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordCredentialValidator.java
@@ -19,7 +19,6 @@ package net.shibboleth.idp.authn;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
 import java.util.regex.Matcher;
@@ -33,7 +32,6 @@ import javax.security.auth.login.LoginException;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.context.UsernamePasswordContext;
@@ -42,8 +40,10 @@ import net.shibboleth.idp.authn.principal.UsernamePrincipal;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
@@ -67,9 +67,6 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
     /** Whether to save the password in the Java Subject's private credentials. */
     private boolean savePasswordToCredentialSet;
     
-    /** Whether to remove the {@link UsernamePasswordContext} after successful validation. */
-    private boolean removeContextAfterValidation;
-    
     /** A regular expression to apply for acceptance testing. */
     @Nullable private Pattern matchExpression;
     
@@ -89,7 +86,7 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
     public AbstractUsernamePasswordCredentialValidator() {
         usernamePasswordContextLookupStrategy = new ChildContextLookup<>(UsernamePasswordContext.class);
 
-        transforms = Collections.emptyList();
+        transforms = CollectionSupport.emptyList();
         
         uppercase = false;
         lowercase = false;
@@ -127,35 +124,6 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
         savePasswordToCredentialSet = flag;
     }
 
-    /**
-     * Get whether to remove the {@link UsernamePasswordContext} after it's
-     * successfully validated.
-     * 
-     * <p>Defaults to true</p>
-     * 
-     * @return whether to remove the context after successful validation
-     * 
-     * @deprecated
-     */
-    @Deprecated(since="4.1.0", forRemoval=true)
-    public boolean removeContextAfterValidation() {
-        return removeContextAfterValidation;
-    }
-    
-    /**
-     * Set whether to remove the {@link UsernamePasswordContext} after it's
-     * successfully validated.
-     * 
-     * @param flag  flag to set
-     * 
-     * @deprecated
-     */
-    @Deprecated(since="4.1.0", forRemoval=true)
-    public void setRemoveContextAfterValidation(final boolean flag) {
-        checkSetterPreconditions();
-        removeContextAfterValidation = flag;
-    }
-
     /**
      * Set a matching expression to apply to the username for acceptance. 
      * 
@@ -185,7 +153,7 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
                         StringSupport.trimOrNull(p.getSecond()), "Replacement expression cannot be null")));
             }
         } else {
-            transforms = Collections.emptyList();
+            transforms = CollectionSupport.emptyList();
         }
     }
 
@@ -235,7 +203,10 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
                         AuthnEventIds.NO_CREDENTIALS);
             }
             throw new LoginException(AuthnEventIds.NO_CREDENTIALS);
-        } else if (upContext.getUsername() == null) {
+        }
+        
+        final String username = upContext.getUsername();
+        if (username == null) {
             log.info("{} No username available within UsernamePasswordContext", getLogPrefix());
             if (errorHandler != null) {
                 errorHandler.handleError(profileRequestContext, authenticationContext, AuthnEventIds.NO_CREDENTIALS,
@@ -251,7 +222,7 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
             throw new LoginException(AuthnEventIds.INVALID_CREDENTIALS);
         }
         
-        upContext.setTransformedUsername(applyTransforms(upContext.getUsername()));
+        upContext.setTransformedUsername(applyTransforms(username));
         
         if (matchExpression != null && !matchExpression.matcher(upContext.getTransformedUsername()).matches()) {
             log.debug("{} Username '{}' did not match expression", getLogPrefix(), upContext.getTransformedUsername());
@@ -294,15 +265,15 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
      */
     @Nonnull protected Subject populateSubject(@Nonnull final Subject subject,
             @Nonnull final UsernamePasswordContext usernamePasswordContext) {
-        subject.getPrincipals().add(new UsernamePrincipal(usernamePasswordContext.getTransformedUsername()));
+        
+        final String u = usernamePasswordContext.getTransformedUsername();
+        assert u != null;
+        subject.getPrincipals().add(new UsernamePrincipal(u));
+        
         if (savePasswordToCredentialSet) {
-            subject.getPrivateCredentials().add(new PasswordPrincipal(usernamePasswordContext.getPassword()));
-        }
-
-        // This is migrating out to the validation action, leaving code here for now but we won't use it.
-        if (removeContextAfterValidation) {
-            usernamePasswordContext.getParent().removeSubcontext(usernamePasswordContext);
-            usernamePasswordContext.setPassword(null);
+            final String p = usernamePasswordContext.getPassword();
+            assert p != null;
+            subject.getPrivateCredentials().add(new PasswordPrincipal(p));
         }
         
         return super.populateSubject(subject);
@@ -336,12 +307,15 @@ public abstract class AbstractUsernamePasswordCredentialValidator extends Abstra
             return s;
         }
         
-        for (final Pair<Pattern,String> p : transforms) {            
-            final Matcher m = p.getFirst().matcher(s);
-            log.trace("{} Applying replacement expression '{}' against input '{}'", getLogPrefix(),
-                    p.getFirst().pattern(), s);
-            s = m.replaceAll(p.getSecond());
-            log.trace("{} Result of replacement is '{}'", getLogPrefix(), s);
+        for (final Pair<Pattern,String> p : transforms) {
+            final Pattern pattern = p.getFirst();
+            if (pattern != null) {
+                final Matcher m = pattern.matcher(s);
+                log.trace("{} Applying replacement expression '{}' against input '{}'", getLogPrefix(),
+                        pattern.pattern(), s);
+                s = m.replaceAll(p.getSecond());
+                log.trace("{} Result of replacement is '{}'", getLogPrefix(), s);
+            }
         }
         
         return s;
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractValidationAction.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractValidationAction.java
index 951a9d71b..32761e94e 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractValidationAction.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractValidationAction.java
@@ -37,8 +37,8 @@ import org.opensaml.core.metrics.MetricsSupport;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
+import com.codahale.metrics.MetricRegistry;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 
@@ -56,7 +56,9 @@ import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
@@ -105,14 +107,13 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
     
     /** Constructor. */
     public AbstractValidationAction() {
+        metricName = DEFAULT_METRIC_NAME;
         addDefaultPrincipals = true;
         authenticatedSubject = new Subject();
         clearErrorContext = true;
-        classifiedMessages = Collections.emptyMap();
+        classifiedMessages = CollectionSupport.emptyMap();
         requesterLookupStrategy = new RelyingPartyIdLookupFunction();
         responderLookupStrategy = new ResponderIdLookupFunction();
-        
-        setMetricName(DEFAULT_METRIC_NAME);
     }
     
     /**
@@ -190,7 +191,7 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
                 }
             }
         } else {
-            classifiedMessages = Collections.emptyMap();
+            classifiedMessages = CollectionSupport.emptyMap();
         }
     }
 
@@ -335,34 +336,38 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
         // able to satisfy the request. This step only applies if the validator has been injected with
         // specific principals, otherwise the flow's capabilities have already been examined.
         final RequestedPrincipalContext rpCtx = authenticationContext.getSubcontext(RequestedPrincipalContext.class);
-        if (rpCtx != null && rpCtx.getOperator() != null && !getSubject().getPrincipals().isEmpty()) {
-            log.debug("{} Request contains principal requirements, evaluating for compatibility", getLogPrefix());
-            for (final Principal p : rpCtx.getRequestedPrincipals()) {
-                final PrincipalEvalPredicateFactory factory =
-                        rpCtx.getPrincipalEvalPredicateFactoryRegistry().lookup(p.getClass(), rpCtx.getOperator());
-                if (factory != null) {
-                    final PrincipalEvalPredicate predicate = factory.getPredicate(p);
-                    if (predicate.test(this)) {
-                        log.debug("{} Compatible with principal type '{}' and operator '{}'", getLogPrefix(),
-                                p.getClass(), rpCtx.getOperator());
-                        rpCtx.setMatchingPrincipal(predicate.getMatchingPrincipal());
-                        return true;
+        if (rpCtx != null && !getSubject().getPrincipals().isEmpty()) {
+            final String operator = rpCtx.getOperator();
+            if (operator != null) {
+                log.debug("{} Request contains principal requirements, evaluating for compatibility", getLogPrefix());
+                for (final Principal p : rpCtx.getRequestedPrincipals()) {
+                    final PrincipalEvalPredicateFactory factory =
+                            rpCtx.getPrincipalEvalPredicateFactoryRegistry().lookup(p.getClass(), operator);
+                    if (factory != null) {
+                        final PrincipalEvalPredicate predicate = factory.getPredicate(p);
+                        if (predicate.test(this)) {
+                            log.debug("{} Compatible with principal type '{}' and operator '{}'", getLogPrefix(),
+                                    p.getClass(), operator);
+                            rpCtx.setMatchingPrincipal(predicate.getMatchingPrincipal());
+                            return true;
+                        }
+                        log.debug("{} Not compatible with principal type '{}' and operator '{}'", getLogPrefix(),
+                                p.getClass(), operator);
+                    } else {
+                        log.debug("{} No comparison logic registered for principal type '{}' and operator '{}'",
+                                getLogPrefix(), p.getClass(), operator);
                     }
-                    log.debug("{} Not compatible with principal type '{}' and operator '{}'", getLogPrefix(),
-                            p.getClass(), rpCtx.getOperator());
-                } else {
-                    log.debug("{} No comparison logic registered for principal type '{}' and operator '{}'",
-                            getLogPrefix(), p.getClass(), rpCtx.getOperator());
                 }
+                
+                log.info("{} Skipping validator, not compatible with request's principal requirements", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.REQUEST_UNSUPPORTED);
+                return false;
             }
-            
-            log.info("{} Skipping validator, not compatible with request's principal requirements", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.REQUEST_UNSUPPORTED);
-            return false;
         }
         
-        if (authenticationContext.getFixedEventLookupStrategy() != null) {
-            final String fixedEvent = authenticationContext.getFixedEventLookupStrategy().apply(profileRequestContext);
+        final Function<ProfileRequestContext,String> strategy = authenticationContext.getFixedEventLookupStrategy(); 
+        if (strategy != null) {
+            final String fixedEvent = strategy.apply(profileRequestContext);
             if (fixedEvent != null) {
                 log.info("{} Signaling fixed event: {}", getLogPrefix(), fixedEvent);
                 ActionSupport.buildEvent(profileRequestContext, fixedEvent);
@@ -385,13 +390,15 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
     protected void buildAuthenticationResult(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
+        final AuthenticationFlowDescriptor attemptedFlow = authenticationContext.getAttemptedFlow();
+        assert attemptedFlow != null;
+        
         if (addDefaultPrincipals) {
             log.debug("{} Adding custom Principal(s) defined on underlying flow descriptor", getLogPrefix());
-            getSubject().getPrincipals().addAll(authenticationContext.getAttemptedFlow().getSupportedPrincipals());
+            getSubject().getPrincipals().addAll(attemptedFlow.getSupportedPrincipals());
         }
         
-        final AuthenticationResult result =
-                authenticationContext.getAttemptedFlow().newAuthenticationResult(populateSubject(getSubject()));
+        final AuthenticationResult result = attemptedFlow.newAuthenticationResult(populateSubject(getSubject()));
         authenticationContext.setAuthenticationResult(result);
         
         // Override cacheability if a predicate is installed.
@@ -401,8 +408,7 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
                     authenticationContext.isResultCacheable() ? "will" : "will not");
         }
         
-        final BiConsumer<ProfileRequestContext,Subject> decorator =
-                authenticationContext.getAttemptedFlow().getSubjectDecorator();
+        final BiConsumer<ProfileRequestContext,Subject> decorator = attemptedFlow.getSubjectDecorator();
         if (decorator != null) {
             decorator.accept(profileRequestContext, result.getSubject());
         }
@@ -416,7 +422,9 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
         if (responderLookupStrategy != null) {
             c14n.setResponderId(responderLookupStrategy.apply(profileRequestContext));
         }
-        authenticationContext.getParent().addSubcontext(c14n, true);
+        
+        Constraint.isNotNull(authenticationContext.getParent(),
+                "Parent context cannot be null").addSubcontext(c14n, true);
     }
     
     /**
@@ -431,36 +439,6 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
      */
     @Nonnull protected abstract Subject populateSubject(@Nonnull final Subject subject);
     
-    /**
-     * Record a successful authentication attempt against the configured counter. Records
-     * nothing if the metrics registry is not installed into the runtime.
-     * 
-     * @since 3.3.0
-     * 
-     * @deprecated
-     */
-    @Deprecated(since="4.1.0", forRemoval=true)
-    protected void recordSuccess() {
-        if (MetricsSupport.getMetricRegistry() != null) {
-            MetricsSupport.getMetricRegistry().counter(getMetricName() + ".successes").inc();
-        }
-    }
-    
-    /**
-     * Record a failed authentication attempt against the configured counter. Records
-     * nothing if the metrics registry is not installed into the runtime.
-     * 
-     * @since 3.3.0
-     * 
-     * @deprecated
-     */
-    @Deprecated(since="4.1.0", forRemoval=true)
-    protected void recordFailure() {
-        if (MetricsSupport.getMetricRegistry() != null) {
-            MetricsSupport.getMetricRegistry().counter(getMetricName() + ".failures").inc();
-        }
-    }
-    
     /**
      * Record a successful authentication attempt against the configured counter. Records
      * nothing if the metrics registry is not installed into the runtime.
@@ -470,7 +448,10 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
      * @since 4.1.0
      */
     protected void recordSuccess(@Nonnull final ProfileRequestContext profileRequestContext) {
-        recordSuccess();
+        final MetricRegistry registry = MetricsSupport.getMetricRegistry();
+        if (registry != null) {
+            registry.counter(getMetricName() + ".successes").inc();
+        }
         if (cleanupHook != null) {
             cleanupHook.accept(profileRequestContext);
         }
@@ -485,7 +466,10 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
      * @since 4.1.0
      */
     protected void recordFailure(@Nonnull final ProfileRequestContext profileRequestContext) {
-        recordFailure();
+        final MetricRegistry registry = MetricsSupport.getMetricRegistry();
+        if (registry != null) {
+            registry.counter(getMetricName() + ".failures").inc();
+        }
     }
     
     /**
@@ -506,7 +490,7 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
             @Nonnull final AuthenticationContext authenticationContext, @Nonnull final Exception e,
             @Nonnull @NotEmpty final String eventId) {
 
-        authenticationContext.getSubcontext(AuthenticationErrorContext.class, true).getExceptions().add(e);
+        authenticationContext.getOrCreateSubcontext(AuthenticationErrorContext.class).getExceptions().add(e);
 
         handleError(profileRequestContext, authenticationContext, e.getMessage(), eventId);
     }
@@ -533,12 +517,13 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
         boolean eventSet = false;
 
         if (!Strings.isNullOrEmpty(message)) {
+            assert message != null;
             final MessageChecker checker = new MessageChecker(message);
             
             for (final Map.Entry<String, Collection<String>> entry : classifiedMessages.entrySet()) {
                 if (Iterables.any(entry.getValue(), checker::test)) {
-                    authenticationContext.getSubcontext(AuthenticationErrorContext.class,
-                            true).getClassifiedErrors().add(entry.getKey());
+                    authenticationContext.getOrCreateSubcontext(
+                            AuthenticationErrorContext.class).getClassifiedErrors().add(entry.getKey());
                     if (!eventSet) {
                         eventSet = true;
                         ActionSupport.buildEvent(profileRequestContext, entry.getKey());
@@ -548,8 +533,8 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
         }
         
         if (!eventSet) {
-            authenticationContext.getSubcontext(AuthenticationErrorContext.class,
-                    true).getClassifiedErrors().add(eventId);
+            authenticationContext.getOrCreateSubcontext(
+                    AuthenticationErrorContext.class).getClassifiedErrors().add(eventId);
             ActionSupport.buildEvent(profileRequestContext, eventId);
         }
     }
@@ -576,12 +561,13 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
         boolean eventSet = false;
         
         if (!Strings.isNullOrEmpty(message)) {
+            assert message != null;
             final MessageChecker checker = new MessageChecker(message);
             
             for (final Map.Entry<String, Collection<String>> entry : classifiedMessages.entrySet()) {
                 if (Iterables.any(entry.getValue(), checker::test)) {
-                    authenticationContext.getSubcontext(AuthenticationWarningContext.class,
-                            true).getClassifiedWarnings().add(entry.getKey());
+                    authenticationContext.getOrCreateSubcontext(
+                            AuthenticationWarningContext.class).getClassifiedWarnings().add(entry.getKey());
                     if (!eventSet) {
                         eventSet = true;
                         ActionSupport.buildEvent(profileRequestContext, entry.getKey());
@@ -591,8 +577,8 @@ public abstract class AbstractValidationAction extends AbstractAuthenticationAct
         }
         
         if (!eventSet) {
-            authenticationContext.getSubcontext(AuthenticationWarningContext.class,
-                    true).getClassifiedWarnings().add(eventId);
+            authenticationContext.getOrCreateSubcontext(
+                    AuthenticationWarningContext.class).getClassifiedWarnings().add(eventId);
             ActionSupport.buildEvent(profileRequestContext, eventId);
         }
     }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
index 8b54ef380..31b314465 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
@@ -23,7 +23,6 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.Map;
 import java.util.Set;
@@ -40,7 +39,6 @@ import org.opensaml.storage.StorageSerializer;
 import org.springframework.core.Ordered;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Predicates;
 
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.principal.PrincipalService;
@@ -50,6 +48,7 @@ import net.shibboleth.idp.profile.FlowDescriptor;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
@@ -127,7 +126,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     @Nullable private StorageSerializer<AuthenticationResult> resultSerializer;
     
     /** Weighted sort oredering of custom Principals produced by flow(s). */
-    @Nullable @NonnullElements private Map<Principal,Integer> principalWeightMap;
+    @Nonnull @NonnullElements private Map<Principal,Integer> principalWeightMap;
     
     /** Access to principal services. */
     @Nullable private PrincipalServiceManager principalServiceManager;
@@ -142,10 +141,10 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
         proxyRestrictionsEnforced = true;
         reuseCondition = new ProxyCountPredicate();
         supportedPrincipals = new Subject();
-        activationCondition = Predicates.alwaysTrue();
+        activationCondition = PredicateSupport.alwaysTrue();
         inactivityTimeout = Duration.ofMinutes(30);
-        principalWeightMap = Collections.emptyMap();
-        stringBasedPrincipals = Collections.emptySet();
+        principalWeightMap = CollectionSupport.emptyMap();
+        stringBasedPrincipals = CollectionSupport.emptySet();
     }
     
     /** {@inheritDoc} */
@@ -329,7 +328,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
      * 
      * @since 4.3.0
      */
-    @Nonnull public BiPredicate<ProfileRequestContext,AuthenticationResult> getRevocationCondition() {
+    @Nullable public BiPredicate<ProfileRequestContext,AuthenticationResult> getRevocationCondition() {
         return revocationCondition;
     }
     
@@ -490,7 +489,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
      */
     public void setSupportedPrincipalsByString(@Nonnull @NonnullElements final Collection<String> principals) {
         checkSetterPreconditions();
-        stringBasedPrincipals = Set.copyOf(StringSupport.normalizeStringCollection(principals));
+        stringBasedPrincipals = CollectionSupport.copyToSet(StringSupport.normalizeStringCollection(principals));
     }
 
     /**
@@ -531,7 +530,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
      */
     public void setPrincipalWeightMap(@Nullable @NonnullElements final Map<Principal,Integer> map) {
         checkSetterPreconditions();
-        principalWeightMap = map != null ? map : Collections.emptyMap();
+        principalWeightMap = map != null ? map : CollectionSupport.emptyMap();
     }
     
     /**
@@ -562,6 +561,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
             supportedPrincipals.getPrincipals().clear();
             
             stringBasedPrincipals.forEach(v -> {
+                assert principalServiceManager != null;
                 final Principal p = principalServiceManager.principalFromString(v);
                 if (p != null) {
                     supportedPrincipals.getPrincipals().add(p);
@@ -595,17 +595,19 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
             throws IOException {
         checkComponentActive();
 
+        assert resultSerializer != null;
         return resultSerializer.serialize(instance);
     }
 
     /** {@inheritDoc} */
     @Override @Nonnull public AuthenticationResult deserialize(final long version,
             @Nonnull @NotEmpty final String context, @Nonnull @NotEmpty final String key, 
-            @Nonnull @NotEmpty final String value, @Nonnull final Long expiration)
+            @Nonnull @NotEmpty final String value, @Nullable final Long expiration)
             throws IOException {
         checkComponentActive();
 
         // Back the expiration off by the inactivity timeout to recover the last activity time.
+        assert resultSerializer != null;
         final AuthenticationResult result = resultSerializer.deserialize(version, context, key, value,
                 (expiration != null) ?
                         expiration - inactivityTimeout.toMillis() - STORAGE_EXPIRATION_OFFSET.toMillis() :
@@ -705,7 +707,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
         /** {@inheritDoc} */
         public boolean test(@Nullable final ProfileRequestContext input) {
             
-            if (proxyScopingEnforced) {
+            if (proxyScopingEnforced && input != null) {
                 final AuthenticationContext authnCtx = input.getSubcontext(AuthenticationContext.class);
                 if (authnCtx != null && authnCtx.getProxyCount() != null && authnCtx.getProxyCount() == 0) {
                     return false;
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java
index 444ad4943..8c2193420 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationResult.java
@@ -79,7 +79,7 @@ public class AuthenticationResult implements PrincipalSupportingComponent, Predi
     @Nonnull private Predicate<ProfileRequestContext> reuseCondition;
     
     /** Whether this result should be considered revoked. */
-    @Nonnull private BiPredicate<ProfileRequestContext,AuthenticationResult> revocationCondition;
+    @Nullable private BiPredicate<ProfileRequestContext,AuthenticationResult> revocationCondition;
     
     /**
      * Constructor.
@@ -383,8 +383,10 @@ public class AuthenticationResult implements PrincipalSupportingComponent, Predi
                 if (ac != null) {
                     final AuthenticationFlowDescriptor flow = ac.getAvailableFlows().get(authenticationFlowId);
                     if (flow != null) {
-                        if (flow.getRevocationCondition() != null) {
-                            return flow.getRevocationCondition().test(prc, result);
+                        final BiPredicate<ProfileRequestContext,AuthenticationResult> condition =
+                                flow.getRevocationCondition();
+                        if (condition != null) {
+                            return condition.test(prc, result);
                         } else {
                             return false;
                         }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java
index b13775133..0d03f031d 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java
@@ -160,7 +160,8 @@ public abstract class ExternalAuthentication {
         if (Strings.isNullOrEmpty(key)) {
             throw new ExternalAuthenticationException("No conversation key found in request");
         }
-
+        assert key != null;
+        
         final ProfileRequestContext profileRequestContext = getProfileRequestContext(key, request);
         final ExternalAuthenticationContext extContext = getExternalAuthenticationContext(profileRequestContext);
         extContext.getExternalAuthentication().doStart(request, profileRequestContext, extContext);
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java
index 600b00209..8842a12a6 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java
@@ -17,7 +17,6 @@
 
 package net.shibboleth.idp.authn;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Function;
@@ -28,6 +27,7 @@ import javax.annotation.Nullable;
 import net.shibboleth.shared.annotation.constraint.Live;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.FunctionSupport;
 import net.shibboleth.shared.primitive.StringSupport;
@@ -124,7 +124,7 @@ public class MultiFactorAuthenticationTransition {
      * @param flowId fully-qualified flow ID to run
      */
     public void setNextFlow(@Nullable @NotEmpty final String flowId) {
-        setNextFlowStrategyMap(Collections.singletonMap("proceed", flowId));
+        setNextFlowStrategyMap(CollectionSupport.singletonMap("proceed", flowId));
     }
 
     /**
@@ -138,7 +138,7 @@ public class MultiFactorAuthenticationTransition {
     public void setNextFlowStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
         Constraint.isNotNull(strategy, "Flow strategy function cannot be null");
         
-        setNextFlowStrategyMap(Collections.singletonMap("proceed", strategy));
+        setNextFlowStrategyMap(CollectionSupport.singletonMap("proceed", strategy));
     }
 
 }
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java
index 043ab7488..3449b56ec 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java
@@ -24,11 +24,11 @@ import javax.annotation.Nonnull;
 import org.opensaml.profile.context.ProfileRequestContext;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Predicates;
 
 import net.shibboleth.idp.profile.FlowDescriptor;
 import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
 
 /**
  * A descriptor for a subject canonicalization flow.
@@ -49,7 +49,7 @@ public class SubjectCanonicalizationFlowDescriptor extends AbstractIdentifiableI
 
     /** Constructor. */
     public SubjectCanonicalizationFlowDescriptor() {
-        activationCondition = Predicates.alwaysTrue();
+        activationCondition = PredicateSupport.alwaysTrue();
     }
 
     /**
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 5d57bff49..177a34ae2 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
@@ -18,12 +18,15 @@
 package net.shibboleth.idp.authn;
 
 import java.util.Arrays;
+
+import javax.annotation.Nonnull;
+
 import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.Connection;
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.ConnectionFactoryManager;
-import org.ldaptive.LdapException;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
 
 /**
  * {@link net.shibboleth.shared.velocity.Template}-based search dn resolver.
@@ -38,7 +41,8 @@ public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver i
      *
      * @throws VelocityException if velocity is not configured properly or the filter template is invalid
      */
-    public TemplateSearchDnResolver(final VelocityEngine engine, final String filter) throws VelocityException {
+    public TemplateSearchDnResolver(@Nonnull final VelocityEngine engine, @Nonnull @NotEmpty final String filter)
+            throws VelocityException {
         super(engine, filter);
     }
 
@@ -51,7 +55,8 @@ public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver i
      *
      * @throws VelocityException if velocity is not configured properly or the filter template is invalid
      */
-    public TemplateSearchDnResolver(final ConnectionFactory cf, final VelocityEngine engine, final String filter)
+    public TemplateSearchDnResolver(@Nonnull final ConnectionFactory cf, @Nonnull final VelocityEngine engine,
+            @Nonnull @NotEmpty final String filter)
             throws VelocityException {
         super(engine, filter);
         setConnectionFactory(cf);
@@ -65,4 +70,5 @@ public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver i
                 getUserFilter(), Arrays.toString(getUserFilterParameters()), getAllowMultipleDns(), getSubtreeSearch(),
                 getDerefAliases());
     }
-}
+
+}
\ No newline at end of file
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 6a38bbb5f..3ecace246 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
@@ -26,6 +26,7 @@ import javax.annotation.Nullable;
 import com.google.common.base.MoreObjects;
 import net.shibboleth.idp.authn.TemplateSearchDnResolver;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 import org.apache.velocity.app.VelocityEngine;
 import org.ldaptive.ActivePassiveConnectionStrategy;
@@ -59,739 +60,856 @@ import org.ldaptive.ssl.AllowAnyHostnameVerifier;
 import org.ldaptive.ssl.CredentialConfig;
 import org.ldaptive.ssl.SslConfig;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
 import org.springframework.beans.factory.config.AbstractFactoryBean;
 
 /** LDAP Authentication configuration. See ldap-authn-config.xml */
 public class LDAPAuthenticationFactoryBean extends AbstractFactoryBean<Authenticator> {
 
-  /** Class logger. */
-  @Nonnull
-  private final Logger log = LoggerFactory.getLogger(LDAPAuthenticationFactoryBean.class);
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(LDAPAuthenticationFactoryBean.class);
+
+    /** Enum that defines authenticator configuration. Labels maps to values in ldap.properties. */
+    public enum AuthenticatorType {
+        
+        /** Anonymous bind. */
+        ANON_SEARCH("anonSearchAuthenticator"),
+        
+        /** Authenticated bind. */
+        BIND_SEARCH("bindSearchAuthenticator"),
+        
+        /** Direct bind by subject. */
+        DIRECT("directAuthenticator"),
+        
+        /** AD specific bind. */
+        AD("adAuthenticator");
+
+        /** Label for this type. */
+        @Nonnull @NotEmpty private final String label;
+    
+        /**
+         * Constructor.
+         *
+         * @param s label for enum
+         */
+        AuthenticatorType(@Nonnull @NotEmpty final String s) {
+            label = s;
+        }
+    
+        /**
+         * Gets the enum string label.
+         * 
+         * @return string label
+         */
+        @Nonnull @NotEmpty public String label() {
+            return label;
+        }
+    
+        /**
+         * Returns the enum matching the input label.
+         *  
+         * @param s input label
+         * 
+         * @return matching enum or null
+         */
+        @Nullable public static AuthenticatorType fromLabel(@Nonnull @NotEmpty final String s) {
+            for (final AuthenticatorType at : AuthenticatorType.values()) {
+                if (at.label().equals(s)) {
+                    return at;
+                }
+            }
+            return null;
+        }
+    }
 
-  /** Enum that defines authenticator configuration. Labels maps to values in ldap.properties. */
-  public enum AuthenticatorType {
-    ANON_SEARCH("anonSearchAuthenticator"),
-    BIND_SEARCH("bindSearchAuthenticator"),
-    DIRECT("directAuthenticator"),
-    AD("adAuthenticator");
+    /** Enum that defines LDAP trust configuration. Labels maps to values in ldap.properties. */
+    public enum TrustType {
+        
+        /** JVM trust. */
+        JVM("jvmTrust"),
+        
+        /** Explicit certificate file trust. */
+        CERTIFICATE("certificateTrust"),
+        
+        /** Explicit keystore file trust. */
+        KEYSTORE("keyStoreTrust"),
+        
+        /** Trust disabled for non-TLS. */
+        DISABLED("disabled");
+
+        /** Label for this type. */
+        @Nonnull @NotEmpty private final String label;
+
+        /**
+         * Constructor.
+         *
+         * @param s
+         *            label for enum
+         */
+        TrustType(@Nonnull @NotEmpty final String s) {
+            label = s;
+        }
 
-    /** Label for this type. */
-    private final String label;
+        /**
+         * Gets the enum string label.
+         * 
+         * @return string label
+         */
+        @Nonnull
+        @NotEmpty
+        public String label() {
+            return label;
+        }
 
-    AuthenticatorType(final String s) {
-      label = s;
+        /**
+         * Returns the enum matching the input label.
+         * 
+         * @param s
+         *            input label
+         * 
+         * @return matching enum or null
+         */
+        @Nullable
+        public static TrustType fromLabel(@Nonnull @NotEmpty final String s) {
+            for (final TrustType tt : TrustType.values()) {
+                if (tt.label().equals(s)) {
+                    return tt;
+                }
+            }
+            return null;
+        }
     }
 
-    public String label() {
-      return label;
-    }
+    /**
+     * Enum that defines an LDAP pool passivator. Labels maps to values in
+     * ldap.properties.
+     */
+    public enum PassivatorType {
+        
+        /** No passivator. */
+        NONE("none"),
+        
+        /** Bind passivator. */
+        BIND("bind"),
+        
+        /** Anonymoud bind passivator. */
+        ANONYMOUS_BIND("anonymousBind");
+
+        /** Label for this type. */
+        @Nonnull
+        @NotEmpty
+        private final String label;
+
+        /**
+         * Constructor.
+         *
+         * @param s
+         *            label for enum
+         */
+        PassivatorType(@Nonnull @NotEmpty final String s) {
+            label = s;
+        }
+
+        /**
+         * Gets the enum string label.
+         * 
+         * @return string label
+         */
+        @Nonnull
+        @NotEmpty
+        public String label() {
+            return label;
+        }
 
-    public static AuthenticatorType fromLabel(final String s) {
-      for (AuthenticatorType at : AuthenticatorType.values()) {
-        if (at.label().equals(s)) {
-          return at;
+        /**
+         * Returns the enum matching the input label.
+         * 
+         * @param s
+         *            input label
+         * 
+         * @return matching enum or null
+         */
+        @Nullable
+        public static PassivatorType fromLabel(@Nonnull @NotEmpty final String s) {
+            for (final PassivatorType pt : PassivatorType.values()) {
+                if (pt.label().equals(s)) {
+                    return pt;
+                }
+            }
+            return null;
         }
-      }
-      return null;
     }
-  }
 
-  /** Enum that defines LDAP trust configuration. Labels maps to values in ldap.properties. */
-  public enum TrustType {
-    JVM("jvmTrust"),
-    CERTIFICATE("certificateTrust"),
-    KEYSTORE("keyStoreTrust"),
-    DISABLED("disabled");
+    /**
+     * Enum that defines LDAP connection strategy. Labels maps to values in
+     * ldap.properties.
+     */
+    public enum ConnectionStrategyType {
+        
+        /** Active/passive connection strategy. */
+        ACTIVE_PASSIVE("ACTIVE_PASSIVE"),
+        
+        /** Round robin connection strategy. */
+        ROUND_ROBIN("ROUND_ROBIN"),
+        
+        /** Random connection strategy. */
+        RANDOM("RANDOM");
+
+        /** Label for this type. */
+        @Nonnull
+        @NotEmpty
+        private final String label;
+
+        /**
+         * Constructor.
+         *
+         * @param s
+         *            label for enum
+         */
+        ConnectionStrategyType(@Nonnull @NotEmpty final String s) {
+            label = s;
+        }
 
-    /** Label for this type. */
-    private final String label;
+        /**
+         * Gets the enum string label.
+         * 
+         * @return string label
+         */
+        @Nonnull
+        @NotEmpty
+        public String label() {
+            return label;
+        }
 
-    TrustType(final String s) {
-      label = s;
+        /**
+         * Returns the enum matching the input label.
+         * 
+         * @param s
+         *            input label
+         * 
+         * @return matching enum or null
+         */
+        @Nullable
+        public static ConnectionStrategyType fromLabel(@Nonnull @NotEmpty final String s) {
+            for (final ConnectionStrategyType cst : ConnectionStrategyType.values()) {
+                if (cst.label().equals(s)) {
+                    return cst;
+                }
+            }
+            return null;
+        }
     }
+  
 
-    public String label() {
-      return label;
-    }
+    /** Type of authenticator to configure. */
+    private AuthenticatorType authenticatorType;
 
-    public static TrustType fromLabel(final String s) {
-      for (TrustType tt : TrustType.values()) {
-        if (tt.label().equals(s)) {
-          return tt;
-        }
-      }
-      return null;
-    }
-  }
+    /** Type of trust model to configure. */
+    private TrustType trustType;
 
-  /** Enum that defines an LDAP pool passivator. Labels maps to values in ldap.properties. */
-  public enum PassivatorType {
-    NONE("none"),
-    BIND("bind"),
-    ANONYMOUS_BIND("anonymousBind");
+    /** Type of connection strategy to configure. */
+    private ConnectionStrategyType connectionStrategyType;
 
-    /** Label for this type. */
-    private final String label;
+    /** LDAP URL. */
+    private String ldapUrl;
 
-    PassivatorType(final String s) {
-      label = s;
-    }
+    /** Whether to use startTLS for connections. */
+    private boolean useStartTLS;
 
-    public String label() {
-      return label;
-    }
+    /** Whether to use the allow-all hostname verifier. */
+    private boolean disableHostnameVerification;
 
-    public static PassivatorType fromLabel(final String s) {
-      for (PassivatorType pt : PassivatorType.values()) {
-        if (pt.label().equals(s)) {
-          return pt;
-        }
-      }
-      return null;
-    }
-  }
+    /** Wait time for connects. */
+    private Duration connectTimeout;
 
-  /** Enum that defines LDAP connection strategy. Labels maps to values in ldap.properties. */
-  public enum ConnectionStrategyType {
-    ACTIVE_PASSIVE("ACTIVE_PASSIVE"),
-    ROUND_ROBIN("ROUND_ROBIN"),
-    RANDOM("RANDOM");
+    /** Wait time for operation responses. */
+    private Duration responseTimeout;
 
-    /** Label for this type. */
-    private final String label;
+    /** Trust configuration when using certificate based trust. */
+    private CredentialConfig trustCertificatesCredentialConfig;
 
-    ConnectionStrategyType(final String s) {
-      label = s;
-    }
+    /** Trust configuration when using truststore based trust. */
+    private CredentialConfig truststoreCredentialConfig;
 
-    public String label() {
-      return label;
-    }
+    /** Whether to disable connection pooling for both binds and searches. */
+    private boolean disablePooling;
 
-    public static ConnectionStrategyType fromLabel(final String s) {
-      for (ConnectionStrategyType cst : ConnectionStrategyType.values()) {
-        if (cst.label().equals(s)) {
-          return cst;
-        }
-      }
-      return null;
-    }
-  }
+    /** Wait time for getting a connection from the pool. */
+    private Duration blockWaitTime;
 
-  /** Type of authenticator to configure. */
-  private AuthenticatorType authenticatorType;
+    /** Minimum pool size. */
+    private int minPoolSize;
 
-  /** Type of trust model to configure. */
-  private TrustType trustType;
+    /** Maximum pool size. */
+    private int maxPoolSize;
 
-  /** Type of connection strategy to configure. */
-  private ConnectionStrategyType connectionStrategyType;
+    /** Whether to validate connections when checked out from the pool. */
+    private boolean validateOnCheckout;
 
-  /** LDAP URL. */
-  private String ldapUrl;
+    /** Whether to validate connections periodically on a background thread. */
+    private boolean validatePeriodically;
 
-  /** Whether to use startTLS for connections. */
-  private boolean useStartTLS;
+    /** Period at which to validate periodically. */
+    private Duration validatePeriod;
 
-  /** Whether to use the allow-all hostname verifier. */
-  private boolean disableHostnameVerification;
+    /** DN to perform connection pool validation against. */
+    private String validateDn;
 
-  /** Wait time for connects. */
-  private Duration connectTimeout;
+    /** Filter to execute against {@link #validateDn}. */
+    private String validateFilter;
 
-  /** Wait time for operation responses. */
-  private Duration responseTimeout;
+    /** Type of passivator to configure for the bind pool. */
+    private PassivatorType bindPoolPassivatorType;
 
-  /** Trust configuration when using certificate based trust. */
-  private CredentialConfig trustCertificatesCredentialConfig;
+    /** Period at which to check and enforce the idle time. */
+    private Duration prunePeriod;
 
-  /** Trust configuration when using truststore based trust. */
-  private CredentialConfig truststoreCredentialConfig;
+    /**
+     * Time at which a connection has been idle and should be removed from the pool.
+     */
+    private Duration idleTime;
 
-  /** Whether to disable connection pooling for both binds and searches. */
-  private boolean disablePooling;
+    /**
+     * Java format string used to construct an LDAP DN. See
+     * {@link String#format(String, Object...)}.
+     */
+    private String dnFormat;
 
-  /** Wait time for getting a connection from the pool. */
-  private Duration blockWaitTime;
+    /** Base DN used to search for users. */
+    private String baseDn;
 
-  /** Minimum pool size. */
-  private int minPoolSize;
+    /** LDAP filter used to search for users. */
+    private String userFilter;
 
-  /** Maximum pool size. */
-  private int maxPoolSize;
+    /** Whether to use a SUBTREE search with the baseDn. */
+    private boolean subtreeSearch;
 
-  /** Whether to validate connections when checked out from the pool. */
-  private boolean validateOnCheckout;
+    /** Whether to return the LDAP entry even if the user BIND fails. */
+    private boolean resolveEntryOnFailure;
 
-  /** Whether to validate connections periodically on a background thread. */
-  private boolean validatePeriodically;
+    /** Whether to resolve the user entry with the bind credentials. */
+    private boolean resolveEntryWithBindDn;
 
-  /** Period at which to validate periodically. */
-  private Duration validatePeriod;
+    /** Velocity engine used to materialize the LDAP filter. */
+    private VelocityEngine velocityEngine;
 
-  /** DN to perform connection pool validation against. */
-  private String validateDn;
+    /** Privileged entry used to search for users. */
+    private String bindDn;
 
-  /** Filter to execute against {@link #validateDn}. */
-  private String validateFilter;
+    /** Credential for the privileged entry. */
+    private String bindDnCredential;
 
-  /** Type of passivator to configure for the bind pool. */
-  private PassivatorType bindPoolPassivatorType;
+    /**
+     * Whether to use the password policy control with the BIND operation. See
+     * draft-behera-ldap-password-policy.
+     */
+    private boolean usePasswordPolicy;
 
-  /** Period at which to check and enforce the idle time. */
-  private Duration prunePeriod;
+    /**
+     * Whether to use the password expiration control with the BIND operation. See
+     * draft-vchu-ldap-pwd-policy.
+     */
+    private boolean usePasswordExpiration;
 
-  /** Time at which a connection has been idle and should be removed from the pool. */
-  private Duration idleTime;
+    /**
+     * Whether to use account state data as defined by active directory diagnostic
+     * messages.
+     */
+    private boolean isActiveDirectory;
 
-  /** Java format string used to construct an LDAP DN. See {@link String#format(String, Object...)}. */
-  private String dnFormat;
+    /**
+     * Whether to use account state data as defined by the FreeIPA directory schema.
+     */
+    private boolean isFreeIPA;
 
-  /** Base DN used to search for users. */
-  private String baseDn;
+    /** Whether to use account state data as defined by the EDirectory schema. */
+    private boolean isEDirectory;
 
-  /** LDAP filter used to search for users. */
-  private String userFilter;
+    /** Authentication handler account state expiration period. */
+    private Period accountStateExpirationPeriod;
 
-  /** Whether to use a SUBTREE search with the baseDn. */
-  private boolean subtreeSearch;
+    /** Authentication handler account state warning period. */
+    private Period accountStateWarningPeriod;
 
-  /** Whether to return the LDAP entry even if the user BIND fails. */
-  private boolean resolveEntryOnFailure;
+    /** Authentication handler account state login failures. */
+    private int accountStateLoginFailures;
 
-  /** Whether to resolve the user entry with the bind credentials. */
-  private boolean resolveEntryWithBindDn;
+    public void setAuthenticatorType(@Nonnull @NotEmpty final String type) {
+        authenticatorType = AuthenticatorType.fromLabel(type);
+        if (authenticatorType == null) {
+            throw new IllegalArgumentException("authenticatorType property did not have a valid value");
+        }
+    }
 
-  /** Velocity engine used to materialize the LDAP filter. */
-  private VelocityEngine velocityEngine;
+    public void setTrustType(@Nonnull @NotEmpty final String type) {
+        trustType = TrustType.fromLabel(type);
+        if (trustType == null) {
+            throw new IllegalArgumentException("trustType property did not have a valid value");
+        }
+    }
 
-  /** Privileged entry used to search for users. */
-  private String bindDn;
+    public void setConnectionStrategyType(@Nonnull @NotEmpty final String type) {
+        connectionStrategyType = ConnectionStrategyType.fromLabel(type);
+        if (connectionStrategyType == null) {
+            throw new IllegalArgumentException("connectionStrategyType property did not have a valid value");
+        }
+    }
 
-  /** Credential for the privileged entry. */
-  private String bindDnCredential;
+    public void setLdapUrl(@Nullable @NotEmpty final String url) {
+        ldapUrl = url;
+    }
 
-  /** Whether to use the password policy control with the BIND operation. See draft-behera-ldap-password-policy. */
-  private boolean usePasswordPolicy;
+    public void setUseStartTLS(final boolean b) {
+        useStartTLS = b;
+    }
 
-  /** Whether to use the password expiration control with the BIND operation. See draft-vchu-ldap-pwd-policy. */
-  private boolean usePasswordExpiration;
+    public void setDisableHostnameVerification(final boolean b) {
+        disableHostnameVerification = b;
+    }
 
-  /** Whether to use account state data as defined by active directory diagnostic messages. */
-  private boolean isActiveDirectory;
+    public void setConnectTimeout(@Nullable final Duration timeout) {
+        connectTimeout = timeout;
+    }
 
-  /** Whether to use account state data as defined by the FreeIPA directory schema. */
-  private boolean isFreeIPA;
+    public void setResponseTimeout(@Nullable final Duration timeout) {
+        responseTimeout = timeout;
+    }
 
-  /** Whether to use account state data as defined by the EDirectory schema. */
-  private boolean isEDirectory;
+    public void setTrustCertificatesCredentialConfig(final CredentialConfig config) {
+        trustCertificatesCredentialConfig = config;
+    }
 
-  /** Authentication handler account state expiration period. */
-  private Period accountStateExpirationPeriod;
+    public void setTruststoreCredentialConfig(final CredentialConfig config) {
+        truststoreCredentialConfig = config;
+    }
 
-  /** Authentication handler account state warning period. */
-  private Period accountStateWarningPeriod;
+    public void setDisablePooling(final boolean b) {
+        disablePooling = b;
+    }
 
-  /** Authentication handler account state login failures. */
-  private int accountStateLoginFailures;
+    public void setBlockWaitTime(@Nullable final Duration time) {
+        blockWaitTime = time;
+    }
 
-  public void setAuthenticatorType(@Nonnull @NotEmpty final String type) {
-    authenticatorType = AuthenticatorType.fromLabel(type);
-    if (authenticatorType == null) {
-        throw new IllegalArgumentException("authenticatorType property did not have a valid value");
+    public void setMinPoolSize(final int size) {
+        minPoolSize = size;
     }
-  }
 
-  public void setTrustType(@Nonnull @NotEmpty final String type) {
-    trustType = TrustType.fromLabel(type);
-    if (trustType == null) {
-        throw new IllegalArgumentException("trustType property did not have a valid value");
+    public void setMaxPoolSize(final int size) {
+        maxPoolSize = size;
     }
-  }
 
-  public void setConnectionStrategyType(@Nonnull @NotEmpty final String type) {
-    connectionStrategyType = ConnectionStrategyType.fromLabel(type);
-    if (connectionStrategyType == null) {
-        throw new IllegalArgumentException("connectionStrategyType property did not have a valid value");
+    public void setValidateOnCheckout(final boolean b) {
+        validateOnCheckout = b;
     }
-  }
 
-  public void setLdapUrl(@Nullable @NotEmpty final String url) {
-    ldapUrl = url;
-  }
+    public void setValidatePeriodically(final boolean b) {
+        validatePeriodically = b;
+    }
 
-  public void setUseStartTLS(final boolean b) {
-    useStartTLS = b;
-  }
+    public void setValidatePeriod(@Nullable final Duration period) {
+        validatePeriod = period;
+    }
 
-  public void setDisableHostnameVerification(final boolean b) {
-      disableHostnameVerification = b;
-  }
+    public void setValidateDn(final String dn) {
+        validateDn = dn;
+    }
 
-  public void setConnectTimeout(@Nullable final Duration timeout) {
-    connectTimeout = timeout;
-  }
+    public void setValidateFilter(final String filter) {
+        validateFilter = filter;
+    }
 
-  public void setResponseTimeout(@Nullable final Duration timeout) {
-    responseTimeout = timeout;
-  }
+    public void setBindPoolPassivatorType(@Nonnull @NotEmpty final String type) {
+        bindPoolPassivatorType = PassivatorType.fromLabel(type);
+        if (bindPoolPassivatorType == null) {
+            throw new IllegalArgumentException("bindPoolPassivatorType property did not have a valid value");
+        }
+    }
 
-  public void setTrustCertificatesCredentialConfig(final CredentialConfig config) {
-    trustCertificatesCredentialConfig = config;
-  }
+    public void setPrunePeriod(@Nullable final Duration period) {
+        prunePeriod = period;
+    }
 
-  public void setTruststoreCredentialConfig(final CredentialConfig config) {
-    truststoreCredentialConfig = config;
-  }
+    public void setIdleTime(@Nullable final Duration time) {
+        idleTime = time;
+    }
 
-  public void setDisablePooling(final boolean b) {
-    disablePooling = b;
-  }
+    public void setDnFormat(final String format) {
+        dnFormat = format;
+    }
 
-  public void setBlockWaitTime(@Nullable final Duration time) {
-    blockWaitTime = time;
-  }
+    public void setBaseDn(final String dn) {
+        baseDn = dn;
+    }
 
-  public void setMinPoolSize(final int size) {
-    minPoolSize = size;
-  }
+    public void setUserFilter(final String filter) {
+        userFilter = filter;
+    }
 
-  public void setMaxPoolSize(final int size) {
-    maxPoolSize = size;
-  }
+    public void setSubtreeSearch(final boolean b) {
+        subtreeSearch = b;
+    }
 
-  public void setValidateOnCheckout(final boolean b) {
-    validateOnCheckout = b;
-  }
+    public void setResolveEntryOnFailure(final boolean b) {
+        resolveEntryOnFailure = b;
+    }
 
-  public void setValidatePeriodically(final boolean b) {
-    validatePeriodically = b;
-  }
+    public void setResolveEntryWithBindDn(final boolean b) {
+        resolveEntryWithBindDn = b;
+    }
 
-  public void setValidatePeriod(@Nullable final Duration period) {
-    validatePeriod = period;
-  }
+    public void setVelocityEngine(final VelocityEngine engine) {
+        velocityEngine = engine;
+    }
 
-  public void setValidateDn(final String dn) {
-    validateDn = dn;
-  }
+    public void setBindDn(final String dn) {
+        bindDn = dn;
+    }
 
-  public void setValidateFilter(final String filter) {
-    validateFilter = filter;
-  }
-
-  public void setBindPoolPassivatorType(@Nonnull @NotEmpty final String type) {
-    bindPoolPassivatorType = PassivatorType.fromLabel(type);
-    if (bindPoolPassivatorType == null) {
-        throw new IllegalArgumentException("bindPoolPassivatorType property did not have a valid value");
-    }
-  }
-
-  public void setPrunePeriod(@Nullable final Duration period) {
-    prunePeriod = period;
-  }
-
-  public void setIdleTime(@Nullable final Duration time) {
-    idleTime = time;
-  }
-
-  public void setDnFormat(final String format) {
-    dnFormat = format;
-  }
-
-  public void setBaseDn(final String dn) {
-    baseDn = dn;
-  }
-
-  public void setUserFilter(final String filter) {
-    userFilter = filter;
-  }
-
-  public void setSubtreeSearch(final boolean b) {
-    subtreeSearch = b;
-  }
-
-  public void setResolveEntryOnFailure(final boolean b) {
-    resolveEntryOnFailure = b;
-  }
-
-  public void setResolveEntryWithBindDn(final boolean b) {
-    resolveEntryWithBindDn = b;
-  }
-
-  public void setVelocityEngine(final VelocityEngine engine) {
-    velocityEngine = engine;
-  }
-
-  public void setBindDn(final String dn) {
-    bindDn = dn;
-  }
-
-  public void setBindDnCredential(final String credential) {
-    bindDnCredential = credential;
-  }
-
-  public void setUsePasswordPolicy(final boolean b) {
-    usePasswordPolicy = b;
-  }
-
-  public void setUsePasswordExpiration(final boolean b) {
-    usePasswordExpiration = b;
-  }
-
-  public void setActiveDirectory(final boolean b) {
-    isActiveDirectory = b;
-  }
-
-  public void setFreeIPA(final boolean b) {
-    isFreeIPA = b;
-  }
-
-  public void setEDirectory(final boolean b) {
-    isEDirectory = b;
-  }
-
-  public void setAccountStateExpirationPeriod(@Nullable final Period period) {
-    accountStateExpirationPeriod = period;
-  }
-
-  public void setAccountStateWarningPeriod(@Nullable final Period period) {
-    accountStateWarningPeriod = period;
-  }
-
-  public void setAccountStateLoginFailures(final int loginFailures) {
-    accountStateLoginFailures = loginFailures;
-  }
-
-  /**
-   * Returns a new SslConfig object derived from the configured {@link #trustType}. Default uses JVM trust.
-   *
-   * @return new SslConfig
-   */
-  protected SslConfig createSslConfig() {
-    final SslConfig config = new SslConfig();
-    switch(trustType) {
-    case CERTIFICATE:
-      config.setCredentialConfig(trustCertificatesCredentialConfig);
-      break;
-    case KEYSTORE:
-      config.setCredentialConfig(truststoreCredentialConfig);
-      break;
-    case DISABLED:
-      config.setCredentialConfig(() -> { throw new GeneralSecurityException("SSL/startTLS is disabled"); });
-      break;
-    case JVM:
-    default:
-      break;
+    public void setBindDnCredential(final String credential) {
+        bindDnCredential = credential;
+    }
+
+    public void setUsePasswordPolicy(final boolean b) {
+        usePasswordPolicy = b;
+    }
+
+    public void setUsePasswordExpiration(final boolean b) {
+        usePasswordExpiration = b;
+    }
+
+    public void setActiveDirectory(final boolean b) {
+        isActiveDirectory = b;
+    }
+
+    public void setFreeIPA(final boolean b) {
+        isFreeIPA = b;
+    }
+
+    public void setEDirectory(final boolean b) {
+        isEDirectory = b;
+    }
+
+    public void setAccountStateExpirationPeriod(@Nullable final Period period) {
+        accountStateExpirationPeriod = period;
+    }
+
+    public void setAccountStateWarningPeriod(@Nullable final Period period) {
+        accountStateWarningPeriod = period;
+    }
+
+    public void setAccountStateLoginFailures(final int loginFailures) {
+        accountStateLoginFailures = loginFailures;
+    }
+
+    /**
+     * Returns a new SslConfig object derived from the configured
+     * {@link #trustType}. Default uses JVM trust.
+     *
+     * @return new SslConfig
+     */
+    protected SslConfig createSslConfig() {
+        final SslConfig config = new SslConfig();
+        switch (trustType) {
+        case CERTIFICATE:
+            config.setCredentialConfig(trustCertificatesCredentialConfig);
+            break;
+        case KEYSTORE:
+            config.setCredentialConfig(truststoreCredentialConfig);
+            break;
+        case DISABLED:
+            config.setCredentialConfig(() -> {
+                throw new GeneralSecurityException("SSL/startTLS is disabled");
+            });
+            break;
+        case JVM:
+        default:
+            break;
+        }
+
+        if (disableHostnameVerification) {
+            log.warn("LDAP Authenticator configured to bypass TLS hostname checking!");
+            config.setHostnameVerifier(new AllowAnyHostnameVerifier());
+        }
+        return config;
+    }
+
+    /**
+     * Returns a new ConnectionConfig without a connection initializer.
+     *
+     * @return new ConnectionConfig
+     */
+    protected ConnectionConfig createConnectionConfig() {
+        return createConnectionConfig(null);
+    }
+
+    /**
+     * Returns a new ConnectionConfig with the supplied connection initializer.
+     *
+     * @param initializer
+     *            to configure or null
+     *
+     * @return new ConnectionConfig
+     */
+    protected ConnectionConfig createConnectionConfig(@Nullable final ConnectionInitializer initializer) {
+        final ConnectionConfig config = new ConnectionConfig();
+        config.setLdapUrl(ldapUrl);
+        config.setUseStartTLS(useStartTLS);
+        config.setConnectTimeout(connectTimeout);
+        config.setResponseTimeout(responseTimeout);
+        switch (connectionStrategyType) {
+        case ROUND_ROBIN:
+            config.setConnectionStrategy(new RoundRobinConnectionStrategy());
+            break;
+        case RANDOM:
+            config.setConnectionStrategy(new RandomConnectionStrategy());
+            break;
+        case ACTIVE_PASSIVE:
+        default:
+            config.setConnectionStrategy(new ActivePassiveConnectionStrategy());
+            break;
+        }
+        config.setSslConfig(createSslConfig());
+        if (initializer != null) {
+            config.setConnectionInitializers(initializer);
+        }
+        return config;
+    }
+
+    /**
+     * 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 PooledConnectionFactory createPooledConnectionFactory(final String name, final ConnectionConfig config) {
+        return createPooledConnectionFactory(name, config,
+                SearchConnectionValidator.builder().period(validatePeriod).build());
+    }
+
+    /**
+     * Returns a new pooled connection factory using the supplied search validator.
+     *
+     * @param name
+     *            of the connection pool
+     * @param config
+     *            to assign to the pool
+     * @param validator
+     *            pool validator
+     *
+     * @return new blocking connection pool
+     */
+    protected PooledConnectionFactory createPooledConnectionFactory(final String name, final ConnectionConfig config,
+            final SearchConnectionValidator validator) {
+        return createPooledConnectionFactory(name, config, validator, null);
+    }
+
+    /**
+     * Returns a new pooled connection factory using the supplied search validator
+     * and passivator. Note that a {@link PassivatorType#BIND} uses the configured
+     * {@link #bindDn} and {@link #bindDnCredential}.
+     *
+     * @param name
+     *            of the connection pool
+     * @param config
+     *            to assign to the pool
+     * @param validator
+     *            pool validator
+     * @param passivator
+     *            pool passivator
+     *
+     * @return new blocking connection pool
+     */
+    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 createSearchConnectionValidator(final String baseDn, final String filter) {
+        final SearchRequest searchRequest = new SearchRequest();
+        searchRequest.setReturnAttributes("1.1");
+        searchRequest.setSearchScope(SearchScope.OBJECT);
+        searchRequest.setSizeLimit(1);
+        if (baseDn != null) {
+            searchRequest.setBaseDn(baseDn);
+        } else {
+            searchRequest.setBaseDn("");
+        }
+        final FilterTemplate searchFilter = new FilterTemplate();
+        if (filter != null) {
+            searchFilter.setFilter(filter);
+        } else {
+            searchFilter.setFilter("(objectClass=*)");
+        }
+        searchRequest.setFilter(searchFilter);
+        return SearchConnectionValidator.builder().request(searchRequest).period(validatePeriod).build();
+    }
+
+    protected ConnectionPassivator createConnectionPassivator(final PassivatorType type) {
+        switch (type) {
+        case BIND:
+            return new BindConnectionPassivator(new SimpleBindRequest(bindDn, new Credential(bindDnCredential)));
+        case ANONYMOUS_BIND:
+            return new BindConnectionPassivator();
+        case NONE:
+        default:
+            return null;
+        }
+    }
+
+    // Checkstyle: CyclomaticComplexity|MethodLength OFF
+    @Override
+    @Nonnull protected Authenticator createInstance() throws Exception {
+        final Authenticator authenticator = new Authenticator();
+        if (disablePooling) {
+            authenticator.setAuthenticationHandler(
+                    new SimpleBindAuthenticationHandler(new DefaultConnectionFactory(createConnectionConfig())));
+        } else {
+            authenticator.setAuthenticationHandler(new SimpleBindAuthenticationHandler(createPooledConnectionFactory(
+                    "bind-pool", createConnectionConfig(), createSearchConnectionValidator(validateDn, validateFilter),
+                    createConnectionPassivator(bindPoolPassivatorType))));
+        }
+        switch (authenticatorType) {
+        case BIND_SEARCH:
+            if (disablePooling) {
+                final TemplateSearchDnResolver bindSearchDnResolver = new TemplateSearchDnResolver(velocityEngine,
+                        userFilter);
+                bindSearchDnResolver.setBaseDn(baseDn);
+                bindSearchDnResolver.setSubtreeSearch(subtreeSearch);
+                bindSearchDnResolver.setConnectionFactory(new DefaultConnectionFactory(createConnectionConfig(
+                        new BindConnectionInitializer(bindDn, new Credential(bindDnCredential)))));
+                authenticator.setDnResolver(bindSearchDnResolver);
+            } else {
+                final TemplateSearchDnResolver bindSearchDnResolver = new TemplateSearchDnResolver(velocityEngine,
+                        userFilter);
+                bindSearchDnResolver.setBaseDn(baseDn);
+                bindSearchDnResolver.setSubtreeSearch(subtreeSearch);
+                bindSearchDnResolver.setConnectionFactory(createPooledConnectionFactory("dn-search-pool",
+                        createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential))),
+                        createSearchConnectionValidator(validateDn, validateFilter)));
+                authenticator.setDnResolver(bindSearchDnResolver);
+            }
+            authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
+            break;
+        case DIRECT:
+            authenticator.setDnResolver(new FormatDnResolver(dnFormat));
+            authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
+            break;
+        case AD:
+            authenticator.setDnResolver(new FormatDnResolver(dnFormat));
+            authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
+            authenticator.setResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler());
+            break;
+        case ANON_SEARCH:
+            if (disablePooling) {
+                final TemplateSearchDnResolver anonSearchDnResolver = new TemplateSearchDnResolver(velocityEngine,
+                        userFilter);
+                anonSearchDnResolver.setBaseDn(baseDn);
+                anonSearchDnResolver.setSubtreeSearch(subtreeSearch);
+                anonSearchDnResolver.setConnectionFactory(new DefaultConnectionFactory(createConnectionConfig()));
+                authenticator.setDnResolver(anonSearchDnResolver);
+            } else {
+                final TemplateSearchDnResolver anonSearchDnResolver = new TemplateSearchDnResolver(velocityEngine,
+                        userFilter);
+                anonSearchDnResolver.setBaseDn(baseDn);
+                anonSearchDnResolver.setSubtreeSearch(subtreeSearch);
+                anonSearchDnResolver.setConnectionFactory(createPooledConnectionFactory("dn-search-pool",
+                        createConnectionConfig(), createSearchConnectionValidator(validateDn, validateFilter)));
+                authenticator.setDnResolver(anonSearchDnResolver);
+            }
+            authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
+            break;
+        default:
+            break;
+        }
+
+        if (resolveEntryWithBindDn) {
+            if (disablePooling) {
+                final SearchEntryResolver searchEntryResolver = new SearchEntryResolver();
+                searchEntryResolver.setConnectionFactory(new DefaultConnectionFactory(createConnectionConfig(
+                        new BindConnectionInitializer(bindDn, new Credential(bindDnCredential)))));
+                authenticator.setEntryResolver(searchEntryResolver);
+            } else {
+                final SearchEntryResolver searchEntryResolver = new SearchEntryResolver();
+                searchEntryResolver.setConnectionFactory(createPooledConnectionFactory("entry-search-pool",
+                        createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential))),
+                        createSearchConnectionValidator(validateDn, validateFilter)));
+                authenticator.setEntryResolver(searchEntryResolver);
+            }
+        }
+
+        if (usePasswordPolicy) {
+            authenticator.setRequestHandlers(new PasswordPolicyAuthenticationRequestHandler());
+            authenticator.setResponseHandlers(new PasswordPolicyAuthenticationResponseHandler());
+        } else if (usePasswordExpiration) {
+            authenticator.setResponseHandlers(new PasswordExpirationAuthenticationResponseHandler());
+        } else if (isActiveDirectory) {
+            authenticator.setResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler(
+                    accountStateExpirationPeriod, accountStateWarningPeriod));
+        } else if (isEDirectory) {
+            authenticator.setResponseHandlers(new EDirectoryAuthenticationResponseHandler(accountStateWarningPeriod));
+        } else if (isFreeIPA) {
+            authenticator.setResponseHandlers(new FreeIPAAuthenticationResponseHandler(accountStateExpirationPeriod,
+                    accountStateWarningPeriod, accountStateLoginFailures));
+        }
+        log.debug("Created {} from {}", authenticator, this);
+        return authenticator;
+    }
+    // Checkstyle: CyclomaticComplexity|MethodLength ON
+
+    @Override
+    protected void destroyInstance(@Nullable final Authenticator instance) {
+        if (instance != null) {
+            instance.close();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this).add("authenticatorType", authenticatorType).add("trustType", trustType)
+                .add("connectionStrategyType", connectionStrategyType).add("ldapUrl", ldapUrl)
+                .add("useStartTLS", useStartTLS).add("disableHostnameVerification", disableHostnameVerification)
+                .add("connectTimeout", connectTimeout).add("responseTimeout", responseTimeout)
+                .add("trustCertificatesCredentialConfig", trustCertificatesCredentialConfig)
+                .add("truststoreCredentialConfig", truststoreCredentialConfig).add("disablePooling", disablePooling)
+                .add("blockWaitTime", blockWaitTime).add("minPoolSize", minPoolSize).add("maxPoolSize", maxPoolSize)
+                .add("validateOnCheckout", validateOnCheckout).add("validatePeriodically", validatePeriodically)
+                .add("validatePeriod", validatePeriod).add("validateDn", validateDn)
+                .add("validateFilter", validateFilter).add("bindPoolPassivatorType", bindPoolPassivatorType)
+                .add("prunePeriod", prunePeriod).add("idleTime", idleTime).add("dnFormat", dnFormat)
+                .add("baseDn", baseDn).add("userFilter", userFilter).add("subtreeSearch", subtreeSearch)
+                .add("resolveEntryOnFailure", resolveEntryOnFailure)
+                .add("resolveEntryWithBindDn", resolveEntryWithBindDn).add("velocityEngine", velocityEngine)
+                .add("bindDn", bindDn).add("bindDnCredential", bindDnCredential != null ? "suppressed" : null)
+                .add("usePasswordPolicy", usePasswordPolicy).add("usePasswordExpiration", usePasswordExpiration)
+                .add("isActiveDirectory", isActiveDirectory).add("isFreeIPA", isFreeIPA)
+                .add("isEDirectory", isEDirectory).add("accountStateExpirationPeriod", accountStateExpirationPeriod)
+                .add("accountStateWarningPeriod", accountStateWarningPeriod)
+                .add("accountStateLoginFailures", accountStateLoginFailures).toString();
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return Authenticator.class;
     }
-    
-    if (disableHostnameVerification) {
-        log.warn("LDAP Authenticator configured to bypass TLS hostname checking!");
-        config.setHostnameVerifier(new AllowAnyHostnameVerifier());
-    }
-    return config;
-  }
-
-  /**
-   * Returns a new ConnectionConfig without a connection initializer.
-   *
-   * @return  new ConnectionConfig
-   */
-  protected ConnectionConfig createConnectionConfig() {
-    return createConnectionConfig(null);
-  }
-
-  /**
-   * Returns a new ConnectionConfig with the supplied connection initializer.
-   *
-   * @param initializer to configure or null
-   *
-   * @return new ConnectionConfig
-   */
-  protected ConnectionConfig createConnectionConfig(@Nullable final ConnectionInitializer initializer) {
-    final ConnectionConfig config = new ConnectionConfig();
-    config.setLdapUrl(ldapUrl);
-    config.setUseStartTLS(useStartTLS);
-    config.setConnectTimeout(connectTimeout);
-    config.setResponseTimeout(responseTimeout);
-    switch (connectionStrategyType) {
-    case ROUND_ROBIN:
-      config.setConnectionStrategy(new RoundRobinConnectionStrategy());
-      break;
-    case RANDOM:
-      config.setConnectionStrategy(new RandomConnectionStrategy());
-      break;
-    case ACTIVE_PASSIVE:
-    default:
-      config.setConnectionStrategy(new ActivePassiveConnectionStrategy());
-      break;
-    }
-    config.setSslConfig(createSslConfig());
-    if (initializer != null) {
-      config.setConnectionInitializers(initializer);
-    }
-    return config;
-  }
-
-  /**
-   * 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 PooledConnectionFactory createPooledConnectionFactory(final String name, final ConnectionConfig config) {
-    return createPooledConnectionFactory(
-      name, config, SearchConnectionValidator.builder().period(validatePeriod).build());
-  }
-
-  /**
-   * Returns a new pooled connection factory using the supplied search validator.
-   *
-   * @param name of the connection pool
-   * @param config to assign to the pool
-   * @param validator pool validator
-   *
-   * @return new blocking connection pool
-   */
-  protected PooledConnectionFactory createPooledConnectionFactory(
-    final String name, final ConnectionConfig config, final SearchConnectionValidator validator) {
-    return createPooledConnectionFactory(name, config, validator, null);
-  }
-
-  /**
-   * Returns a new pooled connection factory using the supplied search validator and passivator. Note that a {@link
-   * PassivatorType#BIND} uses the configured {@link #bindDn} and {@link #bindDnCredential}.
-   *
-   * @param name of the connection pool
-   * @param config to assign to the pool
-   * @param validator pool validator
-   * @param passivator pool passivator
-   *
-   * @return new blocking connection pool
-   */
-  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 createSearchConnectionValidator(final String baseDn, final String filter) {
-    final SearchRequest searchRequest = new SearchRequest();
-    searchRequest.setReturnAttributes("1.1");
-    searchRequest.setSearchScope(SearchScope.OBJECT);
-    searchRequest.setSizeLimit(1);
-    if (baseDn != null) {
-      searchRequest.setBaseDn(baseDn);
-    } else {
-      searchRequest.setBaseDn("");
-    }
-    final FilterTemplate searchFilter = new FilterTemplate();
-    if (filter != null) {
-      searchFilter.setFilter(filter);
-    } else {
-      searchFilter.setFilter("(objectClass=*)");
-    }
-    searchRequest.setFilter(searchFilter);
-    return SearchConnectionValidator.builder().request(searchRequest).period(validatePeriod).build();
-  }
-
-  protected ConnectionPassivator createConnectionPassivator(final PassivatorType type) {
-    switch(type) {
-      case BIND:
-        return new BindConnectionPassivator(new SimpleBindRequest(bindDn, new Credential(bindDnCredential)));
-      case ANONYMOUS_BIND:
-        return new BindConnectionPassivator();
-      case NONE:
-      default:
-        return null;
-    }
-  }
-
-// Checkstyle: CyclomaticComplexity|MethodLength OFF
-  @Override
-  protected Authenticator createInstance() throws Exception {
-    final Authenticator authenticator = new Authenticator();
-    if (disablePooling) {
-      authenticator.setAuthenticationHandler(
-        new SimpleBindAuthenticationHandler(new DefaultConnectionFactory(createConnectionConfig())));
-    } else {
-      authenticator.setAuthenticationHandler(
-        new SimpleBindAuthenticationHandler(
-          createPooledConnectionFactory(
-            "bind-pool",
-            createConnectionConfig(),
-            createSearchConnectionValidator(validateDn, validateFilter),
-            createConnectionPassivator(bindPoolPassivatorType))));
-    }
-    switch(authenticatorType) {
-    case BIND_SEARCH:
-      if (disablePooling) {
-        final TemplateSearchDnResolver bindSearchDnResolver =
-          new TemplateSearchDnResolver(velocityEngine, userFilter);
-        bindSearchDnResolver.setBaseDn(baseDn);
-        bindSearchDnResolver.setSubtreeSearch(subtreeSearch);
-        bindSearchDnResolver.setConnectionFactory(
-          new DefaultConnectionFactory(
-            createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential)))));
-        authenticator.setDnResolver(bindSearchDnResolver);
-      } else {
-        final TemplateSearchDnResolver bindSearchDnResolver =
-          new TemplateSearchDnResolver(velocityEngine, userFilter);
-        bindSearchDnResolver.setBaseDn(baseDn);
-        bindSearchDnResolver.setSubtreeSearch(subtreeSearch);
-        bindSearchDnResolver.setConnectionFactory(
-          createPooledConnectionFactory(
-            "dn-search-pool",
-            createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential))),
-            createSearchConnectionValidator(validateDn, validateFilter)));
-        authenticator.setDnResolver(bindSearchDnResolver);
-      }
-      authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
-      break;
-    case DIRECT:
-      authenticator.setDnResolver(new FormatDnResolver(dnFormat));
-      authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
-      break;
-    case AD:
-      authenticator.setDnResolver(new FormatDnResolver(dnFormat));
-      authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
-      authenticator.setResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler());
-      break;
-    case ANON_SEARCH:
-      if (disablePooling) {
-        final TemplateSearchDnResolver anonSearchDnResolver =
-          new TemplateSearchDnResolver(velocityEngine, userFilter);
-        anonSearchDnResolver.setBaseDn(baseDn);
-        anonSearchDnResolver.setSubtreeSearch(subtreeSearch);
-        anonSearchDnResolver.setConnectionFactory(new DefaultConnectionFactory(createConnectionConfig()));
-        authenticator.setDnResolver(anonSearchDnResolver);
-      } else {
-        final TemplateSearchDnResolver anonSearchDnResolver =
-          new TemplateSearchDnResolver(velocityEngine, userFilter);
-        anonSearchDnResolver.setBaseDn(baseDn);
-        anonSearchDnResolver.setSubtreeSearch(subtreeSearch);
-        anonSearchDnResolver.setConnectionFactory(
-          createPooledConnectionFactory(
-            "dn-search-pool",
-            createConnectionConfig(),
-            createSearchConnectionValidator(validateDn, validateFilter)));
-        authenticator.setDnResolver(anonSearchDnResolver);
-      }
-      authenticator.setResolveEntryOnFailure(resolveEntryOnFailure);
-      break;
-    default:
-      break;
-    }
-
-    if (resolveEntryWithBindDn) {
-      if (disablePooling) {
-        final SearchEntryResolver searchEntryResolver = new SearchEntryResolver();
-        searchEntryResolver.setConnectionFactory(
-          new DefaultConnectionFactory(
-            createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential)))));
-        authenticator.setEntryResolver(searchEntryResolver);
-      } else {
-        final SearchEntryResolver searchEntryResolver = new SearchEntryResolver();
-        searchEntryResolver.setConnectionFactory(
-          createPooledConnectionFactory(
-            "entry-search-pool",
-            createConnectionConfig(new BindConnectionInitializer(bindDn, new Credential(bindDnCredential))),
-            createSearchConnectionValidator(validateDn, validateFilter)));
-        authenticator.setEntryResolver(searchEntryResolver);
-      }
-    }
-
-    if (usePasswordPolicy) {
-      authenticator.setRequestHandlers(new PasswordPolicyAuthenticationRequestHandler());
-      authenticator.setResponseHandlers(new PasswordPolicyAuthenticationResponseHandler());
-    } else if (usePasswordExpiration) {
-      authenticator.setResponseHandlers(new PasswordExpirationAuthenticationResponseHandler());
-    } else if (isActiveDirectory) {
-      authenticator.setResponseHandlers(new ActiveDirectoryAuthenticationResponseHandler(accountStateExpirationPeriod, accountStateWarningPeriod));
-    } else if (isEDirectory) {
-      authenticator.setResponseHandlers(new EDirectoryAuthenticationResponseHandler(accountStateWarningPeriod));
-    } else if (isFreeIPA) {
-      authenticator.setResponseHandlers(new FreeIPAAuthenticationResponseHandler(accountStateExpirationPeriod, accountStateWarningPeriod, accountStateLoginFailures));
-    }
-    log.debug("Created {} from {}", authenticator, this);
-    return authenticator;
-  }
-// Checkstyle: CyclomaticComplexity|MethodLength ON
-
-  @Override
-  protected void destroyInstance(final Authenticator instance) {
-    if (instance != null) {
-      instance.close();
-    }
-  }
-
-  @Override
-  public String toString() {
-    return MoreObjects.toStringHelper(this)
-            .add("authenticatorType", authenticatorType)
-            .add("trustType", trustType)
-            .add("connectionStrategyType", connectionStrategyType)
-            .add("ldapUrl", ldapUrl)
-            .add("useStartTLS", useStartTLS)
-            .add("disableHostnameVerification", disableHostnameVerification)
-            .add("connectTimeout", connectTimeout)
-            .add("responseTimeout", responseTimeout)
-            .add("trustCertificatesCredentialConfig", trustCertificatesCredentialConfig)
-            .add("truststoreCredentialConfig", truststoreCredentialConfig)
-            .add("disablePooling", disablePooling)
-            .add("blockWaitTime", blockWaitTime)
-            .add("minPoolSize", minPoolSize)
-            .add("maxPoolSize", maxPoolSize)
-            .add("validateOnCheckout", validateOnCheckout)
-            .add("validatePeriodically", validatePeriodically)
-            .add("validatePeriod", validatePeriod)
-            .add("validateDn", validateDn)
-            .add("validateFilter", validateFilter)
-            .add("bindPoolPassivatorType", bindPoolPassivatorType)
-            .add("prunePeriod", prunePeriod)
-            .add("idleTime", idleTime)
-            .add("dnFormat", dnFormat)
-            .add("baseDn", baseDn)
-            .add("userFilter", userFilter)
-            .add("subtreeSearch", subtreeSearch)
-            .add("resolveEntryOnFailure", resolveEntryOnFailure)
-            .add("resolveEntryWithBindDn", resolveEntryWithBindDn)
-            .add("velocityEngine", velocityEngine)
-            .add("bindDn", bindDn)
-            .add("bindDnCredential", bindDnCredential != null ? "suppressed" : null)
-            .add("usePasswordPolicy", usePasswordPolicy)
-            .add("usePasswordExpiration", usePasswordExpiration)
-            .add("isActiveDirectory", isActiveDirectory)
-            .add("isFreeIPA", isFreeIPA)
-            .add("isEDirectory", isEDirectory)
-            .add("accountStateExpirationPeriod", accountStateExpirationPeriod)
-            .add("accountStateWarningPeriod", accountStateWarningPeriod)
-            .add("accountStateLoginFailures", accountStateLoginFailures)
-            .toString();
-  }
-
-  @Override
-  public Class<?> getObjectType() {
-    return Authenticator.class;
-  }
 }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/navigate/ForceAuthnProfileConfigPredicate.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/navigate/ForceAuthnProfileConfigPredicate.java
index c59f6a5b0..42bef0869 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/navigate/ForceAuthnProfileConfigPredicate.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/navigate/ForceAuthnProfileConfigPredicate.java
@@ -20,6 +20,7 @@ package net.shibboleth.idp.authn.config.navigate;
 import javax.annotation.Nullable;
 
 import net.shibboleth.idp.authn.config.AuthenticationProfileConfiguration;
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.idp.profile.logic.AbstractRelyingPartyPredicate;
 
@@ -37,8 +38,11 @@ public class ForceAuthnProfileConfigPredicate extends AbstractRelyingPartyPredic
     public boolean test(@Nullable final ProfileRequestContext input) {
         
         final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
-        if (rpc != null && rpc.getProfileConfig() instanceof AuthenticationProfileConfiguration) {
-            return ((AuthenticationProfileConfiguration) rpc.getProfileConfig()).isForceAuthn(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof AuthenticationProfileConfiguration) {
+                return ((AuthenticationProfileConfiguration) pc).isForceAuthn(input);
+            }
         }
         
         return false;
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
index 37125fa68..2b6d4293e 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
@@ -23,7 +23,6 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -46,6 +45,7 @@ import net.shibboleth.shared.annotation.constraint.Live;
 import net.shibboleth.shared.annotation.constraint.NonNegative;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.StringSupport;
 
@@ -94,7 +94,7 @@ public final class AuthenticationContext extends BaseContext {
     @Nullable @NonNegative private Integer proxyCount;
 
     /** Allowable proxied sources of authority. */
-    @Nullable @NonnullElements private Set<String> proxiableAuthorities;
+    @Nonnull @NonnullElements private Set<String> proxiableAuthorities;
     
     /** Lookup strategy for a fixed event to return from validators for testing. */
     @Nullable private Function<ProfileRequestContext,String> fixedEventLookupStrategy;
@@ -265,7 +265,7 @@ public final class AuthenticationContext extends BaseContext {
         evalRegistry = registry;
         
         final RequestedPrincipalContext rpCtx = getSubcontext(RequestedPrincipalContext.class);
-        if (rpCtx != null) {
+        if (rpCtx != null && registry != null) {
             rpCtx.setPrincipalEvalPredicateFactoryRegistry(registry);
         }
         
@@ -703,7 +703,7 @@ public final class AuthenticationContext extends BaseContext {
         }
         // No requirements so anything is acceptable.
         if (principal instanceof ProxyAuthenticationPrincipal) {
-            return checkProxyRestrictions(Collections.singletonList((ProxyAuthenticationPrincipal) principal));
+            return checkProxyRestrictions(CollectionSupport.singletonList((ProxyAuthenticationPrincipal) principal));
         }
         return true;
     }
@@ -726,7 +726,7 @@ public final class AuthenticationContext extends BaseContext {
             @Nonnull @NotEmpty final String className, @Nonnull @NotEmpty final String principal,
             final boolean replace) throws Exception {
         
-        return addRequestedPrincipalContext(operator, className, Collections.singletonList(principal), replace);
+        return addRequestedPrincipalContext(operator, className, CollectionSupport.singletonList(principal), replace);
     }
     
     /**
@@ -772,7 +772,7 @@ public final class AuthenticationContext extends BaseContext {
     public boolean addRequestedPrincipalContext(@Nonnull @NotEmpty final String operator,
             @Nonnull final Principal principal, final boolean replace) {
         
-        return addRequestedPrincipalContext(operator, Collections.singletonList(principal), replace);
+        return addRequestedPrincipalContext(operator, CollectionSupport.singletonList(principal), replace);
     }
     
     /**
@@ -796,9 +796,12 @@ public final class AuthenticationContext extends BaseContext {
         
         rpCtx = new RequestedPrincipalContext();
         rpCtx.setOperator(operator)
-            .setPrincipalEvalPredicateFactoryRegistry(evalRegistry)
             .setRequestedPrincipals(principals);
         
+        if (evalRegistry != null) {
+            rpCtx.setPrincipalEvalPredicateFactoryRegistry(evalRegistry);
+        }
+        
         addSubcontext(rpCtx, true);
         
         return true;
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationWarningContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationWarningContext.java
index a0130563e..13a39bd83 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationWarningContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationWarningContext.java
@@ -42,7 +42,7 @@ import net.shibboleth.shared.annotation.constraint.NotEmpty;
 public final class AuthenticationWarningContext extends BaseContext {
 
     /** Warning conditions detected through classified warning messages. */
-    private Collection<String> classifiedWarnings;
+    @Nonnull @NonnullElements private Collection<String> classifiedWarnings;
     
     /** Constructor. */
     public AuthenticationWarningContext() {
@@ -67,4 +67,5 @@ public final class AuthenticationWarningContext extends BaseContext {
     public boolean isClassifiedWarning(@Nonnull @NotEmpty final String warning) {
         return classifiedWarnings.contains(warning);
     }
+
 }
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/LDAPResponseContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/LDAPResponseContext.java
index 12dd8f59a..dd6020798 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/LDAPResponseContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/LDAPResponseContext.java
@@ -64,8 +64,11 @@ public final class LDAPResponseContext extends BaseContext {
      * @return  true if account state warnings exist
      */
     public boolean hasAccountStateWarning() {
-        final AccountState state = authenticationResponse.getAccountState();
-        return state != null ? state.getWarning() != null : false;
+        if (authenticationResponse != null) {
+            final AccountState state = authenticationResponse.getAccountState();
+            return state != null ? state.getWarning() != null : false;
+        }
+        return false;
     }
 
     /**
@@ -74,8 +77,11 @@ public final class LDAPResponseContext extends BaseContext {
      * @return  true if account state errors exist
      */
     public boolean hasAccountStateError() {
-        final AccountState state = authenticationResponse.getAccountState();
-        return state != null ? state.getError() != null : false;
+        if (authenticationResponse != null) {
+            final AccountState state = authenticationResponse.getAccountState();
+            return state != null ? state.getError() != null : false;
+        }
+        return false;
     }
 
     /** {@inheritDoc} */
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java
index 3be3c1476..13dad3913 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/MultiFactorAuthenticationContext.java
@@ -185,6 +185,7 @@ public final class MultiFactorAuthenticationContext extends BaseContext {
         final AuthenticationContext authnContext = (AuthenticationContext) getParent();
         if (authnContext != null) {
             for (final AuthenticationResult result : activeResults.values()) {
+                assert result != null;
                 // Only include Principals from fresh results or when forced authn is off.
                 if (!(authnContext.isForceAuthn() && result.isPreviousResult())) {
                     if (authnContext.isAcceptable(result)) {
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/PreferredPrincipalContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/PreferredPrincipalContext.java
index b35d47590..f5872d5c3 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/PreferredPrincipalContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/PreferredPrincipalContext.java
@@ -28,6 +28,7 @@ import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 
 import org.opensaml.messaging.context.BaseContext;
@@ -53,7 +54,7 @@ public final class PreferredPrincipalContext extends BaseContext {
     
     /** Constructor. */
     public PreferredPrincipalContext() {
-        preferredPrincipals = Collections.emptyList();
+        preferredPrincipals = CollectionSupport.emptyList();
     }
 
     /**
@@ -75,7 +76,8 @@ public final class PreferredPrincipalContext extends BaseContext {
     @Nonnull public PreferredPrincipalContext setPreferredPrincipals(
             @Nonnull @NonnullElements final List<Principal> principals) {
         
-        preferredPrincipals = List.copyOf(Constraint.isNotNull(principals, "Principal list cannot be null"));
+        preferredPrincipals = CollectionSupport.copyToList(
+                Constraint.isNotNull(principals, "Principal list cannot be null"));
         return this;
     }
         
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/RequestedPrincipalContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/RequestedPrincipalContext.java
index 04d113391..df21df33d 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/RequestedPrincipalContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/RequestedPrincipalContext.java
@@ -19,7 +19,6 @@ package net.shibboleth.idp.authn.context;
 
 import java.security.Principal;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -35,6 +34,7 @@ import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.StringSupport;
 
@@ -77,7 +77,7 @@ public final class RequestedPrincipalContext extends BaseContext {
     /** Constructor. */
     public RequestedPrincipalContext() {
         evalRegistry = new PrincipalEvalPredicateFactoryRegistry();
-        requestedPrincipals = Collections.emptyList();
+        requestedPrincipals = CollectionSupport.emptyList();
     }
 
     /**
@@ -148,7 +148,8 @@ public final class RequestedPrincipalContext extends BaseContext {
     @Nonnull public RequestedPrincipalContext setRequestedPrincipals(
             @Nonnull @NonnullElements final List<Principal> principals) {
         
-        requestedPrincipals = List.copyOf(Constraint.isNotNull(principals, "Principal list cannot be null"));
+        requestedPrincipals =
+                CollectionSupport.copyToList(Constraint.isNotNull(principals, "Principal list cannot be null"));
         return this;
     }
     
@@ -183,8 +184,9 @@ public final class RequestedPrincipalContext extends BaseContext {
      */
     @Nullable public PrincipalEvalPredicate getPredicate(@Nonnull final Principal principal) {
         
-        if (operatorString != null) {
-            final PrincipalEvalPredicateFactory factory = evalRegistry.lookup(principal.getClass(), operatorString);
+        final String op = getOperator();
+        if (op != null) {
+            final PrincipalEvalPredicateFactory factory = evalRegistry.lookup(principal.getClass(), op);
             return factory != null ? factory.getPredicate(principal) : null;
         }
         return null;
@@ -202,6 +204,7 @@ public final class RequestedPrincipalContext extends BaseContext {
      */
     public boolean isAcceptable(@Nonnull final PrincipalSupportingComponent component) {
         for (final Principal requestedPrincipal : requestedPrincipals) {
+            assert requestedPrincipal != null;
             final PrincipalEvalPredicate predicate = getPredicate(requestedPrincipal);
             if (predicate != null) {
                 if (predicate.test(component)) {
@@ -226,7 +229,7 @@ public final class RequestedPrincipalContext extends BaseContext {
      */
     public boolean isAcceptable(@Nonnull @NonnullElements final Collection<Principal> principals) {
         return isAcceptable(new PrincipalSupportingComponent() {
-            public <T extends Principal> Set<T> getSupportedPrincipals(final Class<T> c) {
+            @Nonnull public <T extends Principal> Set<T> getSupportedPrincipals(@Nonnull final Class<T> c) {
                 final HashSet<T> set = new HashSet<>();
                 for (final Principal p : principals) {
                     if (c.isAssignableFrom(p.getClass())) {
@@ -249,11 +252,11 @@ public final class RequestedPrincipalContext extends BaseContext {
      */
     public <T extends Principal> boolean isAcceptable(@Nonnull final T principal) {
         return isAcceptable(new PrincipalSupportingComponent() {
-            public <TT extends Principal> Set<TT> getSupportedPrincipals(final Class<TT> c) {
+            @Nonnull public <TT extends Principal> Set<TT> getSupportedPrincipals(@Nonnull final Class<TT> c) {
                 if (c.isAssignableFrom(principal.getClass())) {
-                    return Collections.singleton(c.cast(principal));
+                    return CollectionSupport.singleton(c.cast(principal));
                 }
-                return Collections.emptySet();
+                return CollectionSupport.emptySet();
             }
         });
     }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/PreviousResultLookupFunction.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/PreviousResultLookupFunction.java
index c2acf6ba0..8a0fc24b4 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/PreviousResultLookupFunction.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/PreviousResultLookupFunction.java
@@ -19,6 +19,7 @@ package net.shibboleth.idp.authn.context.navigate;
 
 import javax.annotation.Nullable;
 
+import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 
 import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
@@ -31,8 +32,11 @@ public class PreviousResultLookupFunction implements ContextDataLookupFunction<A
 
     /** {@inheritDoc} */
     @Nullable public Boolean apply(@Nullable final AuthenticationContext input) {
-        if (input != null && input.getAuthenticationResult() != null) {
-            return input.getAuthenticationResult().isPreviousResult();
+        if (input != null) {
+            final AuthenticationResult result = input.getAuthenticationResult();
+            if (result != null) {
+                return result.isPreviousResult();
+            }
         }
         return null;
     }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/DuoAuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/DuoAuthenticationContext.java
index cd8e35457..5a7648ba1 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/DuoAuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/DuoAuthenticationContext.java
@@ -56,7 +56,7 @@ public final class DuoAuthenticationContext extends BaseContext {
     @Nullable private String duoPasscode;
     
     /** PushInfo data. */
-    @Nullable private Map<String,String> pushInfo;
+    @Nonnull private Map<String,String> pushInfo;
 
     /** Constructor. */
     public DuoAuthenticationContext() {
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/GenericPrincipalSerializer.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/GenericPrincipalSerializer.java
index 5b0d4cab6..62d14f574 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/GenericPrincipalSerializer.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/GenericPrincipalSerializer.java
@@ -41,7 +41,6 @@ import javax.json.JsonValue;
 import javax.json.stream.JsonGenerator;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.BiMap;
@@ -52,6 +51,7 @@ import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * Principal serializer for arbitrary principal types.
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalEvalPredicateFactoryRegistry.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalEvalPredicateFactoryRegistry.java
index 3030618a3..68304542d 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalEvalPredicateFactoryRegistry.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalEvalPredicateFactoryRegistry.java
@@ -30,10 +30,11 @@ import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -73,20 +74,7 @@ public final class PrincipalEvalPredicateFactoryRegistry {
             registrations.forEach(r -> registry.put(r.getTypeAndOperator(), r.getPredicateFactory()));
         }
     }
-    
-    /**
-     * Constructor.
-     * 
-     * @param fromMap  map to populate registry with
-     * 
-     * @deprecated
-     */
-    @Deprecated(since="4.1.0", forRemoval=true)
-    public PrincipalEvalPredicateFactoryRegistry(@Nonnull @NonnullElements @ParameterName(name="fromMap") final
-            Map<Pair<Class<? extends Principal>, String>, PrincipalEvalPredicateFactory> fromMap) {
-        registry = new ConcurrentHashMap<>(Constraint.isNotNull(fromMap, "Source map cannot be null"));
-    }
-    
+        
     /**
      * Add registrations from a map, overwriting any previously matching entries.
      * 
@@ -99,8 +87,11 @@ public final class PrincipalEvalPredicateFactoryRegistry {
         if (fromMap != null) {
             fromMap.entrySet().forEach(entry -> {
                 if (registry.containsKey(entry.getKey())) {
-                    log.info("Replacing auto-wired entry for principal type '{}' and operator '{}'",
-                            entry.getKey().getFirst().getName(), entry.getKey().getSecond());
+                    if (log.isInfoEnabled()) {
+                        final Class<? extends Principal> claz = entry.getKey().getFirst();
+                        log.info("Replacing auto-wired entry for principal type '{}' and operator '{}'",
+                                claz != null ? claz.getName() : "(null)", entry.getKey().getSecond());
+                    }
                 }
                 registry.put(entry.getKey(), entry.getValue());
             });
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalServiceManager.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalServiceManager.java
index 11b2a7735..aef4a17c8 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalServiceManager.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/PrincipalServiceManager.java
@@ -19,7 +19,6 @@ package net.shibboleth.idp.authn.principal;
 
 import java.security.Principal;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -28,7 +27,7 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
 import org.springframework.beans.factory.annotation.Autowired;
 
 import net.shibboleth.shared.annotation.ParameterName;
@@ -36,6 +35,8 @@ import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * Manages and exposes instances of the {@link PrincipalService} interface.
@@ -69,8 +70,8 @@ public class PrincipalServiceManager {
                 idIndexedMap.put(ps.getId(), ps);
             });
         } else {
-            classIndexedMap = Collections.emptyMap();
-            idIndexedMap = Collections.emptyMap();
+            classIndexedMap = CollectionSupport.emptyMap();
+            idIndexedMap = CollectionSupport.emptyMap();
         }
     }
     
@@ -80,7 +81,7 @@ public class PrincipalServiceManager {
      * @return all registered services
      */
     @Nonnull @NonnullElements @NotLive @Unmodifiable public Collection<PrincipalService<?>> all() {
-        return List.copyOf(classIndexedMap.values());
+        return CollectionSupport.copyToList(classIndexedMap.values());
     }
 
     /**
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/ProxyAuthenticationPrincipal.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/ProxyAuthenticationPrincipal.java
index e81ccea4c..485aedf81 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/ProxyAuthenticationPrincipal.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/ProxyAuthenticationPrincipal.java
@@ -32,6 +32,7 @@ import javax.annotation.Nullable;
 import org.opensaml.profile.context.ProfileRequestContext;
 
 import net.shibboleth.idp.authn.config.AuthenticationProfileConfiguration;
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.shared.annotation.constraint.Live;
 import net.shibboleth.shared.annotation.constraint.NonNegative;
@@ -136,11 +137,17 @@ public class ProxyAuthenticationPrincipal implements Principal, Predicate<Profil
         
         // Check for local flow as relying party.
         final RelyingPartyContext rpCtx = input != null ? input.getSubcontext(RelyingPartyContext.class) : null;
-        if (rpCtx == null || !(rpCtx.getProfileConfig() instanceof AuthenticationProfileConfiguration) ||
-                ((AuthenticationProfileConfiguration) rpCtx.getProfileConfig()).isLocal()) {
+        if (rpCtx == null) {
             return true;
         }
-        
+
+        final ProfileConfiguration pc = rpCtx.getProfileConfig();
+        if (!(pc instanceof AuthenticationProfileConfiguration)) {
+            return true;
+        } else if (((AuthenticationProfileConfiguration) pc).isLocal()) {
+            return true;
+        }
+
         if (proxyCount != null && proxyCount == 0) {
             return false;
         } else if (rpCtx.getRelyingPartyId() != null && !audiences.isEmpty() &&
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SealedPrincipalSerializer.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SealedPrincipalSerializer.java
index a0a2f2669..1ee324a11 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SealedPrincipalSerializer.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SealedPrincipalSerializer.java
@@ -24,12 +24,12 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Strings;
 
 import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.security.DataSealer;
 import net.shibboleth.shared.security.DataSealerException;
 
@@ -79,6 +79,7 @@ public class SealedPrincipalSerializer<T extends Principal> extends SimplePrinci
     /** {@inheritDoc} */
     @Override
     public boolean supports(@Nonnull final Principal principal) {
+        checkComponentActive();
         if (!super.supports(principal)) {
             return false;
         } else if (sealer == null) {
@@ -91,7 +92,7 @@ public class SealedPrincipalSerializer<T extends Principal> extends SimplePrinci
     /** {@inheritDoc} */
     @Override
     public boolean supports(@Nonnull @NotEmpty final String value) {
-
+        checkComponentActive();
         if (!super.supports(value)) {
             return false;
         } else if (sealer == null) {
@@ -103,8 +104,13 @@ public class SealedPrincipalSerializer<T extends Principal> extends SimplePrinci
     
     /** {@inheritDoc} */
     @Override
-    protected String getName(@Nonnull final Principal principal) throws IOException {
+    @Nonnull protected String getName(@Nonnull final Principal principal) throws IOException {
+        checkComponentActive();
         try {
+            if (sealer == null) {
+                throw new IOException("No DataSealer was provided, unable to support serialization");
+            }
+            assert sealer != null;
             return sealer.wrap(super.getName(principal));
         } catch (final DataSealerException e) {
             throw new IOException(e);
@@ -113,9 +119,15 @@ public class SealedPrincipalSerializer<T extends Principal> extends SimplePrinci
 
     /** {@inheritDoc} */
     @Override
-    protected String getName(@Nullable final String serializedName) throws IOException {
+    @Nullable protected String getName(@Nullable final String serializedName) throws IOException {
+        checkComponentActive();
         if (!Strings.isNullOrEmpty(serializedName)) {
             try {
+                if (sealer == null) {
+                    throw new IOException("No DataSealer was provided, unable to support serialization");
+                }
+                assert sealer != null;
+                assert serializedName != null;
                 return sealer.unwrap(serializedName);
             } catch (final DataSealerException e) {
                 throw new IOException(e);
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SimplePrincipalSerializer.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SimplePrincipalSerializer.java
index ff478bc7b..7002b080a 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SimplePrincipalSerializer.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/principal/SimplePrincipalSerializer.java
@@ -34,9 +34,6 @@ import javax.json.JsonString;
 import javax.json.JsonStructure;
 import javax.json.stream.JsonGenerator;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.common.base.Strings;
 
 import net.shibboleth.shared.annotation.ParameterName;
@@ -54,9 +51,6 @@ import net.shibboleth.shared.primitive.StringSupport;
 @ThreadSafe
 public class SimplePrincipalSerializer<T extends Principal> extends AbstractPrincipalSerializer<String> {
 
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(SimplePrincipalSerializer.class);
-
     /** Principal type. */
     @Nonnull private final Class<T> principalType;
 
diff --git a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationResultTest.java b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationResultTest.java
index 5d59bbb7e..34d1eee00 100644
--- a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationResultTest.java
+++ b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationResultTest.java
@@ -19,9 +19,6 @@ package net.shibboleth.idp.authn;
 
 import java.time.Instant;
 
-import javax.security.auth.Subject;
-
-import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.authn.principal.UsernamePrincipal;
 import net.shibboleth.shared.logic.ConstraintViolationException;
 
@@ -47,13 +44,6 @@ public class AuthenticationResultTest {
         
         Assert.assertTrue(event.getSubject().getPrincipals(UsernamePrincipal.class).contains(new UsernamePrincipal("bob")));
 
-        try {
-            new AuthenticationResult(null, new UsernamePrincipal("bob"));
-            Assert.fail();
-        } catch (ConstraintViolationException e) {
-
-        }
-
         try {
             new AuthenticationResult("", new UsernamePrincipal("bob"));
             Assert.fail();
@@ -67,13 +57,6 @@ public class AuthenticationResultTest {
         } catch (ConstraintViolationException e) {
 
         }
-
-        try {
-            new AuthenticationResult("test", (Subject) null);
-            Assert.fail();
-        } catch (ConstraintViolationException e) {
-
-        }
     }
 
 }
diff --git a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/principal/UsernamePrincipalTest.java b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/principal/UsernamePrincipalTest.java
index 95945e388..db98700fb 100644
--- a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/principal/UsernamePrincipalTest.java
+++ b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/principal/UsernamePrincipalTest.java
@@ -32,13 +32,6 @@ public class UsernamePrincipalTest {
         UsernamePrincipal principal = new UsernamePrincipal("bob");
         Assert.assertEquals(principal.getName(), "bob");
 
-        try {
-            new UsernamePrincipal(null);
-            Assert.fail();
-        } catch (ConstraintViolationException e) {
-
-        }
-
         try {
             new UsernamePrincipal("");
             Assert.fail();

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


More information about the commits mailing list