/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * docs/licenses/cddl.txt * or http://www.opensource.org/licenses/cddl1.php. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * docs/licenses/cddl.txt. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2012-2024 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.unboundid.asn1.ASN1OctetString; import com.unboundid.directory.sdk.common.operation.SASLBindRequest; import com.unboundid.directory.sdk.common.types.ClientContext; import com.unboundid.directory.sdk.common.types.Entry; import com.unboundid.directory.sdk.common.types.OperationContext; import com.unboundid.directory.sdk.ds.api.SASLMechanismHandler; import com.unboundid.directory.sdk.ds.config.SASLMechanismHandlerConfig; import com.unboundid.directory.sdk.ds.types.SASLBindResult; import com.unboundid.directory.sdk.ds.types.SASLBindResultFactory; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.StaticUtils; import com.unboundid.util.args.ArgumentParser; /** * This class provides an implementation of a SASL mechanism handler that may be * used to test multi-stage bind processing. The bind should be processed in * three stages: * <OL> * <LI>The client sends a SASL bind request with no credentials. The server * returns a response with a result code of SASL_BIND_IN_PROGRESS and * credentials that are a prompt for the username.</LI> * <LI>The client sends a SASL bind request in which the credentials are the * string representation of the username for the user to authenticate. * The server returns a response with a result code of * SASL_BIND_IN_PROGRESS and credentials that are a prompt for the * password.</LI> * <LI>The client sends a SASL bind request in which the credentials are the * password for the specified user. The server then verifies the username * and password and returns either a success or failure result.</LI> * </OL> */ public final class ExampleMultiStageSASLMechanismHandler extends SASLMechanismHandler { /** * Creates a new instance of this SASL mechanism handler. All SASL mechanism * handler implementations must include a default constructor, but any * initialization should generally be done in the * {@code initializeSASLMechanismHandler} method. */ public ExampleMultiStageSASLMechanismHandler() { // No implementation is required. } /** * {@inheritDoc} */ @Override() public String getExtensionName() { return "Multi-Stage SASL Mechanism Handler"; } /** * {@inheritDoc} */ @Override() public String[] getExtensionDescription() { return new String[] { "Tests the ability to perform multi-stage SASL authentication." }; } /** * {@inheritDoc} */ @Override() public boolean isConfigurationAcceptable( final SASLMechanismHandlerConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { // Make sure that an identity mapper is included in the configuration. if (config.getIdentityMapperConfigEntryDN() == null) { unacceptableReasons.add("The EXAMPLE-MULTI-STAGE-USERNAME-AND-PASSWORD " + "SASL mechanism handler must be configured with an identity " + "mapper."); return false; } return true; } /** * {@inheritDoc} */ @Override() public ResultCode applyConfiguration(final SASLMechanismHandlerConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { return ResultCode.SUCCESS; } /** * {@inheritDoc} */ @Override() public List<String> getSASLMechanismNames() { return Arrays.asList("EXAMPLE-MULTI-STAGE-USERNAME-AND-PASSWORD"); } /** * {@inheritDoc} */ @Override() public boolean isSecure(final String mechanism) { // This mechanism does not make any attempt to protect the credentials, so // it is not inherently secure. return false; } /** * {@inheritDoc} */ @Override() public boolean isPasswordBased(final String mechanism) { // This mechanism does rely on a password. return true; } /** * {@inheritDoc} */ @Override() public SASLBindResult processSASLBind(final OperationContext operationContext, final SASLBindRequest bindRequest, final SASLBindResultFactory resultFactory) { // Get the client context, since we'll need to use it to get and set state // information. final ClientContext clientContext = operationContext.getClientContext(); // Get the SASL credentials from the bind request. If there are none, then // this must be the first stage of the multi-stage bind, and we should send // back a continuation result with server SASL credentials requesting the // username. final ASN1OctetString saslCredentials = bindRequest.getSASLCredentials(); if (saslCredentials == null) { clientContext.setSASLAuthStateInfo(null); return resultFactory.createContinuationResult(null, null, new ASN1OctetString("Username")); } // The request included SASL credentials, so it's either the username or // the password. If there is no stored state, then it should be the // username, and we should store that as the state and send back another // continuation result with server SASL credentials requesting the password. final Object saslState = clientContext.getSASLAuthStateInfo(); if (saslState == null) { clientContext.setSASLAuthStateInfo(saslCredentials.stringValue()); return resultFactory.createContinuationResult(null, null, new ASN1OctetString("Password")); } // If we have stored state, then the stored state should be the username, // and the new SASL credentials should be the password. Clear the SASL // state and do the bind processing. final String username = (String) saslState; final ASN1OctetString password = saslCredentials; clientContext.setSASLAuthStateInfo(null); final Entry userEntry; try { userEntry = resultFactory.mapUsernameToEntry(username); } catch (final LDAPException le) { operationContext.getServerContext().debugCaught(le); return resultFactory.createFailureResult( "Cannot map username '" + username + "' to a user entry: " + StaticUtils.getExceptionMessage(le), null, null, null, null, null); } try { if (resultFactory.isUserPasswordValid(userEntry.getDN(), password)) { return resultFactory.createSuccessResult(userEntry.getDN()); } else { return resultFactory.createFailureResult( "The provided password was not valid for user '" + userEntry.getDN() + "'.", null, null, null, null, userEntry.getDN()); } } catch (final LDAPException le) { operationContext.getServerContext().debugCaught(le); return resultFactory.createFailureResult( "Unable to determine whether the provided password is valid for " + "user '" + userEntry.getDN() + "': " + StaticUtils.getExceptionMessage(le), null, null, null, null, userEntry.getDN()); } } /** * Retrieves a map containing examples of configurations that may be used for * this extension. The map key should be a list of sample arguments, and the * corresponding value should be a description of the behavior that will be * exhibited by the extension when used with that configuration. * * @return A map containing examples of configurations that may be used for * this extension. It may be {@code null} or empty if there should * not be any example argument sets. */ @Override() public Map<List<String>,String> getExamplesArgumentSets() { // This SASL mechanism handler doesn't take any arguments. final LinkedHashMap<List<String>,String> exampleArgs = new LinkedHashMap<List<String>,String>(1); exampleArgs.put( new ArrayList<String>(0), "Configure the SASL mechanism handler without any arguments, as " + "none are needed for this implementation."); return exampleArgs; } /** * Indicates whether the specified SASL mechanism, which is listed as * supported by this handler, is available for use by the given user. * * @param mechanism The name of the SASL mechanism for which to make * the determination. It will be one of the * mechanisms for which this handler is registered. * @param passwordAttribute The name of the attribute used to hold the * password for the user. * @param userEntry The entry for the user for whom to make the * determination. * * @return {@code true} if the SASL mechanism is supported for the user, * {@code false} if not, or {@code null} if it could not be * determined. */ @Override() public Boolean isMechanismAvailableForUser(final String mechanism, final String passwordAttribute, final Entry userEntry) { // Make sure that the user has a password. return userEntry.hasAttribute(passwordAttribute); } }