/*
|
* 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-2013 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.*;
|
import org.opends.server.core.*;
|
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.
|
*/
|
private Backend backend;
|
|
/**
|
* Indicates whether we should actually process the search. This should
|
* only be false if it's a persistent search with changesOnly=true.
|
*/
|
private boolean processSearch;
|
|
/**
|
* The client connection for the search operation.
|
*/
|
private ClientConnection clientConnection;
|
|
/**
|
* The base DN for the search.
|
*/
|
private DN baseDN;
|
|
/**
|
* The persistent search request, if applicable.
|
*/
|
private PersistentSearch persistentSearch;
|
|
/**
|
* The filter for the search.
|
*/
|
private 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
|
{
|
this.backend = wfe.getBackend();
|
|
clientConnection = getClientConnection();
|
|
processSearch = true;
|
|
// Check for a request to cancel this operation.
|
checkIfCanceled(false);
|
|
try
|
{
|
BooleanHolder executePostOpPlugins = new BooleanHolder(false);
|
processSearch(wfe, executePostOpPlugins);
|
|
// Check for a request to cancel this operation.
|
checkIfCanceled(false);
|
|
// Invoke the post-operation search plugins.
|
if (executePostOpPlugins.value)
|
{
|
PluginResult.PostOperation postOpResult =
|
DirectoryServer.getPluginConfigManager()
|
.invokePostOperationSearchPlugins(this);
|
if (!postOpResult.continueProcessing())
|
{
|
setResultCode(postOpResult.getResultCode());
|
appendErrorMessage(postOpResult.getErrorMessage());
|
setMatchedDN(postOpResult.getMatchedDN());
|
setReferralURLs(postOpResult.getReferralURLs());
|
}
|
}
|
}
|
finally
|
{
|
LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
|
}
|
}
|
|
private void processSearch(LocalBackendWorkflowElement wfe,
|
BooleanHolder executePostOpPlugins) throws CanceledOperationException
|
{
|
// 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))
|
{
|
return;
|
}
|
|
// 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);
|
return;
|
}
|
|
|
// 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)));
|
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 search plugins.
|
executePostOpPlugins.value = true;
|
PluginResult.PreOperation preOpResult =
|
DirectoryServer.getPluginConfigManager()
|
.invokePreOperationSearchPlugins(this);
|
if (!preOpResult.continueProcessing())
|
{
|
setResultCode(preOpResult.getResultCode());
|
appendErrorMessage(preOpResult.getErrorMessage());
|
setMatchedDN(preOpResult.getMatchedDN());
|
setReferralURLs(preOpResult.getReferralURLs());
|
return;
|
}
|
|
|
// 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)));
|
return;
|
}
|
|
|
// 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());
|
return;
|
}
|
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);
|
}
|
|
return;
|
}
|
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);
|
}
|
}
|
}
|
|
|
/**
|
* Handles any controls contained in the request.
|
*
|
* @throws DirectoryException
|
* If there is a problem with any of the request controls.
|
*/
|
private void handleRequestControls() throws DirectoryException
|
{
|
LocalBackendWorkflowElement.removeAllDisallowedControls(baseDN, this);
|
|
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 (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 privilege 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 privilege 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));
|
}
|
}
|
}
|
}
|
}
|
}
|