/*
* 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 2010-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.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.DirectoryServerContext;
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 a simple implementation of a SASL mechanism handler
* that authenticates a user with a simple username and password (much like SASL
* PLAIN, but without the need to specify the username as an authorization ID,
* and without support for an alternate authorization identity). The SASL
* credentials should simply be a string in the form "username:password". It
* does not take any arguments.
*/
public final class ExampleSASLMechanismHandler
extends SASLMechanismHandler
{
// The server context for this extension.
private DirectoryServerContext serverContext = null;
/**
* 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 performed in the
* {@code initializeSASLMechanismHandler} method.
*/
public ExampleSASLMechanismHandler()
{
// No implementation required.
}
/**
* Retrieves a human-readable name for this extension.
*
* @return A human-readable name for this extension.
*/
@Override()
public String getExtensionName()
{
return "Example SASL Mechanism Handler";
}
/**
* Retrieves a human-readable description for this extension. Each element
* of the array that is returned will be considered a separate paragraph in
* generated documentation.
*
* @return A human-readable description for this extension, or {@code null}
* or an empty array if no description should be available.
*/
@Override()
public String[] getExtensionDescription()
{
return new String[]
{
"This SASL mechanism handler serves as an example that may be used " +
"to demonstrate the process for creating a third-party SASL " +
"mechanism handler. It uses a SASL mechanism name of " +
"'EXAMPLE-USERNAME-AND-PASSWORD' and requires SASL credentials " +
"to be in the form 'username:password'. The SASL mechanism " +
"handler must be configured with an identity mapper, which will " +
"be used to map the username to the corresponding user entry."
};
}
/**
* Initializes this SASL mechanism handler.
*
* @param serverContext A handle to the server context for the server in
* which this extension is running.
* @param config The general configuration for this SASL mechanism
* handler.
* @param parser The argument parser which has been initialized from
* the configuration for this SASL mechanism handler.
*
* @throws LDAPException If a problem occurs while initializing this SASL
* mechanism handler.
*/
@Override()
public void initializeSASLMechanismHandler(
final DirectoryServerContext serverContext,
final SASLMechanismHandlerConfig config,
final ArgumentParser parser)
throws LDAPException
{
this.serverContext = serverContext;
}
/**
* {@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-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;
}
/**
* Retrieves a list of the names of the SASL mechanisms supported by this
* SASL mechanism handler. This method will be invoked only immediately after
* the {@link #initializeSASLMechanismHandler} method is called.
*
* @return A list of the names of the SASL mechanisms supported by this SASL
* mechanism handler.
*/
@Override()
public List<String> getSASLMechanismNames()
{
// This handler only implements support for one SASL mechanism.
return Arrays.asList("EXAMPLE-USERNAME-AND-PASSWORD");
}
/**
* Indicates whether the SASL authentication process using the specified
* mechanism may be considered secure (i.e., that a third party able to
* observe the communication, potentially over an insecure communication
* channel, would not be able to reproduce the authentication process).
*
* @param mechanism The name of the mechanism for which to make the
* determination. This will only be invoked with names of
* mechanisms returned by the
* {@link #getSASLMechanismNames} method.
*
* @return {@code true} if the specified SASL mechanism should be considered
* secure, or {@code false} if not.
*/
@Override()
public boolean isSecure(final String mechanism)
{
// This SASL mechanism isn't inherently secure, since the credentials aren't
// obscured in any way. It would only be secure if used over an encrypted
// connection (e.g., using SSL or StartTLS).
return false;
}
/**
* Indicates whether the SASL authentication process using the specified
* mechanism involves the use of a password stored locally in the server
* (optionally in combination with other forms of credentials).
*
* @param mechanism The name of the mechanism for which to make the
* determination. This will only be invoked with names of
* mechanisms returned by the
* {@link #getSASLMechanismNames} method.
*
* @return {@code true} if the specified SASL mechanism makes use of a local
* password, or {@code false} if not.
*/
@Override()
public boolean isPasswordBased(final String mechanism)
{
// This SASL mechanism is password based.
return true;
}
/**
* Performs the appropriate processing for the provided SASL bind request.
*
* @param operationContext The context for the bind operation.
* @param bindRequest The SASL bind request to be processed.
* @param resultFactory A factory object that will be used to construct
* the result to return.
*
* @return An object with information about the result of the SASL bind
* processing.
*/
@Override()
public SASLBindResult processSASLBind(final OperationContext operationContext,
final SASLBindRequest bindRequest,
final SASLBindResultFactory resultFactory)
{
// The bind request must include SASL credentials.
final ASN1OctetString saslCredentials = bindRequest.getSASLCredentials();
if (saslCredentials == null)
{
return resultFactory.createFailureResult(
"No SASL credentials included in the bind request.",
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
null); // No unsuccessfully authenticated user DN is available.
}
// Extract the username and password elements from the SASL credentials
final String credString = saslCredentials.stringValue();
final int colonPos = credString.indexOf(':');
if (colonPos < 0)
{
return resultFactory.createFailureResult(
"Malformed SASL credentials: no colon to separate the username " +
"from the password.",
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
null); // No unsuccessfully authenticated user DN is available.
}
final String username = credString.substring(0, colonPos);
if (username.length() == 0)
{
return resultFactory.createFailureResult(
"Malformed SASL credentials: Empty username.",
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
null); // No unsuccessfully authenticated user DN is available.
}
final String password = credString.substring(colonPos+1);
if (password.length() == 0)
{
return resultFactory.createFailureResult(
"Malformed SASL credentials: Empty password.",
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
null); // No unsuccessfully authenticated user DN is available.
}
// Get the entry that corresponds to the provided username.
final Entry userEntry;
try
{
userEntry = resultFactory.mapUsernameToEntry(username);
if (userEntry == null)
{
return resultFactory.createFailureResult(
"Could not find entry for username '" + username + "'.",
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
null); // No unsuccessfully authenticated user DN is available.
}
else
{
serverContext.debugInfo("Successfully mapped username '" + username +
"' to entry '" + userEntry.getDN() + "'.");
}
}
catch (final LDAPException le)
{
serverContext.debugCaught(le);
return resultFactory.createFailureResult(
"Could not find entry for username '" + username + "': " +
StaticUtils.getExceptionMessage(le),
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
null); // No unsuccessfully authenticated user DN is available.
}
// Make sure that the password is valid for the user.
try
{
final ASN1OctetString pw = new ASN1OctetString(password);
if (! resultFactory.isUserPasswordValid(userEntry.getDN(), pw))
{
return resultFactory.createFailureResult(
"The supplied password was not valid for user '" +
userEntry.getDN() + "'.",
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
userEntry.getDN());
}
}
catch (final LDAPException le)
{
serverContext.debugCaught(le);
return resultFactory.createFailureResult(
"Unable to verify password for user '" + userEntry.getDN() + "': " +
StaticUtils.getExceptionMessage(le),
null, // No message should be returned to the client.
null, // No matched DN is needed.
null, // No controls are needed.
null, // No server SASL credentials are required.
userEntry.getDN());
}
// If we've gotten here, then the authentication is considered successful.
return resultFactory.createSuccessResult(userEntry.getDN(),
userEntry.getDN(), null, null, null, new ASN1OctetString(password));
}
/**
* 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;
}
}
|