/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.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 2007-2010 Sun Microsystems, Inc.
* Portions Copyright 2013-2015 ForgeRock AS
*/
package org.opends.server.core;
import java.util.ArrayList;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.api.ClientConnection;
import org.opends.server.types.*;
import org.opends.server.types.operation.PreParseBindOperation;
import org.opends.server.workflowelement.localbackend.LocalBackendBindOperation;
import static org.forgerock.opendj.ldap.ResultCode.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.loggers.AccessLogger.*;
import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*;
/**
* This class defines an operation that may be used to authenticate a user to
* the Directory Server. Note that for security restrictions, response messages
* that may be returned to the client must be carefully cleaned to ensure that
* they do not provide a malicious client with information that may be useful in
* an attack. This does impact the debuggability of the server, but that can
* be addressed by calling the <CODE>setAuthFailureReason</CODE> method, which
* can provide a reason for a failure in a form that will not be returned to the
* client but may be written to a log file.
*/
public class BindOperationBasis
extends AbstractOperation
implements BindOperation, PreParseBindOperation
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** The credentials used for SASL authentication. */
private ByteString saslCredentials;
/** The server SASL credentials provided to the client in the response. */
private ByteString serverSASLCredentials;
/** The authentication info for this bind operation. */
private AuthenticationInfo authInfo;
/** The authentication type used for this bind operation. */
private AuthenticationType authType;
/** The raw, unprocessed bind DN as contained in the client request. */
private ByteString rawBindDN;
/** The password used for simple authentication. */
private ByteString simplePassword;
/** The bind DN used for this bind operation. */
private DN bindDN;
/** The DN of the user entry that is attempting to authenticate. */
private DN userEntryDN;
/**
* The DN of the user as whom a SASL authentication was attempted (regardless
* of whether the authentication was successful) for the purpose of updating
* password policy state information.
*/
private Entry saslAuthUserEntry;
/** The set of response controls for this bind operation. */
private final List<Control> responseControls = new ArrayList<>(0);
/** A message explaining the reason for the authentication failure. */
private LocalizableMessage authFailureReason;
/** The SASL mechanism used for SASL authentication. */
private String saslMechanism;
/**
* A string representation of the protocol version for this bind operation.
*/
private String protocolVersion;
/**
* Creates a new simple bind operation with the provided information.
*
* @param clientConnection The client connection with which this operation
* is associated.
* @param operationID The operation ID for this operation.
* @param messageID The message ID of the request with which this
* operation is associated.
* @param requestControls The set of controls included in the request.
* @param protocolVersion The string representation of the protocol version
* associated with this bind request.
* @param rawBindDN The raw, unprocessed bind DN as provided in the
* request from the client.
* @param simplePassword The password to use for the simple
* authentication.
*/
public BindOperationBasis(ClientConnection clientConnection, long operationID,
int messageID, List<Control> requestControls,
String protocolVersion, ByteString rawBindDN,
ByteString simplePassword)
{
super(clientConnection, operationID, messageID, requestControls);
this.protocolVersion = protocolVersion;
setRawBindDN(rawBindDN);
setSimplePassword(simplePassword);
cancelResult = getBindCancelResult();
}
/**
* Creates a new SASL bind operation with the provided information.
*
* @param clientConnection The client connection with which this operation
* is associated.
* @param operationID The operation ID for this operation.
* @param messageID The message ID of the request with which this
* operation is associated.
* @param requestControls The set of controls included in the request.
* @param protocolVersion The string representation of the protocol version
* associated with this bind request.
* @param rawBindDN The raw, unprocessed bind DN as provided in the
* request from the client.
* @param saslMechanism The SASL mechanism included in the request.
* @param saslCredentials The optional SASL credentials included in the
* request.
*/
public BindOperationBasis(ClientConnection clientConnection, long operationID,
int messageID, List<Control> requestControls,
String protocolVersion, ByteString rawBindDN,
String saslMechanism, ByteString saslCredentials)
{
super(clientConnection, operationID, messageID, requestControls);
this.protocolVersion = protocolVersion;
this.authType = AuthenticationType.SASL;
this.saslMechanism = saslMechanism;
this.saslCredentials = saslCredentials;
setRawBindDN(rawBindDN);
cancelResult = getBindCancelResult();
}
/**
* Creates a new simple bind operation with the provided information.
*
* @param clientConnection The client connection with which this operation
* is associated.
* @param operationID The operation ID for this operation.
* @param messageID The message ID of the request with which this
* operation is associated.
* @param requestControls The set of controls included in the request.
* @param protocolVersion The string representation of the protocol version
* associated with this bind request.
* @param bindDN The bind DN for this bind operation.
* @param simplePassword The password to use for the simple
* authentication.
*/
public BindOperationBasis(ClientConnection clientConnection, long operationID,
int messageID, List<Control> requestControls,
String protocolVersion, DN bindDN,
ByteString simplePassword)
{
super(clientConnection, operationID, messageID, requestControls);
this.protocolVersion = protocolVersion;
this.bindDN = bindDN;
rawBindDN = computeRawBindDN(bindDN);
setSimplePassword(simplePassword);
cancelResult = getBindCancelResult();
}
/**
* Creates a new SASL bind operation with the provided information.
*
* @param clientConnection The client connection with which this operation
* is associated.
* @param operationID The operation ID for this operation.
* @param messageID The message ID of the request with which this
* operation is associated.
* @param requestControls The set of controls included in the request.
* @param protocolVersion The string representation of the protocol version
* associated with this bind request.
* @param bindDN The bind DN for this bind operation.
* @param saslMechanism The SASL mechanism included in the request.
* @param saslCredentials The optional SASL credentials included in the
* request.
*/
public BindOperationBasis(ClientConnection clientConnection, long operationID,
int messageID, List<Control> requestControls,
String protocolVersion, DN bindDN,
String saslMechanism, ByteString saslCredentials)
{
super(clientConnection, operationID, messageID, requestControls);
this.protocolVersion = protocolVersion;
this.authType = AuthenticationType.SASL;
this.bindDN = bindDN;
this.saslMechanism = saslMechanism;
this.saslCredentials = saslCredentials;
rawBindDN = computeRawBindDN(bindDN);
cancelResult = getBindCancelResult();
}
private ByteString computeRawBindDN(DN bindDN)
{
if (bindDN != null)
{
return ByteString.valueOfUtf8(bindDN.toString());
}
return ByteString.empty();
}
private CancelResult getBindCancelResult()
{
return new CancelResult(CANNOT_CANCEL, ERR_CANNOT_CANCEL_BIND.get());
}
/** {@inheritDoc} */
@Override
public DN getProxiedAuthorizationDN()
{
return null;
}
/** {@inheritDoc} */
@Override
public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
{
}
/** {@inheritDoc} */
@Override
public final AuthenticationType getAuthenticationType()
{
return authType;
}
/** {@inheritDoc} */
@Override
public final ByteString getRawBindDN()
{
return rawBindDN;
}
/** {@inheritDoc} */
@Override
public final void setRawBindDN(ByteString rawBindDN)
{
if (rawBindDN != null)
{
this.rawBindDN = rawBindDN;
}
else
{
this.rawBindDN = ByteString.empty();
}
bindDN = null;
}
/** {@inheritDoc} */
@Override
public final DN getBindDN()
{
try
{
if (bindDN == null)
{
bindDN = DN.decode(rawBindDN);
}
}
catch (DirectoryException de)
{
logger.traceException(de);
setResultCode(ResultCode.INVALID_CREDENTIALS);
setAuthFailureReason(de.getMessageObject());
}
return bindDN;
}
/** {@inheritDoc} */
@Override
public final ByteString getSimplePassword()
{
return simplePassword;
}
/** {@inheritDoc} */
@Override
public final void setSimplePassword(ByteString simplePassword)
{
if (simplePassword != null)
{
this.simplePassword = simplePassword;
}
else
{
this.simplePassword = ByteString.empty();
}
authType = AuthenticationType.SIMPLE;
saslMechanism = null;
saslCredentials = null;
}
/** {@inheritDoc} */
@Override
public final String getSASLMechanism()
{
return saslMechanism;
}
/** {@inheritDoc} */
@Override
public final ByteString getSASLCredentials()
{
return saslCredentials;
}
/** {@inheritDoc} */
@Override
public final void setSASLCredentials(String saslMechanism,
ByteString saslCredentials)
{
this.saslMechanism = saslMechanism;
this.saslCredentials = saslCredentials;
authType = AuthenticationType.SASL;
simplePassword = null;
}
/** {@inheritDoc} */
@Override
public final ByteString getServerSASLCredentials()
{
return serverSASLCredentials;
}
/** {@inheritDoc} */
@Override
public final void setServerSASLCredentials(ByteString serverSASLCredentials)
{
this.serverSASLCredentials = serverSASLCredentials;
}
/** {@inheritDoc} */
@Override
public final Entry getSASLAuthUserEntry()
{
return saslAuthUserEntry;
}
/** {@inheritDoc} */
@Override
public final void setSASLAuthUserEntry(Entry saslAuthUserEntry)
{
this.saslAuthUserEntry = saslAuthUserEntry;
}
/** {@inheritDoc} */
@Override
public final LocalizableMessage getAuthFailureReason()
{
return authFailureReason;
}
/** {@inheritDoc} */
@Override
public final void setAuthFailureReason(LocalizableMessage message)
{
if (DirectoryServer.returnBindErrorMessages())
{
appendErrorMessage(message);
}
else
{
authFailureReason = message;
}
}
/** {@inheritDoc} */
@Override
public final DN getUserEntryDN()
{
return userEntryDN;
}
/** {@inheritDoc} */
@Override
public final AuthenticationInfo getAuthenticationInfo()
{
return authInfo;
}
/** {@inheritDoc} */
@Override
public final void setAuthenticationInfo(AuthenticationInfo authInfo)
{
this.authInfo = authInfo;
}
/** {@inheritDoc} */
@Override
public final OperationType getOperationType()
{
// Note that no debugging will be done in this method because it is a likely
// candidate for being called by the logging subsystem.
return OperationType.BIND;
}
/** {@inheritDoc} */
@Override
public final List<Control> getResponseControls()
{
return responseControls;
}
/** {@inheritDoc} */
@Override
public final void addResponseControl(Control control)
{
responseControls.add(control);
}
/** {@inheritDoc} */
@Override
public final void removeResponseControl(Control control)
{
responseControls.remove(control);
}
/** {@inheritDoc} */
@Override
public final void toString(StringBuilder buffer)
{
buffer.append("BindOperation(connID=");
buffer.append(clientConnection.getConnectionID());
buffer.append(", opID=");
buffer.append(operationID);
buffer.append(", protocol=\"");
buffer.append(clientConnection.getProtocol());
buffer.append(" ");
buffer.append(protocolVersion);
buffer.append(", dn=");
buffer.append(rawBindDN);
buffer.append(", authType=");
buffer.append(authType);
buffer.append(")");
}
/** {@inheritDoc} */
@Override
public void setUserEntryDN(DN userEntryDN)
{
this.userEntryDN = userEntryDN;
}
/** {@inheritDoc} */
@Override
public String getProtocolVersion()
{
return protocolVersion;
}
/** {@inheritDoc} */
@Override
public void setProtocolVersion(String protocolVersion)
{
this.protocolVersion = protocolVersion;
}
/** {@inheritDoc} */
@Override
public final void run()
{
// Start the processing timer and initially set the result to indicate that
// the result is unknown.
setResultCode(ResultCode.UNDEFINED);
setProcessingStartTime();
logBindRequest(this);
// Wipe out any existing authentication for the client connection and create
// a placeholder that will be used if the bind is successful.
ClientConnection clientConnection = getClientConnection();
clientConnection.setUnauthenticated();
// Abandon any operations that may be in progress for the client.
LocalizableMessage cancelReason = INFO_CANCELED_BY_BIND_REQUEST.get();
CancelRequest cancelRequest = new CancelRequest(true, cancelReason);
clientConnection.cancelAllOperationsExcept(cancelRequest, getMessageID());
// This flag is set to true as soon as a workflow has been executed.
boolean workflowExecuted = false;
try
{
// Invoke the pre-parse bind plugins.
if (!processOperationResult(getPluginConfigManager().invokePreParseBindPlugins(this)))
{
return;
}
// Process the bind DN to convert it from the raw form as provided by the
// client into the form required for the rest of the bind processing.
DN bindDN = getBindDN();
if (bindDN == null){
return;
}
// If this is a simple bind
// Then check whether the bind DN is actually one of the alternate root DNs
// defined in the server. If so, then replace it with the actual DN
// for that user.
switch (getAuthenticationType())
{
case SIMPLE:
DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
if (actualRootDN != null)
{
bindDN = actualRootDN;
}
}
workflowExecuted = execute(this, bindDN);
}
catch(CanceledOperationException coe)
{
// This shouldn't happen for bind operations. Just cancel anyways
logger.traceException(coe);
setResultCode(ResultCode.CANCELLED);
appendErrorMessage(cancelRequest.getCancelReason());
}
finally
{
setProcessingStopTime();
logBindResponse(this);
// Send the bind response to the client.
clientConnection.sendResponse(this);
// If the bind processing is finished, then unset the "bind in progress"
// flag to allow other operations to be processed on the connection.
if (getResultCode() != ResultCode.SASL_BIND_IN_PROGRESS)
{
clientConnection.finishSaslBind();
}
clientConnection.finishBind();
invokePostResponsePlugins(workflowExecuted);
}
}
/**
* Invokes the post response plugins. If a workflow has been executed
* then invoke the post response plugins provided by the workflow
* elements of the workflow, otherwise invoke the post response plugins
* that have been registered with the current operation.
*
* @param workflowExecuted <code>true</code> if a workflow has been executed
*/
private void invokePostResponsePlugins(boolean workflowExecuted)
{
// Invoke the post response plugins
if (workflowExecuted)
{
// The post responses are provided by the workflow elements of the workflow.
List localOperations = (List) getAttachment(Operation.LOCALBACKENDOPERATIONS);
if (localOperations != null)
{
for (Object localOp : localOperations)
{
LocalBackendBindOperation localOperation = (LocalBackendBindOperation) localOp;
// Invoke the post-response bind plugins.
getPluginConfigManager().invokePostResponseBindPlugins(localOperation);
}
}
else
{
// The current operation does not implement any bind post response
// interface so we cannot invoke any post-response plugin.
}
}
}
/** {@inheritDoc} */
@Override
public void updateOperationErrMsgAndResCode()
{
LocalizableMessage message = ERR_BIND_OPERATION_UNKNOWN_USER.get();
setResultCode(ResultCode.INVALID_CREDENTIALS);
setAuthFailureReason(message);
}
}