/*
* 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
*
*
* Copyright 2012-2016 UnboundID Corp.
*/
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;
}
}
|