/* * 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 2019-2024 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import java.util.Arrays; import java.util.Collections; 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.operation.UpdatableSASLBindRequest; import com.unboundid.directory.sdk.common.operation.UpdatableSimpleBindRequest; import com.unboundid.directory.sdk.common.types.LogSeverity; import com.unboundid.directory.sdk.common.types.OperationContext; import com.unboundid.directory.sdk.common.types.ServerContext; 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.Control; import com.unboundid.ldap.sdk.DN; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.ByteString; import com.unboundid.util.StaticUtils; import com.unboundid.util.Validator; import com.unboundid.util.args.ArgumentParser; /** * This class provides a Server SDK SASL mechanism handler implementation that * uses attachments on the associated bind operation to specify the behavior * that the server should exhibit when processing the bind operation. This is * only intended to be used internally within the server and should not be * directly requested by an external client. It is primarily expected to be * used in conjunction with a pre-parse bind plugin that performs all of the * authentication processing itself, but then converts the bind request to this * mechanism to use the information provided in attachments to define the result * of the operation processing. * <BR><BR> * This SASL mechanism handler actually implements support for four related SASL * mechanisms: * <BR> * <UL> * <LI> * PING-SECURE-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT: Indicates * that the authentication used a password provided by the client, and that * the password and any other associated credentials were provided in a * secure manner (that is, in a manner that is not vulnerable to * eavesdropping, replay, or alteration attacks). * </LI> * <LI> * PING-NON-SECURE-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT: Indicates * that the authentication used a password provided by the client, and that * the password or any other associated credentials were provided in a * non-secure manner (that is, in a manner that may be vulnerable to * eavesdropping, replay, or alteration attacks). * </LI> * <LI> * PING-SECURE-NON-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT: Indicates * that the authentication did not use a password provided by the client, * and that any credentials used were provided in a secure manner (that is, * in a manner that is not vulnerable to eavesdropping, replay, or * alteration attacks). * </LI> * <LI> * PING-NON-SECURE-NON-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT: * Indicates that the authentication did not use a password provided by the * client, but that it did use credentials that were provided in a * non-secure manner (that is, in a manner that may be vulnerable to * eavesdropping, replay, or alteration attacks). * </LI> * </UL> * <BR><BR> * All four SASL mechanisms implemented by this handler implement support for * the same set of operation attachments. Supported attachments include: * <BR> * <UL> * <LI> * {@code internal-op-sasl-handler-authentication-succeeded} -- indicates * whether the authentication attempt was successful. This attachment is * required, and its value must be a {@code java.lang.Boolean}. * </LI> * <LI> * {@code internal-op-sasl-handler-diagnostic-message} -- a diagnostic * message that should be included in the bind response to the client. This * attachment is optional and may be included regardless of whether the * authentication attempt succeeded or failed. If it is provided, then its * value must be an instance of {@code java.lang.CharSequence}. * </LI> * <LI> * {@code internal-op-sasl-handler-matched-dn} -- the matched DN value that * should be included in the bind response to the client to indicate that a * provided bind DN did not refer to an entry that exists in the server, and * to provide the DN of its closest ancestor that does exist. This * attachment is optional and should only be used if the authentication * attempt failed. If it is provided, then its value must be an instance of * {@code com.unboundid.ldap.sdk.DN}. * </LI> * <LI> * {@code internal-op-sasl-handler-server-sasl-credentials} -- an encoded * set of server SASL credentials that should be included in the bind * response to the client. This attachment is optional and should only be * set if the original bind request was itself a SASL bind request and * server SASL credentials are appropriate for that request. If provided, * the value of the attachment must be an instance of * {@code com.unboundid.asn1.ASN1OctetString}. * </LI> * <LI> * {@code internal-op-sasl-handler-response-controls} -- a set of controls * that should be included in the bind response to the client. This * attachment is optional, and if it is not set, then the SASL mechanism * handler will not add any controls to the response (although other * components of the server may add response controls if appropriate for the * operation). If provided, the value of the attachment must be an instance * of {@code com.unboundid.ldap.sdk.Control[]}. * </LI> * <LI> * {@code internal-op-sasl-handler-bind-dn} -- the DN of the user * attempting to authenticate. This attachment must be set if the * authentication is successful, and it will be set as the authentication * identity for the associated connection (unless the bind request also * included the retain identity request control). Even if the bind attempt * failed, this attachment should be set if it is possible to determine the * identity of the user that was trying to authenticate so that any * appropriate password policy state updates can be applied to that account. * If provided, the value of the attachment must be an instance of * {@code com.unboundid.ldap.sdk.DN}. * </LI> * <LI> * {@code internal-op-sasl-handler-authentication-failure-reason} -- a * message that will not appear in the bind response to the client, but will * be included in the server's access log to provide additional information * about the reason that the authentication attempt failed. This attachment * is optional and should only be used if the authentication attempt failed. * If it is provided, then its value must be an instance of * {@code java.lang.CharSequence}. * </LI> * <LI> * {@code internal-op-sasl-handler-authorization-dn} -- the DN of the user * that is the alternate authorization identity for the bind operation. * This should only be set if the bind attempt succeeded and requested an * alternate authorization identity. If provided, the value of the * attachment must be an instance of {@code com.unboundid.ldap.sdk.DN}. * </LI> * <LI> * {@code internal-op-sasl-handler-password-used} -- the password that the * client provided during the authentication attempt. This attachment is * optional and should only be used if the authentication attempt was * successful and used a password. If provided, the value of the attachment * must be an instance of {@code com.unboundid.asn1.ASN1OctetString}. * </LI> * </UL> */ public final class InternalOperationAttachmentSASLMechanismHandler extends SASLMechanismHandler { /** * The name for the SASL mechanism that indicates that the authentication used * a password provided by the client, and that the password and any other * associated credentials were provided in a secure manner (that is, in a * manner that is not vulnerable to eavesdropping, replay, or alteration * attacks). */ public static final String SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME = "PING-SECURE-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT"; /** * The name for the SASL mechanism that indicates that the authentication used * a password provided by the client, and that the password or any other * associated credentials were provided in a non-secure manner (that is, in a * manner that may be vulnerable to eavesdropping, replay, or alteration * attacks). */ public static final String NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME = "PING-NON-SECURE-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT"; /** * The name for the SASL mechanism that indicates that the authentication did * not use a password provided by the client, and that any credentials used * were provided in a secure manner (that is, in a manner that is not * vulnerable to eavesdropping, replay, or alteration attacks). */ public static final String SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME = "PING-SECURE-NON-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT"; /** * The name for the SASL mechanism that indicates that the authentication did * not use a password provided by the client, but that it did use credentials * that were provided in a non-secure manner (that is, in a manner that may be * vulnerable to eavesdropping, replay, or alteration attacks). */ public static final String NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME = "PING-NON_SECURE-NON-PASSWORD-BASED-INTERNAL-OPERATION-ATTACHMENT"; /** * The name of the attachment used to indicate whether the authentication * attempt was successful. This attachment is required, and its value must be * a {@code java.lang.Boolean}. */ public static final String ATTACHMENT_NAME_AUTHENTICATION_SUCCEEDED = "internal-op-sasl-handler-authentication-succeeded"; /** * The name of the attachment used to hold a diagnostic message that should be * included in the bind response to the client. This attachment is optional * and may be included regardless of whether the authentication attempt * succeeded or failed. If it is provided, then its value must be an instance * of {@code java.lang.CharSequence}. */ public static final String ATTACHMENT_NAME_DIAGNOSTIC_MESSAGE = "internal-op-sasl-handler-diagnostic-message"; /** * The name of the attachment used to hold the matched DN value that should be * included in the bind response to the client to indicate that a provided * bind DN did not refer to an entry that exists in the server, and to provide * the DN of its closest ancestor that does exist. This attachment is * optional and should only be used if the authentication attempt failed. If * it is provided, then its value must be an instance of * {@code com.unboundid.ldap.sdk.DN}. */ public static final String ATTACHMENT_NAME_MATCHED_DN = "internal-op-sasl-handler-matched-dn"; /** * The name of the attachment used to hold an encoded set of server SASL * credentials that should be included in the bind response to the client. * This attachment is optional and should only be set if the original bind * request was itself a SASL bind request and server SASL credentials are * appropriate for that request. If provided, the value of the attachment * must be an instance of {@code com.unboundid.asn1.ASN1OctetString}. */ public static final String ATTACHMENT_NAME_SERVER_SASL_CREDENTIALS = "internal-op-sasl-handler-server-sasl-credentials"; /** * The name of the attachment used to hold a set of controls that should be * included in the bind response to the client. This attachment is optional, * and if it is not set, then the SASL mechanism handler will not add any * controls to the response (although other components of the server may add * response controls if appropriate for the operation). If provided, the * value of the attachment must be an instance of * {@code com.unboundid.ldap.sdk.Control[]}. */ public static final String ATTACHMENT_NAME_RESPONSE_CONTROLS = "internal-op-sasl-handler-response-controls"; /** * The name of the attachment used to hold the DN of the user attempting to * authenticate. This attachment must be set if the authentication is * successful, and it will be set as the authentication identity for the * associated connection (unless the bind request also included the retain * identity request control). Even if the bind attempt failed, this * attachment should be set if it is possible to determine the identity of the * user that was trying to authenticate so that any appropriate password * policy state updates can be applied to that account. If provided, the * value of the attachment must be an instance of * {@code com.unboundid.ldap.sdk.DN}. */ public static final String ATTACHMENT_NAME_BIND_DN = "internal-op-sasl-handler-bind-dn"; /** * The name of the attachment used to hold a message that will not appear in * the bind response to the client, but will be included in the server's * access log to provide additional information about the reason that the * authentication attempt failed. This attachment is optional and should only * be used if the authentication attempt failed. If it is provided, then its * value must be an instance of {@code java.lang.CharSequence}. */ public static final String ATTACHMENT_NAME_AUTHENTICATION_FAILURE_REASON = "internal-op-sasl-handler-authentication-failure-reason"; /** * The name of the attachment used to hold the DN of the user that is the * alternate authorization identity for the bind operation. This should only * be set if the bind attempt succeeded and requested an alternate * authorization identity. If provided, the value of the attachment must be * an instance of {@code com.unboundid.ldap.sdk.DN}. */ public static final String ATTACHMENT_NAME_AUTHORIZATION_DN = "internal-op-sasl-handler-authorization-dn"; /** * The name of the attachment used to hold the password that the client * provided during the authentication attempt. This attachment is optional * and should only be used if the authentication attempt was successful and * used a password. If provided, the value of the attachment must be an * instance of {@code com.unboundid.asn1.ASN1OctetString}. */ public static final String ATTACHMENT_NAME_PASSWORD_USED = "internal-op-sasl-handler-password-used"; // A handle to the server context in which this extension is running. private volatile 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 done in the {@code initializeSASLMechanismHandler} * method. */ public InternalOperationAttachmentSASLMechanismHandler() { // No implementation is required. } /** * Retrieves a human-readable name for this extension. * * @return A human-readable name for this extension. */ @Override() public String getExtensionName() { return "Internal Operation Attachment 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[] { "Implements support for a number of related SASL mechanisms that use " + "operation attachments to define the behavior the server should " + "exhibit when processing the bind operation. This is only " + "intended to be used internally within the server and should not " + "be directly requested by an external client. It is primarily " + "expected to be used in conjunction with a pre-parse bind plugin " + "that performs all of the authentication processing itself, but " + "then converts the bind request to this mechanism to use the " + "information provided in attachments to define the result of the " + "operation processing.", "The " + SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME + " mechanism " + "should be used if the authentication used a password provided by " + "the client, and that the password and any other associated " + "credentials were provided in a secure manner (that is, in a " + "manner that is not vulnerable to eavesdropping, replay, or " + "alteration attacks).", "The " + NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME + " mechanism " + "should be used if the authentication used a password provided by " + "the client, and that the password or any other associated " + "credentials were provided in a non-secure manner (that is, in a " + "manner that may be vulnerable to eavesdropping, replay, or " + "alteration attacks).", "The " + SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME + " mechanism " + "should be used if the authentication did not use a password " + "provided by the client, and that any credentials used were " + "provided in a secure manner (that is, in a manner that is not " + "vulnerable to eavesdropping, replay, or alteration attacks).", "The " + NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME + " mechanism should be used if the authentication did not use a " + "password provided by the client, but that it did use credentials " + "that were provided in a non-secure manner (that is, in a manner " + "that may be vulnerable to eavesdropping, replay, or alteration " + "attacks).", "All four SASL mechanisms implemented by this handler implement " + "support for the same set of operation attachments. Supported " + "attachments include:", "internal-op-sasl-handler-authentication-succeeded -- indicates " + "whether the authentication attempt was successful. This " + "attachment is required, and its value must be an instance of " + Boolean.class.getName() + '.', "internal-op-sasl-handler-diagnostic-message -- a diagnostic message " + "that should be included in the bind response to the client. " + "This attachment is optional and may be included regardless of " + "whether the authentication attempt succeeded or failed. If it " + "is provided, then its value must be an instance of " + CharSequence.class.getName() + '.', "internal-op-sasl-handler-matched-dn -- the matched DN value that " + "should be included in the bind response to the client to " + "indicate that a provided bind DN did not refer to an entry that " + "exists in the server, and to provide the DN of its closest " + "ancestor that does exist. This attachment is optional and " + "should only be used if the authentication attempt failed. If it " + "is provided, then its value must be an instance of " + DN.class.getName() + '.', "internal-op-sasl-handler-server-sasl-credentials -- an encoded set " + "of server SASL credentials that should be included in the bind " + "response to the client. This attachment is optional and should " + "only be set if the original bind request was itself a SASL bind " + "request and server SASL credentials are appropriate for that " + "request. If provided, the value of the attachment must be an " + "instance of " + ASN1OctetString.class.getName() + '.', "internal-op-sasl-handler-response-controls -- a set of controls that " + "should be included in the bind response to the client. This " + "attachment is optional, and if it is not set, then the SASL " + "mechanism handler will not add any controls to the response " + "(although other components of the server may add response " + "controls if appropriate for the operation). If provided, the " + "value of the attachment must be an instance of " + Control.class.getName() + "[].", "internal-op-sasl-handler-bind-dn -- the DN of the user attempting to " + "authenticate. This attachment must be set if the " + "authentication is successful, and it will be set as the " + "authentication identity for the associated connection (unless " + "the bind request also included the retain identity request " + "control). Even if the bind attempt failed, this attachment " + "should be set if it is possible to determine the identity of the " + "user that was trying to authenticate so that any appropriate " + "password policy state updates can be applied to that account. " + "If provided, the value of the attachment must be an instance of " + DN.class.getName() + '.', "internal-op-sasl-handler-authentication-failure-reason -- a message " + "that will not appear in the bind response to the client, but " + "will be included in the server's access log to provide " + "additional information about the reason that the authentication " + "attempt failed. This attachment is optional and should only be " + "used if the authentication attempt failed. If it is provided, " + "then its value must be an instance of " + CharSequence.class.getName() + '.', "internal-op-sasl-handler-authorization-dn -- the DN of the user that " + "is the alternate authorization identity for the bind operation. " + "This should only be set if the bind attempt succeeded and " + "requested an alternate authorization identity. If provided, the " + "value of the attachment must be an instance of " + DN.class.getName() + '.', "internal-op-sasl-handler-password-used -- the password that the " + "client provided during the authentication attempt. This " + "attachment is optional and should only be used if the " + "authentication attempt was successful and used a password. If " + "provided, the value of the attachment must be an instance of " + ASN1OctetString.class.getName() + '.', }; } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this extension. The argument parser may also be * updated to define relationships between arguments (e.g., to specify * required, exclusive, or dependent argument sets). * * @param parser The argument parser to be updated with the configuration * arguments which may be used by this extension. */ @Override() public void defineConfigArguments(final ArgumentParser parser) { // No arguments will be allowed by default. } /** * 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. */ @Override() public void initializeSASLMechanismHandler( final DirectoryServerContext serverContext, final SASLMechanismHandlerConfig config, final ArgumentParser parser) { if (this.serverContext == null) { this.serverContext = serverContext; } } /** * Indicates whether the configuration represented by the provided argument * parser is acceptable for use by this extension. The parser will have been * used to parse any configuration available for this extension, and any * automatic validation will have been performed. This method may be used to * perform any more complex validation which cannot be performed automatically * by the argument parser. * * @param config The general configuration for this extension. * @param parser The argument parser that has been used to * parse the proposed configuration for this * extension. * @param unacceptableReasons A list to which messages may be added to * provide additional information about why the * provided configuration is not acceptable. * * @return {@code true} if the configuration in the provided argument parser * appears to be acceptable, or {@code false} if not. */ @Override() public boolean isConfigurationAcceptable( final SASLMechanismHandlerConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { if (serverContext == null) { serverContext = config.getServerContext(); } // No validation is needed. return true; } /** * Attempts to apply the configuration from the provided argument parser to * this extension. * * @param config The general configuration for this extension. * @param parser The argument parser that has been used to * parse the new configuration for this * extension. * @param adminActionsRequired A list to which messages may be added to * provide additional information about any * additional administrative actions that may * be required to apply some of the * configuration changes. * @param messages A list to which messages may be added to * provide additional information about the * processing performed by this method. * * @return A result code providing information about the result of applying * the configuration change. A result of {@code SUCCESS} should be * used to indicate that all processing completed successfully. Any * other result will indicate that a problem occurred during * processing. */ @Override() public ResultCode applyConfiguration(final SASLMechanismHandlerConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { if (serverContext == null) { serverContext = config.getServerContext(); } // There is no configuration to apply. return ResultCode.SUCCESS; } /** * Performs any cleanup which may be necessary when this SASL mechanism * handler is to be taken out of service. */ @Override() public void finalizeSASLMechanismHandler() { // No finalization is needed. } /** * 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() { return Collections.unmodifiableList(Arrays.asList( SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME, NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME, SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME, NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME)); } /** * 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) { switch (mechanism) { case SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME: case SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME: // These mechanisms are considered secure. return true; case NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME: case NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME: // These mechanisms are considered non-secure. return false; default: // This should never happen. Throw a RuntimeException to ensure that // this is more visible than other types of non-successful operations. final String message = getClass().getName() + ".isSecure called with an unsupported SASL mechanism of '" + mechanism + "'."; if (serverContext != null) { serverContext.logMessage(LogSeverity.SEVERE_WARNING, message); } throw new RuntimeException(message); } } /** * 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) { switch (mechanism) { case SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME: case NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME: // These mechanisms are password-based. return true; case SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME: case NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME: // These mechanisms are not password-based. return false; default: // This should never happen. Throw a RuntimeException to ensure that // this is more visible than other types of non-successful operations. final String message = getClass().getName() + ".isPasswordBased called with an unsupported SASL mechanism " + "of '" + mechanism + "'."; if (serverContext != null) { serverContext.logMessage(LogSeverity.SEVERE_WARNING, message); } throw new RuntimeException(message); } } /** * 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) { final String mechanism = bindRequest.getSASLMechanism(); boolean authenticationSucceeded; final Object authenticationSucceededObject = operationContext.getAttachment( ATTACHMENT_NAME_AUTHENTICATION_SUCCEEDED); if (authenticationSucceededObject == null) { authenticationSucceeded = false; operationContext.appendAdditionalLogMessage("Indicating that the " + "authentication was unsuccessful because the operation was " + "missing the " + ATTACHMENT_NAME_AUTHENTICATION_SUCCEEDED + " attachment required for use with the " + mechanism + " mechanism."); } else if (authenticationSucceededObject instanceof Boolean) { authenticationSucceeded = ((Boolean) authenticationSucceededObject).booleanValue(); } else { authenticationSucceeded = false; operationContext.appendAdditionalLogMessage("Indicating that the " + "authentication was unsuccessful because the " + ATTACHMENT_NAME_AUTHENTICATION_SUCCEEDED + " attachment was an instance of " + authenticationSucceededObject.getClass().getName() + " instead of the required " + Boolean.class.getName() + '.'); } final String diagnosticMessage; final Object diagnosticMessageObject = operationContext.getAttachment(ATTACHMENT_NAME_DIAGNOSTIC_MESSAGE); if (diagnosticMessageObject == null) { diagnosticMessage = null; } else if (diagnosticMessageObject instanceof CharSequence) { diagnosticMessage = String.valueOf(diagnosticMessageObject); } else { diagnosticMessage = null; operationContext.appendAdditionalLogMessage("Ignoring the value of the " + ATTACHMENT_NAME_DIAGNOSTIC_MESSAGE + " attachment because it was an instance of " + diagnosticMessageObject.getClass().getName() + " instead of the required " + CharSequence.class.getName() + '.'); } final String matchedDN; final Object matchedDNObject = operationContext.getAttachment(ATTACHMENT_NAME_MATCHED_DN); if (matchedDNObject == null) { matchedDN = null; } else if (matchedDNObject instanceof DN) { matchedDN = ((DN) matchedDNObject).toString(); } else { matchedDN = null; operationContext.appendAdditionalLogMessage("Ignoring the value of the " + ATTACHMENT_NAME_MATCHED_DN + " attachment because it was an instance of " + matchedDNObject.getClass().getName() + " instead of the required " + DN.class.getName() + '.'); } final ASN1OctetString serverSASLCredentials; final Object serverSASLCredentialsObject = operationContext.getAttachment( ATTACHMENT_NAME_SERVER_SASL_CREDENTIALS); if (serverSASLCredentialsObject == null) { serverSASLCredentials = null; } else if (serverSASLCredentialsObject instanceof ASN1OctetString) { serverSASLCredentials = (ASN1OctetString) serverSASLCredentialsObject; } else { serverSASLCredentials = null; operationContext.appendAdditionalLogMessage("Ignoring the value of the " + ATTACHMENT_NAME_SERVER_SASL_CREDENTIALS + " attachment because it was an instance of " + serverSASLCredentialsObject.getClass().getName() + " instead of the required " + ASN1OctetString.class.getName() + '.'); } final List<Control> responseControls; final Object responseControlsObject = operationContext.getAttachment(ATTACHMENT_NAME_RESPONSE_CONTROLS); if (responseControlsObject == null) { responseControls = null; } else if (responseControlsObject instanceof Control[]) { final Control[] responseControlArray = (Control[]) responseControlsObject; responseControls = Collections.unmodifiableList(Arrays.asList(responseControlArray)); } else { responseControls = null; operationContext.appendAdditionalLogMessage("Ignoring the value of the " + ATTACHMENT_NAME_RESPONSE_CONTROLS + " attachment because it was an instance of " + responseControlsObject.getClass().getName() + " instead of the required " + Control.class.getName() + "[]."); } String authenticationFailureReason; final Object authenticationFailureReasonObject = operationContext.getAttachment( ATTACHMENT_NAME_AUTHENTICATION_FAILURE_REASON); if (authenticationFailureReasonObject == null) { authenticationFailureReason = null; } else if (authenticationFailureReasonObject instanceof CharSequence) { authenticationFailureReason = String.valueOf(authenticationFailureReasonObject); } else { authenticationFailureReason = null; operationContext.appendAdditionalLogMessage("Ignoring the value of the " + ATTACHMENT_NAME_AUTHENTICATION_FAILURE_REASON + " attachment because it was an instance of " + authenticationFailureReasonObject.getClass().getName() + " instead of the required " + CharSequence.class.getName() + '.'); } final String bindDN; final Object bindDNObject = operationContext.getAttachment(ATTACHMENT_NAME_BIND_DN); if (bindDNObject == null) { if (authenticationSucceeded) { bindDN = null; authenticationSucceeded = false; authenticationFailureReason = "The " + mechanism + " bind operation did not have the " + ATTACHMENT_NAME_BIND_DN + " attachment set, which is required " + "for a successful authentication."; } else { bindDN = null; } } else if (bindDNObject instanceof DN) { bindDN = ((DN) bindDNObject).toString(); } else { bindDN = null; if (authenticationSucceeded) { authenticationSucceeded = false; authenticationFailureReason = "The " + ATTACHMENT_NAME_BIND_DN + " attachment was set in the " + mechanism + " bind operation, but its value was of type " + bindDNObject.getClass().getName() + " instead of the required " + "type of " + DN.class.getName() + '.'; } else { operationContext.appendAdditionalLogMessage("Ignoring the value of " + "the " + ATTACHMENT_NAME_BIND_DN + " attachment because it was an instance of " + bindDNObject.getClass().getName() + " instead of the required " + DN.class.getName() + '.'); } } final String authorizationDN; final Object authorizationDNObject = operationContext.getAttachment(ATTACHMENT_NAME_AUTHORIZATION_DN); if (authorizationDNObject == null) { authorizationDN = bindDN; } else if (authorizationDNObject instanceof DN) { authorizationDN = ((DN) authorizationDNObject).toString(); } else { authorizationDN = null; if (authenticationSucceeded) { authenticationSucceeded = false; authenticationFailureReason = "The " + ATTACHMENT_NAME_AUTHORIZATION_DN + " attachment was set in the " + mechanism + " bind operation, but its value was of type " + bindDNObject.getClass().getName() + " instead of the required " + "type of " + DN.class.getName() + '.'; } else { operationContext.appendAdditionalLogMessage("Ignoring the value of " + "the " + ATTACHMENT_NAME_AUTHORIZATION_DN + " attachment because it was an instance of " + authorizationDNObject.getClass().getName() + " instead of the required " + DN.class.getName() + '.'); } } final ASN1OctetString passwordUsed; final Object passwordUsedObject = operationContext.getAttachment(ATTACHMENT_NAME_PASSWORD_USED); if (passwordUsedObject == null) { passwordUsed = null; } else if (passwordUsedObject instanceof ASN1OctetString) { passwordUsed = (ASN1OctetString) passwordUsedObject; } else { passwordUsed = null; operationContext.appendAdditionalLogMessage("Ignoring the value of the " + ATTACHMENT_NAME_PASSWORD_USED + " attachment because it was an instance of " + passwordUsedObject.getClass().getName() + " instead of the required " + ASN1OctetString.class.getName() + '.'); } if (authenticationSucceeded) { return resultFactory.createSuccessResult(bindDN, authorizationDN, diagnosticMessage, responseControls, serverSASLCredentials, passwordUsed); } else { return resultFactory.createFailureResult(authenticationFailureReason, diagnosticMessage, matchedDN, responseControls, serverSASLCredentials, bindDN); } } /** * Indicates whether the specified SASL mechanism may require multiple stages * to process. * * @param mechanism The mechanism for which to make the determination. * * @return {@code true} if the specified SASL mechanism may require multiple * stages to process, {@code false} if not, or {@code null} if the * answer is not known for the specified mechanism. */ @Override() public Boolean isMultiStageMechanism(final String mechanism) { switch (mechanism) { case SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME: case NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME: case SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME: case NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME: // None of these mechanisms involve multiple stages of authentication // processing. return false; default: // This should never happen. Throw a RuntimeException to ensure that // this is more visible than other types of non-successful operations. final String message = getClass().getName() + ".isMultiStageMechanism called with an unsupported SASL " + "mechanism of '" + mechanism + "'."; if (serverContext != null) { serverContext.logMessage(LogSeverity.SEVERE_WARNING, message); } throw new RuntimeException(message); } } /** * 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() { return Collections.singletonMap( Collections.emptyList(), "This SASL mechanism handler does not take any arguments."); } /** * Converts the provided simple bind request to an * {@code UpdatableSASLBindRequest} object that can be used to cause this SASL * mechanism handler to process a successful authentication as the specified * user. All of the appropriate attachments for this SASL mechanism handler * will be set on the associated operation. It will use the following * settings: * <UL> * <LI> * The authentication attempt will be considered successful. * </LI> * <LI> * There will not be any diagnostic message, matched DN, response * controls, or server SASL credentials. * </LI> * <LI> * The bind DN from the simple bind request will be used as both the * authentication and authorization identity for the SASL bind. * </LI> * <LI> * The password used will be the password from the simple bind request. * </LI> * <LI> * The SASL bind will be considered password-based. * </LI> * <LI> * The SASL bind will be considered secure if and only if the simple bind * request was received over a secure connection. * </LI> * </UL> * * @param simpleBindRequest The simple bind request to use to create the * corresponding SASL bind request. It must not * be {@code null}. * @param operationContext The operation context with which the provided * simple bind request is associated. It must not * be {@code null}. * * @return The {@code UpdatableSASLBindRequest} object created from the * provided {@code UpdatableSimpleBindRequest} object. * * @throws LDAPException If a problem is encountered while trying to create * the SASL bind request. */ public static UpdatableSASLBindRequest convertToSuccessfulSASLBindRequest( final UpdatableSimpleBindRequest simpleBindRequest, final OperationContext operationContext) throws LDAPException { Validator.ensureNotNullWithMessage(simpleBindRequest, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.simpleBindRequest must not " + "be null."); Validator.ensureNotNullWithMessage(operationContext, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.operationContext must not be " + "null."); final String diagnosticMessage = null; final List<Control> responseControls = Collections.emptyList(); return convertToSuccessfulSASLBindRequest(simpleBindRequest, operationContext, diagnosticMessage, responseControls); } /** * Converts the provided simple bind request to an * {@code UpdatableSASLBindRequest} object that can be used to cause this SASL * mechanism handler to process a successful authentication as the specified * user. All of the appropriate attachments for this SASL mechanism handler * will be set on the associated operation. It will use the following * settings: * <UL> * <LI> * The authentication attempt will be considered successful. * </LI> * <LI> * There will not be any matched DN or server SASL credentials. * </LI> * <LI> * The bind DN from the simple bind request will be used as both the * authentication and authorization identity for the SASL bind. * </LI> * <LI> * The password used will be the password from the simple bind request. * </LI> * <LI> * The SASL bind will be considered password-based. * </LI> * <LI> * The SASL bind will be considered secure if and only if the simple bind * request was received over a secure connection. * </LI> * </UL> * * @param simpleBindRequest The simple bind request to use to create the * corresponding SASL bind request. It must not * be {@code null}. * @param operationContext The operation context with which the provided * simple bind request is associated. It must not * be {@code null}. * @param diagnosticMessage A diagnostic message to include in the response * to the client. It may be {@code null} if no * diagnostic message should be returned. * @param responseControls A set of controls to include in the response to * the client. It may be {@code null} or empty if * no response controls should be returned. * * @return The {@code UpdatableSASLBindRequest} object created from the * provided information. * * @throws LDAPException If a problem is encountered while trying to create * the SASL bind request. */ public static UpdatableSASLBindRequest convertToSuccessfulSASLBindRequest( final UpdatableSimpleBindRequest simpleBindRequest, final OperationContext operationContext, final String diagnosticMessage, final List<Control> responseControls) throws LDAPException { Validator.ensureNotNullWithMessage(simpleBindRequest, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.simpleBindRequest must not " + "be null."); Validator.ensureNotNullWithMessage(operationContext, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.operationContext must not be " + "null."); final ServerContext serverContext = operationContext.getServerContext(); final boolean isPasswordBased = true; final boolean isSecure = operationContext.isSecure(); final DN authenticationDN; try { authenticationDN = new DN(simpleBindRequest.getDN()); } catch (final LDAPException e) { serverContext.debugCaught(e); throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, "Unable to parse the bind DN from the provided simple bind " + "request: " + StaticUtils.getExceptionMessage(e), e); } final DN authorizationDN = authenticationDN; final ByteString passwordUsed = simpleBindRequest.getPassword(); return convertToSuccessfulSASLBindRequest(simpleBindRequest, operationContext, isPasswordBased, isSecure, authenticationDN, authorizationDN, diagnosticMessage, responseControls, passwordUsed); } /** * Converts the provided simple bind request to an * {@code UpdatableSASLBindRequest} object that can be used to cause this SASL * mechanism handler to process a successful authentication using the provided * information. All of the appropriate attachments for this SASL mechanism * will be set on the associated operation. * * @param simpleBindRequest The simple bind request to use to create the * corresponding SASL bind request. It must not * be {@code null}. * @param operationContext The operation context with which the provided * simple bind request is associated. It must not * be {@code null}. * @param isPasswordBased Indicates whether the resulting SASL bind should * be considered password based. * @param isSecure Indicates whether the resulting SASL bind should * be considered secure. * @param authenticationDN The DN of the authentication identity for the * resulting SASL bind operation. It must not be * {@code null}, but may be {@code DN.NULL_DN} to * indicate an anonymous authentication identity. * @param authorizationDN The DN of the authorization identity for the * resulting SASL bind operation. It must not be * {@code null}, but may be {@code DN.NULL_DN} to * indicate an anonymous authentication identity. * In most cases, this will be the same as the * authentication DN, but they may differ if * operations should be processed under the * authority of a different user than the * authentication identity. * @param diagnosticMessage A diagnostic message to include in the response * to the client. It may be {@code null} if no * diagnostic message should be returned. * @param responseControls A set of controls to include in the response to * the client. It may be {@code null} or empty if * no response controls should be returned. * @param passwordUsed The password used to authenticate the client. * It may be {@code null} if the authentication * was not password-based, or if the password used * is not known. * * @return The {@code UpdatableSASLBindRequest} object created from the * provided information. */ public static UpdatableSASLBindRequest convertToSuccessfulSASLBindRequest( final UpdatableSimpleBindRequest simpleBindRequest, final OperationContext operationContext, final boolean isPasswordBased, final boolean isSecure, final DN authenticationDN, final DN authorizationDN, final String diagnosticMessage, final List<Control> responseControls, final ByteString passwordUsed) { Validator.ensureNotNullWithMessage(simpleBindRequest, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.simpleBindRequest must not " + "be null."); Validator.ensureNotNullWithMessage(operationContext, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.operationContext must not be " + "null."); Validator.ensureNotNullWithMessage(authenticationDN, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.authenticationDN must not be " + "null."); Validator.ensureNotNullWithMessage(authorizationDN, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createSuccessfulSASLBindRequest.authorizationDN must not be " + "null."); final String saslMechanism; if (isPasswordBased) { if (isSecure) { saslMechanism = SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME; } else { saslMechanism = NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME; } } else { if (isSecure) { saslMechanism = SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME; } else { saslMechanism = NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME; } } operationContext.setAttachment(ATTACHMENT_NAME_AUTHENTICATION_SUCCEEDED, Boolean.TRUE); operationContext.setAttachment(ATTACHMENT_NAME_BIND_DN, authenticationDN); if (! authenticationDN.equals(authorizationDN)) { operationContext.setAttachment(ATTACHMENT_NAME_AUTHORIZATION_DN, authorizationDN); } if (diagnosticMessage != null) { operationContext.setAttachment(ATTACHMENT_NAME_DIAGNOSTIC_MESSAGE, diagnosticMessage); } if ((responseControls != null) && (! responseControls.isEmpty())) { final Control[] responseControlArray = new Control[responseControls.size()]; responseControls.toArray(responseControlArray); operationContext.setAttachment(ATTACHMENT_NAME_RESPONSE_CONTROLS, responseControlArray); } if (passwordUsed != null) { operationContext.setAttachment(ATTACHMENT_NAME_PASSWORD_USED, passwordUsed); } return simpleBindRequest.convertToSASLBindRequest(saslMechanism, null); } /** * Converts the provided simple bind request to an * {@code UpdatableSASLBindRequest} object that can be used to cause this SASL * mechanism handler to process a failed authentication attempt . All of the * appropriate attachments for this SASL mechanism handler will be set on the * associated operation. It will use the following settings: * <UL> * <LI> * The authentication attempt will be considered a failure. * </LI> * <LI> * There will not be any diagnostic message, authentication failure * reason, matched DN, response controls, or server SASL credentials. * </LI> * <LI> * The bind DN from the simple bind request will be used as both the * target user DN for the SASL bind. * </LI> * <LI> * The SASL bind will be considered password-based. * </LI> * <LI> * The SASL bind will be considered secure if and only if the simple bind * request was received over a secure connection. * </LI> * </UL> * * @param simpleBindRequest The simple bind request to use to create the * corresponding SASL bind request. It must not * be {@code null}. * @param operationContext The operation context with which the provided * simple bind request is associated. It must not * be {@code null}. * * @return The {@code UpdatableSASLBindRequest} object created from the * provided information. * * @throws LDAPException If a problem is encountered while trying to create * the SASL bind request. */ public static UpdatableSASLBindRequest convertToFailedSASLBindRequest( final UpdatableSimpleBindRequest simpleBindRequest, final OperationContext operationContext) throws LDAPException { Validator.ensureNotNullWithMessage(simpleBindRequest, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createFailedSASLBindRequest.simpleBindRequest must not " + "be null."); Validator.ensureNotNullWithMessage(operationContext, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createFailedSASLBindRequest.operationContext must not be " + "null."); final boolean isPasswordBased = true; final boolean isSecure = operationContext.isSecure(); final String diagnosticMessage = null; final String authenticationFailureReason = null; final DN matchedDN = null; final DN targetUserDN = null; final List<Control> responseControls = Collections.emptyList(); return convertToFailedSASLBindRequest(simpleBindRequest, operationContext, isPasswordBased, isSecure, diagnosticMessage, authenticationFailureReason, matchedDN, targetUserDN, responseControls); } /** * Converts the provided simple bind request to an * {@code UpdatableSASLBindRequest} object that can be used to cause this SASL * mechanism handler to process a failed authentication attempt using the * provided information. All of the appropriate attachments for this SASL * mechanism will be set on the associated operation. * * @param simpleBindRequest The simple bind request to use to * create the corresponding SASL bind * request. It must not be {@code null}. * @param operationContext The operation context with which the * provided simple bind request is * associated. It must not be * {@code null}. * @param isPasswordBased Indicates whether the resulting SASL * bind should be considered password * based. * @param isSecure Indicates whether the resulting SASL * bind should be considered secure. * @param diagnosticMessage A diagnostic message to include in the * response to the client. It may be * {@code null} if no diagnostic message * should be returned. * @param authenticationFailureReason A message that describes the reason * for the authentication failure. This * message will not be returned to the * client, but will be included in the * access log message for the operation * so that administrators may better * diagnose the failure. It may be * {@code null} if no authentication * failure reason is available. * @param matchedDN The matched DN to include in the * response to the client. It may be * {@code null} if no matched DN should * be included. * @param targetUserDN The DN of the user that attempted to * authenticate. It may be {@code null} * if the target user DN is not known. * @param responseControls A set of controls to include in the * response to the client. It may be * {@code null} or empty if no response * controls should be returned. * * @return The {@code UpdatableSASLBindRequest} object created from the * provided information. */ public static UpdatableSASLBindRequest convertToFailedSASLBindRequest( final UpdatableSimpleBindRequest simpleBindRequest, final OperationContext operationContext, final boolean isPasswordBased, final boolean isSecure, final String diagnosticMessage, final String authenticationFailureReason, final DN matchedDN, final DN targetUserDN, final List<Control> responseControls) { Validator.ensureNotNullWithMessage(simpleBindRequest, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createFailedSASLBindRequest.simpleBindRequest must not " + "be null."); Validator.ensureNotNullWithMessage(operationContext, InternalOperationAttachmentSASLMechanismHandler.class.getName() + ".createFailedSASLBindRequest.operationContext must not be " + "null."); final String saslMechanism; if (isPasswordBased) { if (isSecure) { saslMechanism = SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME; } else { saslMechanism = NON_SECURE_PASSWORD_BASED_SASL_MECHANISM_NAME; } } else { if (isSecure) { saslMechanism = SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME; } else { saslMechanism = NON_SECURE_NON_PASSWORD_BASED_SASL_MECHANISM_NAME; } } operationContext.setAttachment(ATTACHMENT_NAME_AUTHENTICATION_SUCCEEDED, Boolean.FALSE); if (diagnosticMessage != null) { operationContext.setAttachment(ATTACHMENT_NAME_DIAGNOSTIC_MESSAGE, diagnosticMessage); } if (authenticationFailureReason != null) { operationContext.setAttachment( ATTACHMENT_NAME_AUTHENTICATION_FAILURE_REASON, authenticationFailureReason); } if (matchedDN != null) { operationContext.setAttachment(ATTACHMENT_NAME_MATCHED_DN, matchedDN); } if (targetUserDN != null) { operationContext.setAttachment(ATTACHMENT_NAME_BIND_DN, targetUserDN); } if ((responseControls != null) && (! responseControls.isEmpty())) { final Control[] responseControlArray = new Control[responseControls.size()]; responseControls.toArray(responseControlArray); operationContext.setAttachment(ATTACHMENT_NAME_RESPONSE_CONTROLS, responseControlArray); } return simpleBindRequest.convertToSASLBindRequest(saslMechanism, null); } }