/*
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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-2010 Sun Microsystems, Inc.
* Portions copyright 2011-2012 ForgeRock AS
*/
package org.opends.server.workflowelement.localbackend;
import java.util.List;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.MatchedValuesControl;
import org.opends.server.controls.PersistentSearchControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.controls.SubentriesControl;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.SearchOperationWrapper;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import org.opends.server.types.operation.PostOperationSearchOperation;
import org.opends.server.types.operation.PreOperationSearchOperation;
import org.opends.server.types.operation.SearchEntrySearchOperation;
import org.opends.server.types.operation.SearchReferenceSearchOperation;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class defines an operation used to search for entries in a local backend
* of the Directory Server.
*/
public class LocalBackendSearchOperation
extends SearchOperationWrapper
implements PreOperationSearchOperation, PostOperationSearchOperation,
SearchEntrySearchOperation, SearchReferenceSearchOperation
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
/**
* The backend in which the search is to be performed.
*/
protected Backend backend;
/**
* Indicates whether we should actually process the search. This should
* only be false if it's a persistent search with changesOnly=true.
*/
protected boolean processSearch;
/**
* The client connection for the search operation.
*/
protected ClientConnection clientConnection;
/**
* The base DN for the search.
*/
protected DN baseDN;
/**
* The persistent search request, if applicable.
*/
protected PersistentSearch persistentSearch;
/**
* The filter for the search.
*/
protected SearchFilter filter;
/**
* Creates a new operation that may be used to search for entries in a local
* backend of the Directory Server.
*
* @param search The operation to process.
*/
public LocalBackendSearchOperation(SearchOperation search)
{
super(search);
LocalBackendWorkflowElement.attachLocalOperation(search, this);
}
/**
* Process this search operation against a local backend.
*
* @param wfe
* The local backend work-flow element.
* @throws CanceledOperationException
* if this operation should be cancelled
*/
public void processLocalSearch(LocalBackendWorkflowElement wfe)
throws CanceledOperationException
{
boolean executePostOpPlugins = false;
this.backend = wfe.getBackend();
clientConnection = getClientConnection();
// Get the plugin config manager that will be used for invoking plugins.
PluginConfigManager pluginConfigManager =
DirectoryServer.getPluginConfigManager();
processSearch = true;
// Check for a request to cancel this operation.
checkIfCanceled(false);
// Create a labeled block of code that we can break out of if a problem is
// detected.
searchProcessing:
{
// Process the search base and filter to convert them from their raw forms
// as provided by the client to the forms required for the rest of the
// search processing.
baseDN = getBaseDN();
filter = getFilter();
if ((baseDN == null) || (filter == null)){
break searchProcessing;
}
// Check to see if there are any controls in the request. If so, then
// see if there is any special processing required.
try
{
handleRequestControls();
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
setResponseData(de);
break searchProcessing;
}
// Check to see if the client has permission to perform the
// search.
// FIXME: for now assume that this will check all permission
// pertinent to the operation. This includes proxy authorization
// and any other controls specified.
try
{
if (!AccessControlConfigManager.getInstance()
.getAccessControlHandler().isAllowed(this))
{
setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
.get(String.valueOf(baseDN)));
break searchProcessing;
}
}
catch (DirectoryException e)
{
setResultCode(e.getResultCode());
appendErrorMessage(e.getMessageObject());
break searchProcessing;
}
// Check for a request to cancel this operation.
checkIfCanceled(false);
// Invoke the pre-operation search plugins.
executePostOpPlugins = true;
PluginResult.PreOperation preOpResult =
pluginConfigManager.invokePreOperationSearchPlugins(this);
if (!preOpResult.continueProcessing())
{
setResultCode(preOpResult.getResultCode());
appendErrorMessage(preOpResult.getErrorMessage());
setMatchedDN(preOpResult.getMatchedDN());
setReferralURLs(preOpResult.getReferralURLs());
break searchProcessing;
}
// Check for a request to cancel this operation.
checkIfCanceled(false);
// Get the backend that should hold the search base. If there is none,
// then fail.
if (backend == null)
{
setResultCode(ResultCode.NO_SUCH_OBJECT);
appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
String.valueOf(baseDN)));
break searchProcessing;
}
// We'll set the result code to "success". If a problem occurs, then it
// will be overwritten.
setResultCode(ResultCode.SUCCESS);
// If there's a persistent search, then register it with the server.
if (persistentSearch != null)
{
//The Core server maintains the count of concurrent persistent searches
//so that all the backends (Remote and Local) are aware of it. Verify
//with the core if we have already reached the threshold.
if(!DirectoryServer.allowNewPersistentSearch())
{
setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
appendErrorMessage(ERR_MAX_PSEARCH_LIMIT_EXCEEDED.get());
break searchProcessing;
}
wfe.registerPersistentSearch(persistentSearch);
persistentSearch.enable();
}
// Process the search in the backend and all its subordinates.
try
{
if (processSearch)
{
backend.search(this);
}
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.VERBOSE, de);
}
setResponseData(de);
if (persistentSearch != null)
{
persistentSearch.cancel();
setSendResponse(true);
}
break searchProcessing;
}
catch (CanceledOperationException coe)
{
if (persistentSearch != null)
{
persistentSearch.cancel();
setSendResponse(true);
}
throw coe;
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
setResultCode(DirectoryServer.getServerErrorResultCode());
appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(
getExceptionMessage(e)));
if (persistentSearch != null)
{
persistentSearch.cancel();
setSendResponse(true);
}
break searchProcessing;
}
}
// Check for a request to cancel this operation.
checkIfCanceled(false);
// Invoke the post-operation search plugins.
if (executePostOpPlugins)
{
PluginResult.PostOperation postOpResult =
pluginConfigManager.invokePostOperationSearchPlugins(this);
if (!postOpResult.continueProcessing())
{
setResultCode(postOpResult.getResultCode());
appendErrorMessage(postOpResult.getErrorMessage());
setMatchedDN(postOpResult.getMatchedDN());
setReferralURLs(postOpResult.getReferralURLs());
}
}
}
/**
* Handles any controls contained in the request.
*
* @throws DirectoryException If there is a problem with any of the request
* controls.
*/
protected void handleRequestControls()
throws DirectoryException
{
List<Control> requestControls = getRequestControls();
if ((requestControls != null) && (! requestControls.isEmpty()))
{
for (int i=0; i < requestControls.size(); i++)
{
Control c = requestControls.get(i);
String oid = c.getOID();
if (!LocalBackendWorkflowElement.isControlAllowed(baseDN, this, c))
{
// Skip disallowed non-critical controls.
continue;
}
if (oid.equals(OID_LDAP_ASSERTION))
{
LDAPAssertionRequestControl assertControl =
getRequestControl(LDAPAssertionRequestControl.DECODER);
SearchFilter assertionFilter;
try
{
assertionFilter = assertControl.getSearchFilter();
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
throw new DirectoryException(de.getResultCode(),
ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
de.getMessageObject()), de);
}
Entry entry;
try
{
entry = DirectoryServer.getEntry(baseDN);
}
catch (DirectoryException de)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
throw new DirectoryException(de.getResultCode(),
ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
de.getMessageObject()));
}
if (entry == null)
{
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
}
// Check if the current user has permission to make
// this determination.
if (!AccessControlConfigManager.getInstance().
getAccessControlHandler().isAllowed(this, entry, assertionFilter))
{
throw new DirectoryException(
ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
}
try {
if (! assertionFilter.matchesEntry(entry))
{
throw new DirectoryException(ResultCode.ASSERTION_FAILED,
ERR_SEARCH_ASSERTION_FAILED.get());
}
}
catch (DirectoryException de)
{
if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
{
throw de;
}
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, de);
}
throw new DirectoryException(de.getResultCode(),
ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
de.getMessageObject()), de);
}
}
else if (oid.equals(OID_PROXIED_AUTH_V1))
{
// Log usage of legacy proxy authz V1 control.
addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
"obsoleteProxiedAuthzV1Control"));
// The requester must have the PROXIED_AUTH privilige in order to be
// able to use this control.
if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
{
throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
}
ProxiedAuthV1Control proxyControl =
getRequestControl(ProxiedAuthV1Control.DECODER);
Entry authorizationEntry = proxyControl.getAuthorizationEntry();
setAuthorizationEntry(authorizationEntry);
if (authorizationEntry == null)
{
setProxiedAuthorizationDN(DN.nullDN());
}
else
{
setProxiedAuthorizationDN(authorizationEntry.getDN());
}
}
else if (oid.equals(OID_PROXIED_AUTH_V2))
{
// The requester must have the PROXIED_AUTH privilige in order to be
// able to use this control.
if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
{
throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
}
ProxiedAuthV2Control proxyControl =
getRequestControl(ProxiedAuthV2Control.DECODER);
Entry authorizationEntry = proxyControl.getAuthorizationEntry();
setAuthorizationEntry(authorizationEntry);
if (authorizationEntry == null)
{
setProxiedAuthorizationDN(DN.nullDN());
}
else
{
setProxiedAuthorizationDN(authorizationEntry.getDN());
}
}
else if (oid.equals(OID_PERSISTENT_SEARCH))
{
PersistentSearchControl psearchControl =
getRequestControl(PersistentSearchControl.DECODER);
persistentSearch = new PersistentSearch(this,
psearchControl.getChangeTypes(),
psearchControl.getReturnECs());
// If we're only interested in changes, then we don't actually want
// to process the search now.
if (psearchControl.getChangesOnly())
{
processSearch = false;
}
}
else if (oid.equals(OID_LDAP_SUBENTRIES))
{
SubentriesControl subentriesControl =
getRequestControl(SubentriesControl.DECODER);
setReturnSubentriesOnly(subentriesControl.getVisibility());
}
else if (oid.equals(OID_LDUP_SUBENTRIES))
{
// Support for legacy draft-ietf-ldup-subentry.
addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
"obsoleteSubentryControl"));
setReturnSubentriesOnly(true);
}
else if (oid.equals(OID_MATCHED_VALUES))
{
MatchedValuesControl matchedValuesControl =
getRequestControl(MatchedValuesControl.DECODER);
setMatchedValuesControl(matchedValuesControl);
}
else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
{
setIncludeUsableControl(true);
}
else if (oid.equals(OID_REAL_ATTRS_ONLY))
{
setRealAttributesOnly(true);
}
else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
{
setVirtualAttributesOnly(true);
}
else if (oid.equals(OID_GET_EFFECTIVE_RIGHTS) &&
DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
{
// Do nothing here and let AciHandler deal with it.
}
// NYI -- Add support for additional controls.
else if (c.isCritical())
{
if ((backend == null) || (! backend.supportsControl(oid)))
{
throw new DirectoryException(
ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
}
}
}
}
}
}