[java-identity-provider] branch master updated: IDP-1391 - Add a service layer for password validators.
Scott Cantor
cantor.2 at osu.edu
Thu Aug 8 10:34:19 EDT 2019
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=9e4b294e97ca9d39182636b308b9310abb6a47d5
The following commit(s) were added to refs/heads/master by this push:
new 9e4b294 IDP-1391 - Add a service layer for password validators.
9e4b294 is described below
commit 9e4b294e97ca9d39182636b308b9310abb6a47d5
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Aug 8 10:34:15 2019 -0400
IDP-1391 - Add a service layer for password validators.
Redesign credential validation with new APIs.
Convert existing behavior and internal config.
---
...stractUsernamePasswordCredentialValidator.java} | 239 +++++++++---------
.../shibboleth/idp/authn/CredentialValidator.java | 122 +++++++++
...ainstJAAS.java => JAASCredentialValidator.java} | 194 +++++++-------
...beros.java => KerberosCredentialValidator.java} | 142 ++++++-----
...ainstLDAP.java => LDAPCredentialValidator.java} | 153 +++++------
.../idp/authn/impl/ValidateCredentials.java | 279 +++++++++++++++++++++
.../ValidateUsernamePasswordAgainstJAASTest.java | 100 +++++---
.../ValidateUsernamePasswordAgainstLDAPTest.java | 100 ++++++--
.../system/flows/authn/password-authn-beans.xml | 80 +++---
.../system/flows/authn/password-authn-flow.xml | 2 +-
10 files changed, 938 insertions(+), 473 deletions(-)
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordValidationAction.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordCredentialValidator.java
similarity index 51%
rename from idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordValidationAction.java
rename to idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordCredentialValidator.java
index 5b44018..21b79e2 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordValidationAction.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractUsernamePasswordCredentialValidator.java
@@ -17,48 +17,46 @@
package net.shibboleth.idp.authn;
+import java.util.function.Function;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.UsernamePasswordContext;
import net.shibboleth.idp.authn.principal.PasswordPrincipal;
import net.shibboleth.idp.authn.principal.UsernamePrincipal;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiedInitializableComponent;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * An abstract action that checks for a {@link UsernamePasswordContext} and produces an
- * {@link net.shibboleth.idp.authn.AuthenticationResult} based on that identity by invoking
- * a subclass method.
- *
- * <p>Lockout behavior can be enabled by injecting an {@link AccountLockoutManager}</p>
- *
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @event {@link AuthnEventIds#ACCOUNT_LOCKED}
- * @post If AuthenticationContext.getSubcontext(UsernamePasswordContext.class) != null, then
- * an {@link net.shibboleth.idp.authn.AuthenticationResult} is saved to the {@link AuthenticationContext} on a
- * successful login. On a failed login, the
- * {@link AbstractValidationAction#handleError(ProfileRequestContext, AuthenticationContext, Exception, String)}
- * method is called.
+ * An abstract {@link CredentialValidator} that checks for a {@link UsernamePasswordContext} and delegates
+ * to subclasses to produce an {@link net.shibboleth.idp.authn.AuthenticationResult}.
+ *
+ * @since 4.0.0
*/
-public abstract class AbstractUsernamePasswordValidationAction extends AbstractValidationAction {
+public abstract class AbstractUsernamePasswordCredentialValidator extends AbstractIdentifiedInitializableComponent
+ implements CredentialValidator {
/** Default prefix for metrics. */
@Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.password";
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractUsernamePasswordValidationAction.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractUsernamePasswordCredentialValidator.class);
+ /** Lookup strategy for UP context. */
+ @Nonnull private Function<AuthenticationContext,UsernamePasswordContext> usernamePasswordContextLookupStrategy;
+
/** Whether to save the password in the Java Subject's private credentials. */
private boolean savePasswordToCredentialSet;
@@ -68,16 +66,32 @@ public abstract class AbstractUsernamePasswordValidationAction extends AbstractV
/** A regular expression to apply for acceptance testing. */
@Nullable private Pattern matchExpression;
- /** Optional lockout management interface. */
- @Nullable private AccountLockoutManager lockoutManager;
-
- /** UsernamePasswordContext containing the credentials to validate. */
- @Nullable private UsernamePasswordContext upContext;
+ /** Cached log prefix. */
+ @Nullable private String logPrefix;
/** Constructor. */
- public AbstractUsernamePasswordValidationAction() {
+ public AbstractUsernamePasswordCredentialValidator() {
+ usernamePasswordContextLookupStrategy = new ChildContextLookup<>(UsernamePasswordContext.class);
removeContextAfterValidation = true;
- setMetricName(DEFAULT_METRIC_NAME);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setId(final String id) {
+ super.setId(id);
+ }
+
+ /**
+ * Set the lookup strategy to locate the {@link UsernamePasswordContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setUsernamePasswordContextLookupStrategy(
+ @Nonnull final Function<AuthenticationContext,UsernamePasswordContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ usernamePasswordContextLookupStrategy = Constraint.isNotNull(strategy,
+ "UsernamePasswordContextLookupStrategy cannot be null");
}
/**
@@ -107,8 +121,6 @@ public abstract class AbstractUsernamePasswordValidationAction extends AbstractV
* <p>Defaults to true</p>
*
* @return whether to remove the context after successful validation
- *
- * @since 3.3.0
*/
public boolean removeContextAfterValidation() {
return removeContextAfterValidation;
@@ -119,8 +131,6 @@ public abstract class AbstractUsernamePasswordValidationAction extends AbstractV
* successfully validated.
*
* @param flag flag to set
- *
- * @since 3.3.0
*/
public void setRemoveContextAfterValidation(final boolean flag) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
@@ -139,129 +149,110 @@ public abstract class AbstractUsernamePasswordValidationAction extends AbstractV
matchExpression = expression;
}
- /**
- * Get an account lockout management component.
- *
- * @return lockout manager
- */
- @Nullable public AccountLockoutManager getLockoutManager() {
- return lockoutManager;
- }
-
- /**
- * Set an account lockout management component.
- *
- * @param manager lockout manager
- */
- public void setLockoutManager(@Nullable final AccountLockoutManager manager) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- lockoutManager = manager;
- }
-
- /**
- * Get the {@link UsernamePasswordContext} to validate.
- *
- * @return context to validate
- */
- @Nullable public UsernamePasswordContext getUsernamePasswordContext() {
- return upContext;
- }
-
/** {@inheritDoc} */
@Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
+ public Subject validate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception {
- if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
- return false;
- }
-
- upContext = authenticationContext.getSubcontext(UsernamePasswordContext.class);
+ final UsernamePasswordContext upContext = getUsernamePasswordContext(authenticationContext);
if (upContext == null) {
- log.info("{} No UsernamePasswordContext available within authentication context", getLogPrefix());
- handleError(profileRequestContext, authenticationContext, "NoCredentials", AuthnEventIds.NO_CREDENTIALS);
- recordFailure();
- return false;
+ log.info("{} No UsernamePasswordContext available", getLogPrefix());
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, (String) null,
+ AuthnEventIds.NO_CREDENTIALS);
+ }
+ throw new LoginException(AuthnEventIds.NO_CREDENTIALS);
} else if (upContext.getUsername() == null) {
log.info("{} No username available within UsernamePasswordContext", getLogPrefix());
- handleError(profileRequestContext, authenticationContext, "NoCredentials", AuthnEventIds.NO_CREDENTIALS);
- recordFailure();
- return false;
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, (String) null,
+ AuthnEventIds.NO_CREDENTIALS);
+ }
+ throw new LoginException(AuthnEventIds.NO_CREDENTIALS);
} else if (upContext.getPassword() == null) {
log.info("{} No password available within UsernamePasswordContext", getLogPrefix());
- handleError(profileRequestContext, authenticationContext, AuthnEventIds.INVALID_CREDENTIALS,
- AuthnEventIds.INVALID_CREDENTIALS);
- recordFailure();
- return false;
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, (String) null,
+ AuthnEventIds.INVALID_CREDENTIALS);
+ }
+ throw new LoginException(AuthnEventIds.INVALID_CREDENTIALS);
}
if (matchExpression != null && !matchExpression.matcher(upContext.getUsername()).matches()) {
log.debug("{} Username '{}' did not match expression", getLogPrefix(), upContext.getUsername());
- handleError(profileRequestContext, authenticationContext, AuthnEventIds.INVALID_CREDENTIALS,
- AuthnEventIds.INVALID_CREDENTIALS);
- recordFailure();
- return false;
- }
-
- if (lockoutManager != null && lockoutManager.check(profileRequestContext)) {
- log.info("{} Account for '{}' is locked out, aborting authentication", getLogPrefix(),
- upContext.getUsername());
- handleError(profileRequestContext, authenticationContext, AuthnEventIds.ACCOUNT_LOCKED,
- AuthnEventIds.ACCOUNT_LOCKED);
- recordFailure();
- return false;
+ return null;
}
-
- return true;
+
+ return doValidate(profileRequestContext, authenticationContext, upContext, warningHandler, errorHandler);
}
- /** {@inheritDoc} */
- @Override
- @Nonnull protected Subject populateSubject(@Nonnull final Subject subject) {
- subject.getPrincipals().add(new UsernamePrincipal(upContext.getUsername()));
+ /**
+ * Override method for subclasses to use to perform the actual validation.
+ *
+ * @param profileRequestContext profile request context
+ * @param authenticationContext authentication context
+ * @param usernamePasswordContext the username/password to validate
+ * @param warningHandler optional warning handler interface
+ * @param errorHandler optional error handler interface
+ *
+ * @return the validated result, or null if inapplicable
+ *
+ * @throws Exception if an error occurs
+ */
+ @Nullable protected abstract Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nonnull final UsernamePasswordContext usernamePasswordContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception;
+
+ /**
+ * Get the {@link UsernamePasswordContext} to validate.
+ *
+ * @param authenticationContext parent context
+ *
+ * @return context to validate
+ */
+ @Nullable protected UsernamePasswordContext getUsernamePasswordContext(
+ @Nonnull final AuthenticationContext authenticationContext) {
+ return usernamePasswordContextLookupStrategy.apply(authenticationContext);
+ }
+
+ /**
+ * Decorate the subject with "standard" content from the validation
+ * and clean up as instructed.
+ *
+ * @param subject the subject being returned
+ * @param usernamePasswordContext the username/password validated
+ *
+ * @return the decorated subject
+ */
+ @Nonnull protected Subject populateSubject(@Nonnull final Subject subject,
+ @Nonnull final UsernamePasswordContext usernamePasswordContext) {
+ subject.getPrincipals().add(new UsernamePrincipal(usernamePasswordContext.getUsername()));
if (savePasswordToCredentialSet) {
- subject.getPrivateCredentials().add(new PasswordPrincipal(upContext.getPassword()));
+ subject.getPrivateCredentials().add(new PasswordPrincipal(usernamePasswordContext.getPassword()));
}
if (removeContextAfterValidation) {
- upContext.getParent().removeSubcontext(upContext);
- upContext.setPassword(null);
- upContext = null;
+ usernamePasswordContext.getParent().removeSubcontext(usernamePasswordContext);
+ usernamePasswordContext.setPassword(null);
}
return subject;
}
-
- /**
- * Record a successful authentication attempt against the configured counter,
- * optionally clearing account lockout state.
- *
- * @param profileRequestContext current profile request context
- *
- * @since 3.3.0
- */
- protected void recordSuccess(@Nonnull final ProfileRequestContext profileRequestContext) {
- recordSuccess();
- if (lockoutManager != null) {
- lockoutManager.clear(profileRequestContext);
- }
- }
/**
- * Record a failed authentication attempt against the configured counter,
- * optionally incrementing the account lockout counter.
- *
- * @param profileRequestContext current profile request context
- * @param inc true iff lockout counter should be incremented
+ * Return a prefix for logging messages for this component.
*
- * @since 3.3.0
+ * @return a string for insertion at the beginning of any log messages
*/
- protected void recordFailure(@Nonnull final ProfileRequestContext profileRequestContext, final boolean inc) {
- recordFailure();
- if (inc && lockoutManager != null) {
- lockoutManager.increment(profileRequestContext);
+ @Nonnull @NotEmpty protected String getLogPrefix() {
+ if (logPrefix == null) {
+ logPrefix = "Credential Validator " + (getId() != null ? getId() : "(unknown)") + ":";
}
+ return logPrefix;
}
-
+
}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/CredentialValidator.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/CredentialValidator.java
new file mode 100644
index 0000000..aa28d95
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/CredentialValidator.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.authn;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.security.auth.Subject;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.IdentifiableComponent;
+
+/**
+ * High-level API for validating credentials and producing a Java Subject as a result.
+ *
+ * <p>This is more or less what JAAS does but with a simpler interface adapted better
+ * to the IdP's needs. Predominantly for password validation scenarios but the interface
+ * is not specific to that use case.</p>
+ *
+ * <p>Instances of this interface must be stateless.</p>
+ *
+ * @since 4.0.0
+ */
+ at ThreadSafe
+public interface CredentialValidator extends IdentifiableComponent {
+
+ /**
+ * Validate any credentials found in a supported form within the input context tree
+ * and produce a {@link Subject} as the outcome.
+ *
+ * <p>A null result is used to signal that validation was not attempted.</p>
+ *
+ * @param profileRequestContext profile request context
+ * @param authenticationContext authentication context
+ * @param warningHandler optional warning handler interface
+ * @param errorHandler optional error handler interface
+ *
+ * @return result of a successful validation, or null
+ *
+ * @throws Exception when validation is unsuccessful due to a failed attempt
+ */
+ @Nullable Subject validate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception;
+
+ /**
+ * Interface to use to report warnings to the caller.
+ */
+ @ThreadSafe
+ public interface WarningHandler {
+
+ /**
+ * Reports a warning state to the caller.
+ *
+ * <p>Warnings are an indication that authentication may have succeeded but with some information
+ * worth capturing.</p>
+ *
+ * @param profileRequestContext the current profile request context
+ * @param authenticationContext the current authentication context
+ * @param message to report
+ * @param eventId a default webflow event to report as the result of the calling action
+ */
+ void handleWarning(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext, @Nullable final String message,
+ @Nonnull @NotEmpty final String eventId);
+ }
+
+ /**
+ * Interface to use to report errors to the caller.
+ */
+ @ThreadSafe
+ public interface ErrorHandler {
+
+ /**
+ * Reports an error state to the caller.
+ *
+ * <p>Errors should never be reported as part of a successful login.</p>
+ *
+ * @param profileRequestContext the current profile request context
+ * @param authenticationContext the current authentication context
+ * @param e exception to report
+ * @param eventId a default webflow event to report as the result of the calling action
+ */
+ void handleError(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext, @Nonnull final Exception e,
+ @Nonnull @NotEmpty final String eventId);
+
+ /**
+ * Reports an error state to the caller.
+ *
+ * <p>Errors should never be reported as part of a successful login.</p>
+ *
+ * @param profileRequestContext the current profile request context
+ * @param authenticationContext the current authentication context
+ * @param message to report
+ * @param eventId a default webflow event to report as the result of the calling action
+ */
+ void handleError(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext, @Nullable final String message,
+ @Nonnull @NotEmpty final String eventId);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAAS.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/JAASCredentialValidator.java
similarity index 70%
rename from idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAAS.java
rename to idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/JAASCredentialValidator.java
index cdf2d4e..598d71f 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAAS.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/JAASCredentialValidator.java
@@ -27,20 +27,21 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.LanguageCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginException;
-import net.shibboleth.idp.authn.AbstractUsernamePasswordValidationAction;
+import net.shibboleth.idp.authn.AbstractUsernamePasswordCredentialValidator;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
+import net.shibboleth.idp.authn.context.UsernamePasswordContext;
import net.shibboleth.idp.authn.principal.PrincipalEvalPredicate;
import net.shibboleth.idp.authn.principal.PrincipalEvalPredicateFactory;
import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
@@ -50,36 +51,23 @@ import net.shibboleth.utilities.java.support.collection.Pair;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
-import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * An action that checks for a {@link net.shibboleth.idp.authn.context.UsernamePasswordContext} and directly produces an
- * {@link net.shibboleth.idp.authn.AuthenticationResult} based on that identity by invoking a JAAS configuration.
+ * A password validator that authenticates against JAAS.
*
- * <p>Various optional properties are supported to control the JAAS configuration process.</p>
- *
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- * @event {@link AuthnEventIds#REQUEST_UNSUPPORTED}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class).getAttemptedFlow() != null</pre>
- * @post If AuthenticationContext.getSubcontext(UsernamePasswordContext.class) != null, then
- * an {@link net.shibboleth.idp.authn.AuthenticationResult} is saved to the {@link AuthenticationContext} on a
- * successful login. On a failed login, the
- * {@link net.shibboleth.idp.authn.AbstractValidationAction#handleError(ProfileRequestContext, AuthenticationContext,
- * Exception, String)}
- * method is called.
+ * <p>Support for complex chaining of JAAS modules remains supported but should be
+ * avoided in favor of the new support for chaining validators in most cases.</p>
+ *
+ * @since 4.0.0
*/
-public class ValidateUsernamePasswordAgainstJAAS extends AbstractUsernamePasswordValidationAction {
-
- /** Default prefix for metrics. */
- @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn";
+ at ThreadSafe
+public class JAASCredentialValidator extends AbstractUsernamePasswordCredentialValidator {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateUsernamePasswordAgainstJAAS.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(JAASCredentialValidator.class);
/** Type of JAAS Configuration to instantiate. */
@Nullable private String loginConfigType;
@@ -93,17 +81,8 @@ public class ValidateUsernamePasswordAgainstJAAS extends AbstractUsernamePasswor
/** Strategy function to dynamically derive the login config(s) to use. */
@Nullable private Function<ProfileRequestContext,Collection<Pair<String,Subject>>> loginConfigStrategy;
- /** Saved off context. */
- @Nullable private RequestedPrincipalContext requestedPrincipalCtx;
-
- /** Tracks any principals derived from the login configuration to add to the Subject. */
- @Nullable private Subject derivedSubject;
-
- /** Tracker for current login config for reporting. */
- @Nullable private String currentLoginConfigName;
-
/** Constructor. */
- public ValidateUsernamePasswordAgainstJAAS() {
+ public JAASCredentialValidator() {
// For compatibility with V2.
loginConfigurations = Collections.singletonList(new Pair<String,Subject>("ShibUserPassAuth", null));
}
@@ -207,82 +186,77 @@ public class ValidateUsernamePasswordAgainstJAAS extends AbstractUsernamePasswor
/** {@inheritDoc} */
@Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
-
- if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
- return false;
- }
-
- requestedPrincipalCtx = authenticationContext.getSubcontext(RequestedPrincipalContext.class);
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
+ @Nullable protected Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nonnull final UsernamePasswordContext usernamePasswordContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception {
+ final RequestedPrincipalContext requestedPrincipalCtx =
+ authenticationContext.getSubcontext(RequestedPrincipalContext.class);
+
final Collection<Pair<String,Subject>> configs;
if (loginConfigStrategy != null) {
configs = loginConfigStrategy.apply(profileRequestContext);
} else {
configs = loginConfigurations;
}
-
- boolean eventSignaled = false;
+
+ Exception caughtException = null;
for (final Pair<String,Subject> loginConfig : configs) {
- if (!isAcceptable(authenticationContext, loginConfig.getFirst(), loginConfig.getSecond())) {
+ if (!isAcceptable(requestedPrincipalCtx, loginConfig.getFirst(), loginConfig.getSecond())) {
continue;
}
-
+
+ final String currentLoginConfigName = loginConfig.getFirst();
+
try {
- currentLoginConfigName = loginConfig.getFirst();
log.debug("{} Attempting to authenticate user '{}' via '{}'", getLogPrefix(),
- getUsernamePasswordContext().getUsername(), currentLoginConfigName);
- authenticate(currentLoginConfigName);
+ usernamePasswordContext.getUsername(), currentLoginConfigName);
+ final Subject subject = authenticate(currentLoginConfigName, usernamePasswordContext);
log.info("{} Login by '{}' via '{}' succeeded", getLogPrefix(),
- getUsernamePasswordContext().getUsername(), currentLoginConfigName);
- recordSuccess(profileRequestContext);
- derivedSubject = loginConfig.getSecond();
- buildAuthenticationResult(profileRequestContext, authenticationContext);
- ActionSupport.buildProceedEvent(profileRequestContext);
- return;
+ usernamePasswordContext.getUsername(), currentLoginConfigName);
+ return populateSubject(subject, loginConfig.getSecond(), usernamePasswordContext);
} catch (final LoginException e){
- log.info("{} Login by '{}' via '{}' failed", getLogPrefix(), getUsernamePasswordContext().getUsername(),
+ log.info("{} Login by '{}' via '{}' failed", getLogPrefix(), usernamePasswordContext.getUsername(),
currentLoginConfigName, e);
- handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.INVALID_CREDENTIALS);
- recordFailure(profileRequestContext, true);
- eventSignaled = true;
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, e,
+ AuthnEventIds.INVALID_CREDENTIALS);
+ }
+ caughtException = e;
} catch (final Exception e) {
log.warn("{} Login by '{}' via '{}' produced exception", getLogPrefix(),
- getUsernamePasswordContext().getUsername(), currentLoginConfigName, e);
- handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
- recordFailure(profileRequestContext, false);
- eventSignaled = true;
+ usernamePasswordContext.getUsername(), currentLoginConfigName, e);
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, e,
+ AuthnEventIds.AUTHN_EXCEPTION);
+ }
+ caughtException = e;
}
}
-
- if (!eventSignaled) {
- log.warn("{} No JAAS application configurations are available or acceptable for use", getLogPrefix());
- handleError(profileRequestContext, authenticationContext, "RequestUnsupported",
- AuthnEventIds.REQUEST_UNSUPPORTED);
+
+ if (caughtException == null) {
+ log.info("{} No JAAS application configurations are available or acceptable for use", getLogPrefix());
+ return null;
}
+
+ throw caughtException;
}
/**
* Checks a particular JAAS configuration and principal collection for suitability.
*
- * @param authenticationContext the authentication context
+ * @param requestedPrincipalCtx the relevant context
* @param configName name of JAAS config
* @param subject collection of custom principals to check, embedded in a subject
*
* @return true iff the request does not specify requirements or the principal collection is empty
* or the combination is acceptable
*/
- private boolean isAcceptable(@Nonnull final AuthenticationContext authenticationContext,
+ private boolean isAcceptable(@Nullable final RequestedPrincipalContext requestedPrincipalCtx,
@Nonnull @NotEmpty final String configName, @Nullable final Subject subject) {
if (subject != null && requestedPrincipalCtx != null && requestedPrincipalCtx.getOperator() != null) {
@@ -326,12 +300,16 @@ public class ValidateUsernamePasswordAgainstJAAS extends AbstractUsernamePasswor
* Create a JAAS configuration and attempt a login with it.
*
* @param loginConfigName the application name to use
+ * @param usernamePasswordContext input context
+ *
+ * @return the JAAS result
*
* @throws LoginException if the JAAS login process fails
* @throws NoSuchAlgorithmException if a JAAS configuration cannot be created
*/
- private void authenticate(@Nonnull @NotEmpty final String loginConfigName)
- throws LoginException, NoSuchAlgorithmException {
+ @Nonnull private Subject authenticate(@Nonnull @NotEmpty final String loginConfigName,
+ @Nonnull final UsernamePasswordContext usernamePasswordContext)
+ throws LoginException, NoSuchAlgorithmException {
final javax.security.auth.login.LoginContext jaasLoginCtx;
@@ -340,32 +318,35 @@ public class ValidateUsernamePasswordAgainstJAAS extends AbstractUsernamePasswor
getLoginConfigType(), getLoginConfigParameters().getClass().getName());
final Configuration loginConfig =
Configuration.getInstance(getLoginConfigType(), getLoginConfigParameters());
- jaasLoginCtx = new javax.security.auth.login.LoginContext(loginConfigName, getSubject(),
- new SimpleCallbackHandler(), loginConfig);
+ jaasLoginCtx = new javax.security.auth.login.LoginContext(loginConfigName, null,
+ new SimpleCallbackHandler(usernamePasswordContext), loginConfig);
} else {
log.debug("{} Using system JAAS configuration", getLogPrefix());
- jaasLoginCtx = new javax.security.auth.login.LoginContext(loginConfigName, getSubject(),
- new SimpleCallbackHandler());
+ jaasLoginCtx = new javax.security.auth.login.LoginContext(loginConfigName, null,
+ new SimpleCallbackHandler(usernamePasswordContext));
}
jaasLoginCtx.login();
+
+ return jaasLoginCtx.getSubject();
}
- /** {@inheritDoc} */
- @Override
- @Nonnull protected Subject populateSubject(@Nonnull final Subject subject) {
-
- final Subject theSubject = super.populateSubject(subject);
+ /**
+ * Finish decorating the result.
+ *
+ * @param subject the JAAS result
+ * @param derivedSubject container for additional principals
+ * @param usernamePasswordContext input context
+ *
+ * @return final result
+ */
+ @Nonnull protected Subject populateSubject(@Nonnull final Subject subject,
+ @Nullable final Subject derivedSubject, @Nonnull final UsernamePasswordContext usernamePasswordContext) {
+
if (derivedSubject != null) {
- theSubject.getPrincipals().addAll(derivedSubject.getPrincipals());
+ subject.getPrincipals().addAll(derivedSubject.getPrincipals());
}
- return theSubject;
- }
-
- /** {@inheritDoc} */
- @Override
- @Nonnull @NotEmpty public String getMetricName() {
- return super.getMetricName() + '.' + currentLoginConfigName;
+ return super.populateSubject(subject, usernamePasswordContext);
}
/**
@@ -376,6 +357,18 @@ public class ValidateUsernamePasswordAgainstJAAS extends AbstractUsernamePasswor
*/
protected class SimpleCallbackHandler implements CallbackHandler {
+ /** Context for call. */
+ @Nonnull private final UsernamePasswordContext context;
+
+ /**
+ * Constructor.
+ *
+ * @param usernamePasswordContext input context
+ */
+ public SimpleCallbackHandler(@Nonnull final UsernamePasswordContext usernamePasswordContext) {
+ context = usernamePasswordContext;
+ }
+
/**
* Handle a callback.
*
@@ -393,17 +386,10 @@ public class ValidateUsernamePasswordAgainstJAAS extends AbstractUsernamePasswor
for (final Callback cb : callbacks) {
if (cb instanceof NameCallback) {
final NameCallback ncb = (NameCallback) cb;
- ncb.setName(getUsernamePasswordContext().getUsername());
+ ncb.setName(context.getUsername());
} else if (cb instanceof PasswordCallback) {
final PasswordCallback pcb = (PasswordCallback) cb;
- pcb.setPassword(getUsernamePasswordContext().getPassword().toCharArray());
- } else if (cb instanceof LanguageCallback) {
- if (getHttpServletRequest() != null) {
- final LanguageCallback lcb = (LanguageCallback) cb;
- lcb.setLocale(getHttpServletRequest().getLocale());
- } else {
- log.warn("{} Language callback invoked, no HttpServletRequest available", getLogPrefix());
- }
+ pcb.setPassword(context.getPassword().toCharArray());
}
}
}
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstKerberos.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/KerberosCredentialValidator.java
similarity index 72%
rename from idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstKerberos.java
rename to idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/KerberosCredentialValidator.java
index 5070266..ca6b46d 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstKerberos.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/KerberosCredentialValidator.java
@@ -24,6 +24,7 @@ import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
@@ -33,9 +34,10 @@ import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
-import net.shibboleth.idp.authn.AbstractUsernamePasswordValidationAction;
+import net.shibboleth.idp.authn.AbstractUsernamePasswordCredentialValidator;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.UsernamePasswordContext;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -54,26 +56,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * An action that checks for a {@link net.shibboleth.idp.authn.context.UsernamePasswordContext} and directly produces an
- * {@link net.shibboleth.idp.authn.AuthenticationResult} based on that identity by acquiring
- * a TGT and optional service ticket from Kerberos.
- *
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#AUTHN_EXCEPTION}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false).getAttemptedFlow() != null</pre>
- * @post If AuthenticationContext.getSubcontext(UsernamePasswordContext.class, false) != null, then
- * an {@link net.shibboleth.idp.authn.AuthenticationResult} is saved to the {@link AuthenticationContext} on a
- * successful login. On a failed login, the {@link net.shibboleth.idp.authn.AbstractValidationAction#handleError(
- * ProfileRequestContext, AuthenticationContext, Exception, String)} method is called.
+ * A password validator that authenticates against Kerberos natively, with optional service ticket verification.
+ *
+ * @since 4.0.0
*/
-public class ValidateUsernamePasswordAgainstKerberos extends AbstractUsernamePasswordValidationAction {
-
- /** Default prefix for metrics. */
- @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.krb5";
+ at ThreadSafe
+public class KerberosCredentialValidator extends AbstractUsernamePasswordCredentialValidator {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateUsernamePasswordAgainstKerberos.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(KerberosCredentialValidator.class);
/** Class name of JAAS LoginModule to acquire Kerberos credentials. */
@NonnullAfterInit @NotEmpty private String loginModuleClassName;
@@ -97,9 +88,8 @@ public class ValidateUsernamePasswordAgainstKerberos extends AbstractUsernamePas
@NonnullAfterInit private Map<String,String> serverOptions;
/** Constructor. */
- public ValidateUsernamePasswordAgainstKerberos() {
+ public KerberosCredentialValidator() {
loginModuleClassName = "com.sun.security.auth.module.Krb5LoginModule";
- setMetricName(DEFAULT_METRIC_NAME);
}
/**
@@ -189,67 +179,81 @@ public class ValidateUsernamePasswordAgainstKerberos extends AbstractUsernamePas
/** {@inheritDoc} */
@Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
+ protected Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nonnull final UsernamePasswordContext usernamePasswordContext,
+ @Nullable final WarningHandler warningHandler, @Nullable final ErrorHandler errorHandler) throws Exception {
+
+ String eventToSignal = AuthnEventIds.AUTHN_EXCEPTION;
try {
- final LoginModule clientLoginModule = (LoginModule) Class.forName(loginModuleClassName).
- getDeclaredConstructor().newInstance();
- clientLoginModule.initialize(getSubject(), new SimpleCallbackHandler(), new HashMap(), clientOptions);
- if (!clientLoginModule.login() || !clientLoginModule.commit()) {
- clientLoginModule.abort();
- throw new LoginException("Login module reported failure");
- }
-
- // We don't call logout, since that would destroy the contents of the Subject.
-
- if (servicePrincipal != null) {
- verifyKDC();
+ try {
+ final Subject subject = new Subject();
+ final LoginModule clientLoginModule = (LoginModule) Class.forName(loginModuleClassName).
+ getDeclaredConstructor().newInstance();
+ clientLoginModule.initialize(subject, new SimpleCallbackHandler(usernamePasswordContext), new HashMap(),
+ clientOptions);
+ if (!clientLoginModule.login() || !clientLoginModule.commit()) {
+ clientLoginModule.abort();
+ throw new LoginException("Login module reported failure");
+ }
+
+ // We don't call logout, since that would destroy the contents of the Subject.
+
+ if (servicePrincipal != null) {
+ log.debug("{} TGT acquired for {}, " +
+ "attempting to verify authenticity of TGT using service principal {}",
+ getLogPrefix(), usernamePasswordContext.getUsername(), servicePrincipal);
+ verifyKDC(subject);
+ }
+
+ log.info("{} Login by '{}' succeeded", getLogPrefix(), usernamePasswordContext.getUsername());
+ return populateSubject(subject, usernamePasswordContext);
+ } catch (final InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+ log.error("{} Unable to instantiate JAAS module for Kerberos", getLogPrefix(), e);
+ throw e;
+ } catch (final LoginException e) {
+ log.info("{} Login by {} failed", getLogPrefix(), usernamePasswordContext.getUsername(), e);
+ eventToSignal = AuthnEventIds.INVALID_CREDENTIALS;
+ throw e;
+ } catch(final GSSException e) {
+ log.warn("{} Login by {} failed during GSS context establishment to verify KDC", getLogPrefix(),
+ usernamePasswordContext.getUsername(), e);
+ eventToSignal = AuthnEventIds.INVALID_CREDENTIALS;
+ throw e;
+ } catch (final Exception e) {
+ log.warn("{} Login by {} produced unknown exception", getLogPrefix(),
+ usernamePasswordContext.getUsername(), e);
+ throw e;
}
-
- log.info("{} Login by '{}' succeeded", getLogPrefix(), getUsernamePasswordContext().getUsername());
- recordSuccess(profileRequestContext);
- buildAuthenticationResult(profileRequestContext, authenticationContext);
- } catch (final InstantiationException | IllegalAccessException | ClassNotFoundException e) {
- log.error("{} Unable to instantiate JAAS module for Kerberos", getLogPrefix(), e);
- handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
- recordFailure(profileRequestContext, false);
- } catch (final LoginException e) {
- log.info("{} Login by {} failed", getLogPrefix(), getUsernamePasswordContext().getUsername(), e);
- handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.INVALID_CREDENTIALS);
- recordFailure(profileRequestContext, true);
- } catch(final GSSException e) {
- log.warn("{} Login by {} failed during GSS context establishment to verify KDC", getLogPrefix(),
- getUsernamePasswordContext().getUsername(), e);
- handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.INVALID_CREDENTIALS);
- recordFailure(profileRequestContext, false);
} catch (final Exception e) {
- log.warn("{} Login by {} produced unknown exception", getLogPrefix(),
- getUsernamePasswordContext().getUsername(), e);
- handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
- recordFailure(profileRequestContext, false);
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, e, eventToSignal);
+ }
+ throw e;
}
}
/** {@inheritDoc} */
@Override
- @Nonnull protected Subject populateSubject(@Nonnull final Subject subject) {
+ @Nonnull protected Subject populateSubject(@Nonnull final Subject subject,
+ @Nonnull final UsernamePasswordContext usernamePasswordContext) {
if (!preserveTicket) {
subject.getPrivateCredentials().clear();
}
- return super.populateSubject(subject);
+ return super.populateSubject(subject, usernamePasswordContext);
}
/**
* Use credentials to acquire and verify a service ticket.
*
+ * @param subject client identity
+ *
* @throws Exception if an error occurs
*/
- private void verifyKDC() throws Exception {
- log.debug("{} TGT acquired for {}, attempting to verify authenticity of TGT using service principal {}",
- getLogPrefix(), getUsernamePasswordContext().getUsername(), servicePrincipal);
+ private void verifyKDC(@Nonnull final Subject subject) throws Exception {
final Oid mechOid = new Oid("1.2.840.113554.1.2.2");
@@ -273,7 +277,7 @@ public class ValidateUsernamePasswordAgainstKerberos extends AbstractUsernamePas
// The GSS context initiation has to be performed as a privileged action with the client subject
// so that the null credential above indicating the default credentials pulls from the JAAS subject.
- final byte[] token = Subject.doAs(getSubject(), new PrivilegedExceptionAction<byte[]>() {
+ final byte[] token = Subject.doAs(subject, new PrivilegedExceptionAction<byte[]>() {
public byte[] run() throws GSSException {
final byte[] token = new byte[0];
// This is a one pass context initialization.
@@ -323,6 +327,18 @@ public class ValidateUsernamePasswordAgainstKerberos extends AbstractUsernamePas
* This handler only supports {@link NameCallback} and {@link PasswordCallback}.
*/
private class SimpleCallbackHandler implements CallbackHandler {
+
+ /** Context for call. */
+ @Nonnull private final UsernamePasswordContext context;
+
+ /**
+ * Constructor.
+ *
+ * @param usernamePasswordContext input context
+ */
+ public SimpleCallbackHandler(@Nonnull final UsernamePasswordContext usernamePasswordContext) {
+ context = usernamePasswordContext;
+ }
/**
* Handle a callback.
@@ -341,10 +357,10 @@ public class ValidateUsernamePasswordAgainstKerberos extends AbstractUsernamePas
for (final Callback cb : callbacks) {
if (cb instanceof NameCallback) {
final NameCallback ncb = (NameCallback) cb;
- ncb.setName(getUsernamePasswordContext().getUsername());
+ ncb.setName(context.getUsername());
} else if (cb instanceof PasswordCallback) {
final PasswordCallback pcb = (PasswordCallback) cb;
- pcb.setPassword(getUsernamePasswordContext().getPassword().toCharArray());
+ pcb.setPassword(context.getPassword().toCharArray());
}
}
}
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAP.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
similarity index 51%
rename from idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAP.java
rename to idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
index cbae93b..1af9ee5 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAP.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
@@ -19,14 +19,15 @@ package net.shibboleth.idp.authn.impl;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
import javax.security.auth.Subject;
-import net.shibboleth.idp.authn.AbstractUsernamePasswordValidationAction;
+import net.shibboleth.idp.authn.AbstractUsernamePasswordCredentialValidator;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.LDAPResponseContext;
+import net.shibboleth.idp.authn.context.UsernamePasswordContext;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -47,44 +48,21 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * An action that checks for a {@link net.shibboleth.idp.authn.context.UsernamePasswordContext} and directly produces an
- * {@link net.shibboleth.idp.authn.AuthenticationResult} based on that identity by authenticating against an LDAP.
+ * A password validator that authenticates against LDAP natively.
*
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#AUTHN_EXCEPTION}
- * @event {@link AuthnEventIds#ACCOUNT_WARNING}
- * @event {@link AuthnEventIds#ACCOUNT_ERROR}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- * @pre <pre>
- * ProfileRequestContext.getSubcontext(AuthenticationContext.class).getAttemptedFlow() != null
- * </pre>
- * @post If AuthenticationContext.getSubcontext(UsernamePasswordContext.class) != null, then an
- * {@link net.shibboleth.idp.authn.AuthenticationResult} is saved to the {@link AuthenticationContext} on a
- * successful login. On a failed login, the
- * {@link net.shibboleth.idp.authn.AbstractValidationAction#handleError(ProfileRequestContext,
- * AuthenticationContext, String, String)} method is called.
+ * @since 4.0.0
*/
-public class ValidateUsernamePasswordAgainstLDAP extends AbstractUsernamePasswordValidationAction {
-
- /** Default prefix for metrics. */
- @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.ldap";
+ at ThreadSafe
+public class LDAPCredentialValidator extends AbstractUsernamePasswordCredentialValidator {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateUsernamePasswordAgainstLDAP.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LDAPCredentialValidator.class);
/** LDAP authenticator. */
@Nonnull private Authenticator authenticator;
/** Attributes to return from authentication. */
@Nullable private String[] returnAttributes;
-
- /** Authentication response associated with the login. */
- @Nullable private AuthenticationResponse response;
-
- /** Constructor. */
- public ValidateUsernamePasswordAgainstLDAP() {
- setMetricName(DEFAULT_METRIC_NAME);
- }
/**
* Returns the authenticator.
@@ -127,7 +105,8 @@ public class ValidateUsernamePasswordAgainstLDAP extends AbstractUsernamePasswor
}
/** {@inheritDoc} */
- @Override protected void doInitialize() throws ComponentInitializationException {
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
if (authenticator == null) {
@@ -135,72 +114,98 @@ public class ValidateUsernamePasswordAgainstLDAP extends AbstractUsernamePasswor
}
}
+// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
- @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
+ @Override
+ @Nullable protected Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nonnull final UsernamePasswordContext usernamePasswordContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception {
+
+ final String username = usernamePasswordContext.getUsername();
+
+ String eventToSignal = AuthnEventIds.INVALID_CREDENTIALS;
+
+ // The error handling is squonky. We log at info to generically record the failure.
+ // Known conditions are not explicitly logged but are wrapped with an exception and
+ // reported out to the caller. Last ditch, an exception is logged on warn and then
+ // reported out.
+
try {
- log.debug("{} Attempting to authenticate user {}", getLogPrefix(), getUsernamePasswordContext()
- .getUsername());
+ log.debug("{} Attempting to authenticate user {}", getLogPrefix(), username);
final VelocityContext context = new VelocityContext();
- context.put("usernamePasswordContext", getUsernamePasswordContext());
+ context.put("usernamePasswordContext", usernamePasswordContext);
final AuthenticationRequest request =
- new AuthenticationRequest(new User(getUsernamePasswordContext().getUsername(), context),
- new Credential(getUsernamePasswordContext().getPassword()), returnAttributes);
- response = authenticator.authenticate(request);
+ new AuthenticationRequest(new User(username, context),
+ new Credential(usernamePasswordContext.getPassword()), returnAttributes);
+ final AuthenticationResponse response = authenticator.authenticate(request);
log.trace("{} Authentication response {}", getLogPrefix(), response);
if (response.getResult()) {
- log.info("{} Login by '{}' succeeded", getLogPrefix(), getUsernamePasswordContext().getUsername());
- recordSuccess(profileRequestContext);
- authenticationContext.getSubcontext(LDAPResponseContext.class, true)
- .setAuthenticationResponse(response);
+ log.info("{} Login by '{}' succeeded", getLogPrefix(), username);
+ authenticationContext.getSubcontext(
+ LDAPResponseContext.class, true).setAuthenticationResponse(response);
if (response.getAccountState() != null) {
final AccountState.Error error = response.getAccountState().getError();
- handleWarning(
- profileRequestContext,
- authenticationContext,
- String.format("%s:%s:%s", error != null ? error : "ACCOUNT_WARNING",
- response.getResultCode(), response.getMessage()), AuthnEventIds.ACCOUNT_WARNING);
+ if (warningHandler != null) {
+ warningHandler.handleWarning(
+ profileRequestContext,
+ authenticationContext,
+ String.format("%s:%s:%s", error != null ? error : "ACCOUNT_WARNING",
+ response.getResultCode(), response.getMessage()),
+ AuthnEventIds.ACCOUNT_WARNING);
+ }
}
- buildAuthenticationResult(profileRequestContext, authenticationContext);
+ return populateSubject(usernamePasswordContext, response);
} else {
- log.info("{} Login by '{}' failed", getLogPrefix(), getUsernamePasswordContext().getUsername());
- authenticationContext.getSubcontext(LDAPResponseContext.class, true)
- .setAuthenticationResponse(response);
+ log.info("{} Login by '{}' failed", getLogPrefix(), username);
+ authenticationContext.getSubcontext(
+ LDAPResponseContext.class, true).setAuthenticationResponse(response);
if (AuthenticationResultCode.DN_RESOLUTION_FAILURE == response.getAuthenticationResultCode()
|| AuthenticationResultCode.INVALID_CREDENTIAL == response.getAuthenticationResultCode()) {
- handleError(profileRequestContext, authenticationContext,
- String.format("%s:%s", response.getAuthenticationResultCode(), response.getMessage()),
- AuthnEventIds.INVALID_CREDENTIALS);
- recordFailure(profileRequestContext, true);
+ throw new LdapException(
+ String.format("%s:%s", response.getAuthenticationResultCode(), response.getMessage()));
} else if (response.getAccountState() != null) {
final AccountState state = response.getAccountState();
- handleError(profileRequestContext, authenticationContext, String.format("%s:%s:%s",
- state.getError(), response.getResultCode(), response.getMessage()),
- AuthnEventIds.ACCOUNT_ERROR);
- recordFailure(profileRequestContext, true);
+ eventToSignal = AuthnEventIds.ACCOUNT_ERROR;
+ throw new LdapException(
+ String.format("%s:%s:%s", state.getError(), response.getResultCode(), response.getMessage())
+ );
} else if (response.getResultCode() == ResultCode.INVALID_CREDENTIALS) {
- handleError(profileRequestContext, authenticationContext,
- String.format("%s:%s", response.getResultCode(), response.getMessage()),
- AuthnEventIds.INVALID_CREDENTIALS);
- recordFailure(profileRequestContext, true);
+ throw new LdapException(String.format("%s:%s", response.getResultCode(), response.getMessage()));
} else {
- throw new LdapException(response.getMessage(), response.getResultCode(), response.getMatchedDn(),
+ eventToSignal = AuthnEventIds.AUTHN_EXCEPTION;
+ final LdapException e =
+ new LdapException(response.getMessage(), response.getResultCode(), response.getMatchedDn(),
response.getControls(), response.getReferralURLs(), response.getMessageId());
+ log.warn("{} Login by {} produced exception", getLogPrefix(), username, e);
+ throw e;
}
}
} catch (final LdapException e) {
- log.warn("{} Login by {} produced exception", getLogPrefix(), getUsernamePasswordContext().getUsername(),
- e);
- recordFailure(profileRequestContext, false);
- handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, e, eventToSignal);
+ }
+ throw e;
}
}
+// Checkstyle: CyclomaticComplexity ON
- /** {@inheritDoc} */
- @Override @Nonnull protected Subject populateSubject(@Nonnull final Subject subject) {
+ /**
+ * Builds a new {@link Subject} populated with the necessary data.
+ *
+ * @param usernamePasswordContext input context
+ * @param ldapResponse LDAP response data
+ *
+ * @return the subject to return
+ */
+ @Nonnull protected Subject populateSubject(@Nonnull final UsernamePasswordContext usernamePasswordContext,
+ @Nonnull final AuthenticationResponse ldapResponse) {
+
+ final Subject subject = new Subject();
subject.getPrincipals().add(
- new LdapPrincipal(getUsernamePasswordContext().getUsername(), response.getLdapEntry()));
- return super.populateSubject(subject);
+ new LdapPrincipal(usernamePasswordContext.getUsername(), ldapResponse.getLdapEntry()));
+ return super.populateSubject(subject, usernamePasswordContext);
}
-}
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateCredentials.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateCredentials.java
new file mode 100644
index 0000000..10647c5
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateCredentials.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.authn.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.security.auth.Subject;
+
+import net.shibboleth.idp.authn.AbstractValidationAction;
+import net.shibboleth.idp.authn.AccountLockoutManager;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.CredentialValidator;
+import net.shibboleth.idp.authn.CredentialValidator.ErrorHandler;
+import net.shibboleth.idp.authn.CredentialValidator.WarningHandler;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+
+/**
+ * An action that processes a list of {@link CredentialValidator} objects to produce an {@link AuthenticationResult}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event others on error
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class).getAttemptedFlow() != null</pre>
+ *
+ * @since 4.0.0
+ */
+public class ValidateCredentials extends AbstractValidationAction implements WarningHandler, ErrorHandler {
+
+ /** Default prefix for metrics. */
+ @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateCredentials.class);
+
+ /** Ordered list of validators. */
+ @Nonnull @NonnullElements private List<CredentialValidator> credentialValidators;
+
+ /** Whether all validators must succeed. */
+ private boolean requireAll;
+
+ /** Optional lockout management interface. */
+ @Nullable private AccountLockoutManager lockoutManager;
+
+ /** Results from successful validators. */
+ @Nonnull @NonnullElements private Collection<Subject> results;
+
+ /** Currently executing validator. */
+ @Nullable private CredentialValidator currentValidator;
+
+ /** Tracks whether a warning event was signaled. */
+ private boolean warningSignaled;
+
+ /** Tracks whether an error event was signaled. */
+ private boolean errorSignaled;
+
+ /** Constructor. */
+ public ValidateCredentials() {
+ setMetricName(DEFAULT_METRIC_NAME);
+ credentialValidators = Collections.emptyList();
+ results = new ArrayList<>(1);
+ }
+
+ /**
+ * Set an account lockout management component.
+ *
+ * @param manager lockout manager
+ */
+ public void setLockoutManager(@Nullable final AccountLockoutManager manager) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ lockoutManager = manager;
+ }
+
+ /**
+ * Set the list of validators to use.
+ *
+ * @param validators validators to use
+ */
+ public void setValidators(@Nonnull @NonnullElements final List<CredentialValidator> validators) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ Constraint.isNotNull(validators, "Validators list cannot be null");
+
+ credentialValidators = new ArrayList<>(Collections2.filter(validators, Predicates.notNull()));
+ }
+
+ /**
+ * Set whether to execute and require success from all configured validators,
+ * or stop at the first successful result.
+ *
+ * @param flag flag to set
+ */
+ public void setRequireAll(final boolean flag) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ requireAll = flag;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull @NotEmpty public String getMetricName() {
+ return super.getMetricName() + '.' + currentValidator.getId();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void handleWarning(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext, @Nullable final String message,
+ @Nonnull @NotEmpty final String eventId) {
+ warningSignaled = true;
+ super.handleWarning(profileRequestContext, authenticationContext, message, eventId);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void handleError(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext, @Nullable final String message,
+ @Nonnull @NotEmpty final String eventId) {
+ errorSignaled = true;
+ super.handleError(profileRequestContext, authenticationContext, message, eventId);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void handleError(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext, @Nonnull final Exception e,
+ @Nonnull @NotEmpty final String eventId) {
+ errorSignaled = true;
+ super.handleError(profileRequestContext, authenticationContext, e, eventId);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+ return false;
+ }
+
+ if (authenticationContext.getAttemptedFlow() == null) {
+ log.info("{} No attempted flow within authentication context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ if (lockoutManager != null && lockoutManager.check(profileRequestContext)) {
+ log.info("{} Account locked out, aborting authentication", getLogPrefix());
+ handleError(profileRequestContext, authenticationContext, AuthnEventIds.ACCOUNT_LOCKED,
+ AuthnEventIds.ACCOUNT_LOCKED);
+ return;
+ }
+
+ for (final CredentialValidator validator : credentialValidators) {
+ log.trace("{} Attempting credential validation via {}", getLogPrefix(), validator.getId());
+
+ currentValidator = validator;
+
+ try {
+ final Subject subject =
+ currentValidator.validate(profileRequestContext, authenticationContext, this, this);
+ if (subject == null) {
+ // Ignored, so try next one.
+ continue;
+ }
+
+ // Add the result to the list and record it.
+ results.add(subject);
+
+ if (!requireAll) {
+ recordSuccess(profileRequestContext);
+ buildAuthenticationResult(profileRequestContext, authenticationContext);
+ if (!warningSignaled) {
+ ActionSupport.buildProceedEvent(profileRequestContext);
+ }
+ return;
+ }
+ } catch (final Exception e) {
+ recordFailure();
+ if (!errorSignaled) {
+ super.handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
+ errorSignaled = true;
+ }
+ }
+ }
+
+ // If all must pass, and all passed, and at least one did something, then that's also success.
+ if (requireAll && !errorSignaled && !results.isEmpty()) {
+ recordSuccess(profileRequestContext);
+ buildAuthenticationResult(profileRequestContext, authenticationContext);
+ if (!warningSignaled) {
+ ActionSupport.buildProceedEvent(profileRequestContext);
+ }
+ return;
+ }
+
+ // If failure, then we may need to bump a lockout count if one of them outright
+ // failed. Failure could also just mean nothing was attempted.
+
+ if (errorSignaled) {
+ if (lockoutManager != null) {
+ lockoutManager.increment(profileRequestContext);
+ }
+ } else {
+ log.warn("{} No validators were available or usable", getLogPrefix());
+ handleError(profileRequestContext, authenticationContext, AuthnEventIds.REQUEST_UNSUPPORTED,
+ AuthnEventIds.REQUEST_UNSUPPORTED);
+ }
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull protected Subject populateSubject(@Nonnull final Subject subject) {
+
+ for (final Subject s : results) {
+ subject.getPrincipals().addAll(s.getPrincipals());
+ subject.getPublicCredentials().addAll(s.getPublicCredentials());
+ subject.getPrivateCredentials().addAll(s.getPrivateCredentials());
+ }
+
+ return subject;
+ }
+
+ /**
+ * Record a successful authentication attempt against the configured counter,
+ * optionally clearing account lockout state.
+ *
+ * @param profileRequestContext current profile request context
+ */
+ protected void recordSuccess(@Nonnull final ProfileRequestContext profileRequestContext) {
+ recordSuccess();
+ if (lockoutManager != null) {
+ lockoutManager.clear(profileRequestContext);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java
index 87ba381..3757665 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java
@@ -57,12 +57,14 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.sdk.LDAPException;
-/** {@link ValidateUsernamePasswordAgainstJAAS} unit test. */
+/** Unit test for JAAS validation. */
public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationContextTest {
private static final String DATA_PATH = "src/test/resources/net/shibboleth/idp/authn/impl/";
- private ValidateUsernamePasswordAgainstJAAS action;
+ private JAASCredentialValidator validator;
+
+ private ValidateCredentials action;
private InMemoryDirectoryServer directoryServer;
@@ -91,7 +93,11 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
@BeforeMethod public void setUp() throws Exception {
super.setUp();
- action = new ValidateUsernamePasswordAgainstJAAS();
+ validator = new JAASCredentialValidator();
+ validator.setId("jaastest");
+
+ action = new ValidateCredentials();
+ action.setValidators(Collections.singletonList(validator));
final Map<String,Collection<String>> mappings = new HashMap<>();
mappings.put("UnknownUsername", Collections.singleton("DN_RESOLUTION_FAILURE"));
@@ -102,6 +108,7 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
}
@Test public void testMissingFlow() throws Exception {
+ validator.initialize();
action.initialize();
final Event event = action.execute(src);
@@ -110,6 +117,8 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
@Test public void testMissingUser() throws Exception {
prc.getSubcontext(AuthenticationContext.class).setAttemptedFlow(authenticationFlows.get(0));
+
+ validator.initialize();
action.initialize();
final Event event = action.execute(src);
@@ -120,6 +129,8 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
ac.getSubcontext(UsernamePasswordContext.class, true);
+
+ validator.initialize();
action.initialize();
final Event event = action.execute(src);
@@ -132,6 +143,8 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
+
+ validator.initialize();
action.initialize();
doExtract(prc);
@@ -149,11 +162,12 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setLoginConfigNames(Collections.singletonList("ShibBadAuth"));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigNames(Collections.singletonList("ShibBadAuth"));
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -178,12 +192,13 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
rpc.setOperator("exact");
rpc.setRequestedPrincipals(Collections.<Principal>singletonList(new TestPrincipal("test1")));
- action.setLoginConfigurations(Collections.singletonList(new Pair<String,Collection<Principal>>("ShibUserPassAuth",
+ validator.setLoginConfigurations(Collections.singletonList(new Pair<String,Collection<Principal>>("ShibUserPassAuth",
Collections.<Principal>singletonList(new TestPrincipal("test2")))));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -200,13 +215,15 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
ac.setAttemptedFlow(authenticationFlows.get(0));
ac.getSubcontext(UsernamePasswordContext.class, true);
- action.setMatchExpression(Pattern.compile("foo.+"));
+ validator.setMatchExpression(Pattern.compile("foo.+"));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
final Event event = action.execute(src);
- ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.REQUEST_UNSUPPORTED);
}
@Test public void testBadUsername() throws Exception {
@@ -215,10 +232,11 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
+
+ validator.initialize();
action.initialize();
doExtract(prc);
@@ -237,10 +255,11 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -260,10 +279,11 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -284,11 +304,12 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
- action.setRemoveContextAfterValidation(false);
+ validator.setRemoveContextAfterValidation(false);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -315,12 +336,13 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
rpc.setOperator("exact");
rpc.setRequestedPrincipals(Collections.<Principal>singletonList(new TestPrincipal("test1")));
- action.setLoginConfigurations(Collections.singletonList(new Pair<String,Collection<Principal>>("ShibUserPassAuth",
+ validator.setLoginConfigurations(Collections.singletonList(new Pair<String,Collection<Principal>>("ShibUserPassAuth",
Collections.<Principal>singletonList(new TestPrincipal("test1")))));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -343,11 +365,12 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setLoginConfigNames(Arrays.asList("ShibBadAuth", "ShibUserPassAuth"));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigNames(Arrays.asList("ShibBadAuth", "ShibUserPassAuth"));
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -368,11 +391,12 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setLoginConfigType("JavaLoginConfig");
- System.out.println(getCurrentDir());
- action.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ validator.setLoginConfigType("JavaLoginConfig");
+ validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
+ '/' + DATA_PATH + "jaas.config")));
- action.setMatchExpression(Pattern.compile(".+_THE_.+"));
+ validator.setMatchExpression(Pattern.compile(".+_THE_.+"));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAPTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAPTest.java
index f26924a..8175e77 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAPTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAPTest.java
@@ -63,12 +63,14 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.sdk.LDAPException;
-/** {@link ValidateUsernamePasswordAgainstLDAP} unit test. */
+/** Unit test for LDAP credential validation. */
public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationContextTest {
private static final String DATA_PATH = "src/test/resources/net/shibboleth/idp/authn/impl/";
- private ValidateUsernamePasswordAgainstLDAP action;
+ private LDAPCredentialValidator validator;
+
+ private ValidateCredentials action;
private InMemoryDirectoryServer directoryServer;
@@ -117,20 +119,25 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
@BeforeMethod public void setUp() throws Exception {
super.setUp();
- action = new ValidateUsernamePasswordAgainstLDAP();
+ validator = new LDAPCredentialValidator();
+ validator.setId("ldaptest");
+
+ action = new ValidateCredentials();
+ action.setValidators(Collections.singletonList(validator));
- Map<String, Collection<String>> mappings = new HashMap<>();
+ final Map<String, Collection<String>> mappings = new HashMap<>();
mappings.put("UnknownUsername", Collections.singleton("DN_RESOLUTION_FAILURE"));
mappings.put("InvalidPassword", Collections.singleton("INVALID_CREDENTIALS"));
mappings.put("ExpiringPassword", Collections.singleton("ACCOUNT_WARNING"));
mappings.put("ExpiredPassword", Arrays.asList("PASSWORD_EXPIRED", "CHANGE_AFTER_RESET"));
action.setClassifiedMessages(mappings);
-
action.setHttpServletRequest(new MockHttpServletRequest());
}
@Test public void testMissingFlow() throws Exception {
- action.setAuthenticator(authenticator);
+ validator.setAuthenticator(authenticator);
+ validator.initialize();
+
action.initialize();
final Event event = action.execute(src);
@@ -139,7 +146,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
@Test public void testMissingUser() throws Exception {
prc.getSubcontext(AuthenticationContext.class).setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(authenticator);
+
+ validator.setAuthenticator(authenticator);
+ validator.initialize();
+
action.initialize();
final Event event = action.execute(src);
@@ -150,7 +160,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
ac.getSubcontext(UsernamePasswordContext.class, true);
- action.setAuthenticator(authenticator);
+
+ validator.setAuthenticator(authenticator);
+ validator.initialize();
+
action.initialize();
final Event event = action.execute(src);
@@ -166,14 +179,17 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
ac.getSubcontext(UsernamePasswordContext.class, true);
- action.setAuthenticator(authenticator);
- action.setMatchExpression(Pattern.compile("foo.+"));
+
+ validator.setAuthenticator(authenticator);
+ validator.setMatchExpression(Pattern.compile("foo.+"));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
final Event event = action.execute(src);
- ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.REQUEST_UNSUPPORTED);
}
@Test public void testBadConfig() throws Exception {
@@ -182,7 +198,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(new Authenticator(new SearchDnResolver(), authHandler));
+
+ validator.setAuthenticator(new Authenticator(new SearchDnResolver(), authHandler));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -207,8 +226,11 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(new Authenticator(dnResolver,
+
+ validator.setAuthenticator(new Authenticator(dnResolver,
new BindAuthenticationHandler(new DefaultConnectionFactory("ldap://unknown:389"))));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -235,7 +257,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(authenticator);
+
+ validator.setAuthenticator(authenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -260,7 +285,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(authenticator);
+
+ validator.setAuthenticator(authenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -277,7 +305,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(authenticator);
+
+ validator.setAuthenticator(authenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -309,7 +340,9 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
response.setAccountState(new PasswordPolicyAccountState(PasswordPolicyControl.Error.PASSWORD_EXPIRED));
}
});
- action.setAuthenticator(errorAuthenticator);
+ validator.setAuthenticator(errorAuthenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -343,7 +376,9 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
new PasswordPolicyAccountState(PasswordPolicyControl.Error.CHANGE_AFTER_RESET));
}
});
- action.setAuthenticator(errorAuthenticator);
+ validator.setAuthenticator(errorAuthenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -387,7 +422,9 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
new AccountState(new AccountState.DefaultWarning(java.util.Calendar.getInstance(), 10)));
}
});
- action.setAuthenticator(warningAuthenticator);
+ validator.setAuthenticator(warningAuthenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -427,7 +464,9 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(authenticator);
+ validator.setAuthenticator(authenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -468,7 +507,9 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(defaultFilterAuthenticator);
+ validator.setAuthenticator(defaultFilterAuthenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -509,7 +550,9 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(defaultFilterAuthenticator);
+ validator.setAuthenticator(defaultFilterAuthenticator);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -545,8 +588,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(authenticator);
- action.setMatchExpression(Pattern.compile(".+_THE_.+"));
+ validator.setAuthenticator(authenticator);
+ validator.setMatchExpression(Pattern.compile(".+_THE_.+"));
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -582,8 +627,10 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
ac.setAttemptedFlow(authenticationFlows.get(0));
- action.setAuthenticator(authenticator);
- action.setRemoveContextAfterValidation(false);
+ validator.setAuthenticator(authenticator);
+ validator.setRemoveContextAfterValidation(false);
+ validator.initialize();
+
action.initialize();
doExtract(prc);
@@ -600,4 +647,5 @@ public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationC
extract.initialize();
extract.execute(src);
}
+
}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml b/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml
index 183ef27..d0627f5 100644
--- a/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml
@@ -44,22 +44,38 @@
p:trim-ref="shibboleth.authn.Password.Trim"
p:transforms-ref="shibboleth.authn.Password.Transforms" />
- <bean id="ValidateUsernamePasswordAgainstJAAS"
- class="net.shibboleth.idp.authn.impl.ValidateUsernamePasswordAgainstJAAS" scope="prototype"
- p:savePasswordToCredentialSet="#{getObject('shibboleth.authn.Password.RetainAsPrivateCredential') ?: false}"
- p:removeContextAfterValidation="#{getObject('shibboleth.authn.Password.RemoveAfterValidation') ?: true}"
+ <bean id="PopulateSubjectCanonicalizationContext"
+ class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
+ p:availableFlows-ref="shibboleth.PostLoginSubjectCanonicalizationFlows" />
+
+ <!-- New action bean that uses CredentialValidator chains. -->
+ <bean id="ValidateCredentials"
+ class="net.shibboleth.idp.authn.impl.ValidateCredentials" scope="prototype"
+ p:validators="#{getObject('shibboleth.authn.Password.Validators') ?: getObject('ValidateUsernamePassword')}"
+ p:addDefaultPrincipals="#{getObject('shibboleth.authn.Password.addDefaultPrincipals') ?:
+ (getObject('shibboleth.authn.Password.PrincipalOverride') == null
+ or getObject('shibboleth.authn.Password.PrincipalOverride').isEmpty())}"
+ p:supportedPrincipals="#{getObject('shibboleth.authn.Password.PrincipalOverride')}"
+ p:classifiedMessages-ref="shibboleth.authn.Password.ClassifiedMessageMap"
+ p:resultCachingPredicate="#{getObject('shibboleth.authn.Password.resultCachingPredicate')}"
+ p:lockoutManager="#{getObject('shibboleth.authn.Password.AccountLockoutManager')}" />
+
+ <!-- New parent bean for defining validators. -->
+
+ <bean id="shibboleth.CredentialValidator" abstract="true"
+ p:savePasswordToCredentialSet="#{getObject('shibboleth.authn.Password.RetainAsPrivateCredential') ?: false}"
+ p:removeContextAfterValidation="#{getObject('shibboleth.authn.Password.RemoveAfterValidation') ?: true}"
+ p:matchExpression="#{getObject('shibboleth.authn.Password.matchExpression')}" />
+
+ <!-- Legacy validators defined under V3 action bean names. -->
+
+ <bean id="ValidateUsernamePasswordAgainstJAAS" parent="shibboleth.CredentialValidator" lazy-init="true"
+ class="net.shibboleth.idp.authn.impl.JAASCredentialValidator"
+ p:id="jaas"
p:loginConfigStrategy="#{getObject('shibboleth.authn.JAAS.LoginConfigStrategy')}"
p:loginConfigNames="#{getObject('shibboleth.authn.JAAS.LoginConfigNames')}"
p:loginConfigurations="#{getObject('shibboleth.authn.JAAS.LoginConfigurations')}"
- p:loginConfigType="JavaLoginConfig"
- p:addDefaultPrincipals="#{getObject('shibboleth.authn.Password.addDefaultPrincipals') ?:
- (getObject('shibboleth.authn.Password.PrincipalOverride') == null
- or getObject('shibboleth.authn.Password.PrincipalOverride').isEmpty())}"
- p:supportedPrincipals="#{getObject('shibboleth.authn.Password.PrincipalOverride')}"
- p:classifiedMessages-ref="shibboleth.authn.Password.ClassifiedMessageMap"
- p:resultCachingPredicate="#{getObject('shibboleth.authn.Password.resultCachingPredicate')}"
- p:matchExpression="#{getObject('shibboleth.authn.Password.matchExpression')}"
- p:lockoutManager="#{getObject('shibboleth.authn.Password.AccountLockoutManager')}">
+ p:loginConfigType="JavaLoginConfig">
<property name="loginConfigParameters">
<bean class="java.security.URIParameter">
<constructor-arg ref="shibboleth.authn.JAAS.JAASConfigURI" />
@@ -67,22 +83,13 @@
</property>
</bean>
- <bean id="ValidateUsernamePasswordAgainstKerberos"
- class="net.shibboleth.idp.authn.impl.ValidateUsernamePasswordAgainstKerberos" scope="prototype"
- p:savePasswordToCredentialSet="#{getObject('shibboleth.authn.Password.RetainAsPrivateCredential') ?: false}"
- p:removeContextAfterValidation="#{getObject('shibboleth.authn.Password.RemoveAfterValidation') ?: true}"
+ <bean id="ValidateUsernamePasswordAgainstKerberos" parent="shibboleth.CredentialValidator" lazy-init="true"
+ class="net.shibboleth.idp.authn.impl.KerberosCredentialValidator"
+ p:id="krb5"
p:refreshKrb5Config-ref="shibboleth.authn.Krb5.RefreshConfig"
p:preserveTicket-ref="shibboleth.authn.Krb5.PreserveTicket"
p:servicePrincipal="#{getObject('shibboleth.authn.Krb5.ServicePrincipal')}"
- p:keytabPath="#{getObject('shibboleth.authn.Krb5.Keytab')}"
- p:addDefaultPrincipals="#{getObject('shibboleth.authn.Password.addDefaultPrincipals') ?:
- (getObject('shibboleth.authn.Password.PrincipalOverride') == null
- or getObject('shibboleth.authn.Password.PrincipalOverride').isEmpty())}"
- p:supportedPrincipals="#{getObject('shibboleth.authn.Password.PrincipalOverride')}"
- p:classifiedMessages-ref="shibboleth.authn.Password.ClassifiedMessageMap"
- p:resultCachingPredicate="#{getObject('shibboleth.authn.Password.resultCachingPredicate')}"
- p:matchExpression="#{getObject('shibboleth.authn.Password.matchExpression')}"
- p:lockoutManager="#{getObject('shibboleth.authn.Password.AccountLockoutManager')}" />
+ p:keytabPath="#{getObject('shibboleth.authn.Krb5.Keytab')}" />
<!-- Parent beans for custom ldaptive CredentialConfig types. -->
<bean id="shibboleth.X509ResourceCredentialConfig"
@@ -90,23 +97,10 @@
<bean id="shibboleth.KeystoreResourceCredentialConfig"
class="net.shibboleth.idp.authn.impl.KeystoreResourceCredentialConfig" abstract="true" />
- <bean id="ValidateUsernamePasswordAgainstLDAP"
- class="net.shibboleth.idp.authn.impl.ValidateUsernamePasswordAgainstLDAP" scope="prototype"
- p:savePasswordToCredentialSet="#{getObject('shibboleth.authn.Password.RetainAsPrivateCredential') ?: false}"
- p:removeContextAfterValidation="#{getObject('shibboleth.authn.Password.RemoveAfterValidation') ?: true}"
+ <bean id="ValidateUsernamePasswordAgainstLDAP" parent="shibboleth.CredentialValidator" lazy-init="true"
+ class="net.shibboleth.idp.authn.impl.LDAPCredentialValidator"
+ p:id="ldap"
p:authenticator-ref="shibboleth.authn.LDAP.authenticator"
- p:addDefaultPrincipals="#{getObject('shibboleth.authn.Password.addDefaultPrincipals') ?:
- (getObject('shibboleth.authn.Password.PrincipalOverride') == null
- or getObject('shibboleth.authn.Password.PrincipalOverride').isEmpty())}"
- p:supportedPrincipals="#{getObject('shibboleth.authn.Password.PrincipalOverride')}"
- p:classifiedMessages-ref="shibboleth.authn.Password.ClassifiedMessageMap"
- p:resultCachingPredicate="#{getObject('shibboleth.authn.Password.resultCachingPredicate')}"
- p:matchExpression="#{getObject('shibboleth.authn.Password.matchExpression')}"
- p:returnAttributes-ref="shibboleth.authn.LDAP.returnAttributes"
- p:lockoutManager="#{getObject('shibboleth.authn.Password.AccountLockoutManager')}" />
-
- <bean id="PopulateSubjectCanonicalizationContext"
- class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
- p:availableFlows-ref="shibboleth.PostLoginSubjectCanonicalizationFlows" />
+ p:returnAttributes-ref="shibboleth.authn.LDAP.returnAttributes" />
</beans>
diff --git a/idp-conf/src/main/resources/system/flows/authn/password-authn-flow.xml b/idp-conf/src/main/resources/system/flows/authn/password-authn-flow.xml
index 87b3c2d..4c7923f 100644
--- a/idp-conf/src/main/resources/system/flows/authn/password-authn-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/password-authn-flow.xml
@@ -80,7 +80,7 @@
</action-state>
<action-state id="ValidateUsernamePassword" parent="authn/conditions#ValidateUsernamePassword">
- <evaluate expression="ValidateUsernamePassword" />
+ <evaluate expression="ValidateCredentials" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="ContinueSuccessfulAuthentication" />
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list