/*
* 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 2008-2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.workflowelement.localbackend;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.core.*;
import org.opends.server.types.*;
import org.opends.server.types.operation.PostOperationCompareOperation;
import org.opends.server.types.operation.PostResponseCompareOperation;
import org.opends.server.types.operation.PreOperationCompareOperation;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.types.AbstractOperation.*;
import static org.opends.server.util.ServerConstants.*;
/**
* This class defines an operation that may be used to determine whether a
* specified entry in the Directory Server contains a given attribute-value pair.
*/
public class LocalBackendCompareOperation
extends CompareOperationWrapper
implements PreOperationCompareOperation, PostOperationCompareOperation,
PostResponseCompareOperation
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** The backend in which the comparison is to be performed. */
private Backend<?> backend;
/** The client connection for this operation. */
private ClientConnection clientConnection;
/** The DN of the entry to compare. */
private DN entryDN;
/** The entry to be compared. */
private Entry entry;
/**
* Creates a new compare operation based on the provided compare operation.
*
* @param compare the compare operation
*/
public LocalBackendCompareOperation(CompareOperation compare)
{
super(compare);
LocalBackendWorkflowElement.attachLocalOperation (compare, this);
}
/**
* Retrieves the entry to target with the compare operation.
*
* @return The entry to target with the compare operation, or
* <CODE>null</CODE> if the entry is not yet available.
*/
@Override
public Entry getEntryToCompare()
{
return entry;
}
/**
* Process this compare operation in a local backend.
*
* @param wfe
* The local backend work-flow element.
* @throws CanceledOperationException
* if this operation should be cancelled
*/
public void processLocalCompare(LocalBackendWorkflowElement wfe)
throws CanceledOperationException
{
this.backend = wfe.getBackend();
clientConnection = getClientConnection();
// Check for a request to cancel this operation.
checkIfCanceled(false);
try
{
AtomicBoolean executePostOpPlugins = new AtomicBoolean(false);
processCompare(executePostOpPlugins);
// Check for a request to cancel this operation.
checkIfCanceled(false);
// Invoke the post-operation compare plugins.
if (executePostOpPlugins.get())
{
processOperationResult(this, getPluginConfigManager().invokePostOperationComparePlugins(this));
}
}
finally
{
LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
}
}
private void processCompare(AtomicBoolean executePostOpPlugins)
throws CanceledOperationException
{
// Process the entry DN to convert it from the raw form to the form
// required for the rest of the compare processing.
entryDN = getEntryDN();
if (entryDN == null)
{
return;
}
// If the target entry is in the server configuration, then make sure the
// requester has the CONFIG_READ privilege.
if (DirectoryServer.getConfigHandler().handlesEntry(entryDN)
&& !clientConnection.hasPrivilege(Privilege.CONFIG_READ, this))
{
appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
return;
}
// Check for a request to cancel this operation.
checkIfCanceled(false);
try
{
// Get the entry. If it does not exist, then fail.
try
{
entry = DirectoryServer.getEntry(entryDN);
if (entry == null)
{
setResultCode(ResultCode.NO_SUCH_OBJECT);
appendErrorMessage(ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN));
// See if one of the entry's ancestors exists.
setMatchedDN(findMatchedDN(entryDN));
return;
}
}
catch (DirectoryException de)
{
logger.traceException(de);
setResultCodeAndMessageNoInfoDisclosure(entry, entryDN,
de.getResultCode(), de.getMessageObject());
return;
}
// Check to see if there are any controls in the request. If so, then
// see if there is any special processing required.
handleRequestControls();
// Check to see if the client has permission to perform the
// compare.
// FIXME: for now assume that this will check all permission
// pertinent to the operation. This includes proxy authorization
// and any other controls specified.
// FIXME: earlier checks to see if the entry already exists may
// have already exposed sensitive information to the client.
try
{
if (!getAccessControlHandler().isAllowed(this))
{
setResultCodeAndMessageNoInfoDisclosure(entry, entryDN,
ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN));
return;
}
}
catch (DirectoryException e)
{
setResultCode(e.getResultCode());
appendErrorMessage(e.getMessageObject());
return;
}
// Check for a request to cancel this operation.
checkIfCanceled(false);
// Invoke the pre-operation compare plugins.
executePostOpPlugins.set(true);
if (!processOperationResult(this, getPluginConfigManager().invokePreOperationComparePlugins(this)))
{
return;
}
// Get the base attribute type and set of options.
Set<String> options = getAttributeOptions();
AttributeType attrType = getAttributeType();
// Actually perform the compare operation.
List<Attribute> attrList = entry.getAttribute(attrType, options);
if (attrList == null || attrList.isEmpty())
{
setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
Arg2<Object, Object> errorMsg = options == null
? WARN_COMPARE_OP_NO_SUCH_ATTR
: WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS;
appendErrorMessage(errorMsg.get(entryDN, getRawAttributeType()));
}
else
{
ByteString value = getAssertionValue();
setResultCode(matchExists(attrList, value));
}
}
catch (DirectoryException de)
{
logger.traceException(de);
setResponseData(de);
}
}
private ResultCode matchExists(List<Attribute> attrList, ByteString value)
{
for (Attribute a : attrList)
{
if (a.contains(value))
{
return ResultCode.COMPARE_TRUE;
}
}
return ResultCode.COMPARE_FALSE;
}
private DirectoryException newDirectoryException(Entry entry,
ResultCode resultCode, LocalizableMessage message) throws DirectoryException
{
return LocalBackendWorkflowElement.newDirectoryException(this, entry, null,
resultCode, message, ResultCode.NO_SUCH_OBJECT,
ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN));
}
private void setResultCodeAndMessageNoInfoDisclosure(Entry entry, DN entryDN,
ResultCode realResultCode, LocalizableMessage realMessage) throws DirectoryException
{
LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this,
entry, entryDN, realResultCode, realMessage, ResultCode.NO_SUCH_OBJECT,
ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN));
}
private DN findMatchedDN(DN entryDN)
{
try
{
DN matchedDN = entryDN.getParentDNInSuffix();
while (matchedDN != null)
{
if (DirectoryServer.entryExists(matchedDN))
{
return matchedDN;
}
matchedDN = matchedDN.getParentDNInSuffix();
}
}
catch (Exception e)
{
logger.traceException(e);
}
return null;
}
/**
* Performs any processing required for the controls included in the request.
*
* @throws DirectoryException If a problem occurs that should prevent the
* operation from succeeding.
*/
private void handleRequestControls() throws DirectoryException
{
LocalBackendWorkflowElement.evaluateProxyAuthControls(this);
LocalBackendWorkflowElement.removeAllDisallowedControls(entryDN, this);
List<Control> requestControls = getRequestControls();
if (requestControls != null && !requestControls.isEmpty())
{
for (Control c : requestControls)
{
final String oid = c.getOID();
if (OID_LDAP_ASSERTION.equals(oid))
{
LDAPAssertionRequestControl assertControl =
getRequestControl(LDAPAssertionRequestControl.DECODER);
SearchFilter filter;
try
{
filter = assertControl.getSearchFilter();
}
catch (DirectoryException de)
{
logger.traceException(de);
throw newDirectoryException(entry, de.getResultCode(),
ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject()));
}
// Check if the current user has permission to make this determination.
if (!getAccessControlHandler().isAllowed(this, entry, filter))
{
throw new DirectoryException(
ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
}
try
{
if (!filter.matchesEntry(entry))
{
throw newDirectoryException(entry, ResultCode.ASSERTION_FAILED,
ERR_COMPARE_ASSERTION_FAILED.get(entryDN));
}
}
catch (DirectoryException de)
{
if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
{
throw de;
}
logger.traceException(de);
throw newDirectoryException(entry, de.getResultCode(),
ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject()));
}
}
else if (LocalBackendWorkflowElement.isProxyAuthzControl(oid))
{
continue;
}
// NYI -- Add support for additional controls.
else if (c.isCritical()
&& (backend == null || !backend.supportsControl(oid)))
{
throw new DirectoryException(
ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
ERR_COMPARE_UNSUPPORTED_CRITICAL_CONTROL.get(entryDN, oid));
}
}
}
}
private AccessControlHandler<?> getAccessControlHandler()
{
return AccessControlConfigManager.getInstance().getAccessControlHandler();
}
}