001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.workflowelement.localbackend;
018
019import java.util.concurrent.atomic.AtomicBoolean;
020
021import org.forgerock.i18n.slf4j.LocalizedLogger;
022import org.forgerock.opendj.ldap.DN;
023import org.forgerock.opendj.ldap.ResultCode;
024import org.opends.server.api.AccessControlHandler;
025import org.opends.server.api.Backend;
026import org.opends.server.api.ClientConnection;
027import org.opends.server.controls.*;
028import org.opends.server.core.*;
029import org.opends.server.types.*;
030import org.opends.server.types.operation.PostOperationSearchOperation;
031import org.opends.server.types.operation.PreOperationSearchOperation;
032import org.opends.server.types.operation.SearchEntrySearchOperation;
033import org.opends.server.types.operation.SearchReferenceSearchOperation;
034
035import static org.opends.messages.CoreMessages.*;
036import static org.opends.server.core.DirectoryServer.*;
037import static org.opends.server.types.AbstractOperation.*;
038import static org.opends.server.util.ServerConstants.*;
039import static org.opends.server.util.StaticUtils.*;
040
041/**
042 * This class defines an operation used to search for entries in a local backend
043 * of the Directory Server.
044 */
045public class LocalBackendSearchOperation
046       extends SearchOperationWrapper
047       implements PreOperationSearchOperation, PostOperationSearchOperation,
048                  SearchEntrySearchOperation, SearchReferenceSearchOperation
049{
050  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
051
052  /** The backend in which the search is to be performed. */
053  private Backend<?> backend;
054
055  /** The client connection for the search operation. */
056  private ClientConnection clientConnection;
057
058  /** The base DN for the search. */
059  private DN baseDN;
060
061  /** The persistent search request, if applicable. */
062  private PersistentSearch persistentSearch;
063
064  /** The filter for the search. */
065  private SearchFilter filter;
066
067  /**
068   * Creates a new operation that may be used to search for entries in a local
069   * backend of the Directory Server.
070   *
071   * @param  search  The operation to process.
072   */
073  public LocalBackendSearchOperation(SearchOperation search)
074  {
075    super(search);
076    LocalBackendWorkflowElement.attachLocalOperation(search, this);
077  }
078
079
080
081  /**
082   * Process this search operation against a local backend.
083   *
084   * @param wfe
085   *          The local backend work-flow element.
086   * @throws CanceledOperationException
087   *           if this operation should be cancelled
088   */
089  public void processLocalSearch(LocalBackendWorkflowElement wfe)
090      throws CanceledOperationException
091  {
092    this.backend = wfe.getBackend();
093    this.clientConnection = getClientConnection();
094
095    // Check for a request to cancel this operation.
096    checkIfCanceled(false);
097
098    try
099    {
100      AtomicBoolean executePostOpPlugins = new AtomicBoolean(false);
101      processSearch(executePostOpPlugins);
102
103      // Check for a request to cancel this operation.
104      checkIfCanceled(false);
105
106      // Invoke the post-operation search plugins.
107      if (executePostOpPlugins.get())
108      {
109        processOperationResult(this, getPluginConfigManager().invokePostOperationSearchPlugins(this));
110      }
111    }
112    finally
113    {
114      LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this);
115    }
116  }
117
118  private void processSearch(AtomicBoolean executePostOpPlugins) throws CanceledOperationException
119  {
120    // Process the search base and filter to convert them from their raw forms
121    // as provided by the client to the forms required for the rest of the
122    // search processing.
123    baseDN = getBaseDN();
124    filter = getFilter();
125
126    if (baseDN == null || filter == null)
127    {
128      return;
129    }
130
131    // Check to see if there are any controls in the request. If so, then
132    // see if there is any special processing required.
133    try
134    {
135      handleRequestControls();
136    }
137    catch (DirectoryException de)
138    {
139      logger.traceException(de);
140
141      setResponseData(de);
142      return;
143    }
144
145
146    // Check to see if the client has permission to perform the search.
147
148    // FIXME: for now assume that this will check all permission
149    // pertinent to the operation. This includes proxy authorization
150    // and any other controls specified.
151    try
152    {
153      if (!getAccessControlHandler().isAllowed(this))
154      {
155        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
156        appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(baseDN));
157        return;
158      }
159    }
160    catch (DirectoryException e)
161    {
162      setResultCode(e.getResultCode());
163      appendErrorMessage(e.getMessageObject());
164      return;
165    }
166
167    // Check for a request to cancel this operation.
168    checkIfCanceled(false);
169
170
171    // Invoke the pre-operation search plugins.
172    executePostOpPlugins.set(true);
173    if (!processOperationResult(this, getPluginConfigManager().invokePreOperationSearchPlugins(this)))
174    {
175      return;
176    }
177
178
179    // Check for a request to cancel this operation.
180    checkIfCanceled(false);
181
182
183    // Get the backend that should hold the search base. If there is none,
184    // then fail.
185    if (backend == null)
186    {
187      setResultCode(ResultCode.NO_SUCH_OBJECT);
188      appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(baseDN));
189      return;
190    }
191
192
193    // We'll set the result code to "success". If a problem occurs, then it
194    // will be overwritten.
195    setResultCode(ResultCode.SUCCESS);
196
197    try
198    {
199      // If there's a persistent search, then register it with the server.
200      boolean processSearchNow = true;
201      if (persistentSearch != null)
202      {
203        // If we're only interested in changes, then we do not actually want
204        // to process the search now.
205        processSearchNow = !persistentSearch.isChangesOnly();
206
207        // The Core server maintains the count of concurrent persistent searches
208        // so that all the backends (Remote and Local) are aware of it. Verify
209        // with the core if we have already reached the threshold.
210        if (!DirectoryServer.allowNewPersistentSearch())
211        {
212          setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
213          appendErrorMessage(ERR_MAX_PSEARCH_LIMIT_EXCEEDED.get());
214          return;
215        }
216        backend.registerPersistentSearch(persistentSearch);
217        persistentSearch.enable();
218      }
219
220
221      if (processSearchNow)
222      {
223        // Process the search in the backend and all its subordinates.
224        backend.search(this);
225      }
226    }
227    catch (DirectoryException de)
228    {
229      logger.traceException(de);
230      setResponseData(de);
231
232      if (persistentSearch != null)
233      {
234        persistentSearch.cancel();
235        setSendResponse(true);
236      }
237
238      return;
239    }
240    catch (CanceledOperationException coe)
241    {
242      if (persistentSearch != null)
243      {
244        persistentSearch.cancel();
245        setSendResponse(true);
246      }
247
248      throw coe;
249    }
250    catch (Exception e)
251    {
252      logger.traceException(e);
253
254      setResultCode(DirectoryServer.getServerErrorResultCode());
255      appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION
256          .get(getExceptionMessage(e)));
257
258      if (persistentSearch != null)
259      {
260        persistentSearch.cancel();
261        setSendResponse(true);
262      }
263    }
264  }
265
266
267  /**
268   * Handles any controls contained in the request.
269   *
270   * @throws DirectoryException
271   *           If there is a problem with any of the request controls.
272   */
273  private void handleRequestControls() throws DirectoryException
274  {
275    LocalBackendWorkflowElement.evaluateProxyAuthControls(this);
276    LocalBackendWorkflowElement.removeAllDisallowedControls(baseDN, this);
277
278    for (Control c : getRequestControls())
279    {
280      final String oid = c.getOID();
281
282      if (OID_LDAP_ASSERTION.equals(oid))
283      {
284        LDAPAssertionRequestControl assertControl = getRequestControl(LDAPAssertionRequestControl.DECODER);
285
286        SearchFilter assertionFilter;
287        try
288        {
289          assertionFilter = assertControl.getSearchFilter();
290        }
291        catch (DirectoryException de)
292        {
293          logger.traceException(de);
294
295          throw new DirectoryException(de.getResultCode(),
296              ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(de.getMessageObject()), de);
297        }
298
299        Entry entry;
300        try
301        {
302          entry = DirectoryServer.getEntry(baseDN);
303        }
304        catch (DirectoryException de)
305        {
306          logger.traceException(de);
307
308          throw new DirectoryException(de.getResultCode(),
309              ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(de.getMessageObject()));
310        }
311
312        if (entry == null)
313        {
314          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
315        }
316
317        // Check if the current user has permission to make this determination.
318        if (!getAccessControlHandler().isAllowed(this, entry, assertionFilter))
319        {
320          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
321              ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
322        }
323
324        try
325        {
326          if (!assertionFilter.matchesEntry(entry))
327          {
328            throw new DirectoryException(ResultCode.ASSERTION_FAILED, ERR_SEARCH_ASSERTION_FAILED.get());
329          }
330        }
331        catch (DirectoryException de)
332        {
333          if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
334          {
335            throw de;
336          }
337
338          logger.traceException(de);
339
340          throw new DirectoryException(de.getResultCode(),
341              ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(de.getMessageObject()), de);
342        }
343      }
344      else if (LocalBackendWorkflowElement.isProxyAuthzControl(oid))
345      {
346        continue;
347      }
348      else if (OID_PERSISTENT_SEARCH.equals(oid))
349      {
350        final PersistentSearchControl ctl = getRequestControl(PersistentSearchControl.DECODER);
351        persistentSearch = new PersistentSearch(this, ctl.getChangeTypes(), ctl.getChangesOnly(), ctl.getReturnECs());
352      }
353      else if (OID_LDAP_SUBENTRIES.equals(oid))
354      {
355        SubentriesControl subentriesControl = getRequestControl(SubentriesControl.DECODER);
356        setReturnSubentriesOnly(subentriesControl.getVisibility());
357      }
358      else if (OID_LDUP_SUBENTRIES.equals(oid))
359      {
360        // Support for legacy draft-ietf-ldup-subentry.
361        addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(), "obsoleteSubentryControl"));
362
363        setReturnSubentriesOnly(true);
364      }
365      else if (OID_MATCHED_VALUES.equals(oid))
366      {
367        setMatchedValuesControl(getRequestControl(MatchedValuesControl.DECODER));
368      }
369      else if (OID_ACCOUNT_USABLE_CONTROL.equals(oid))
370      {
371        setIncludeUsableControl(true);
372      }
373      else if (OID_REAL_ATTRS_ONLY.equals(oid))
374      {
375        setRealAttributesOnly(true);
376      }
377      else if (OID_VIRTUAL_ATTRS_ONLY.equals(oid))
378      {
379        setVirtualAttributesOnly(true);
380      }
381      else if (OID_GET_EFFECTIVE_RIGHTS.equals(oid) && DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
382      {
383        // Do nothing here and let AciHandler deal with it.
384      }
385      else if (c.isCritical() && !backendSupportsControl(oid))
386      {
387        throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
388            ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
389      }
390    }
391  }
392
393  private AccessControlHandler<?> getAccessControlHandler()
394  {
395    return AccessControlConfigManager.getInstance().getAccessControlHandler();
396  }
397
398  /** Indicates if the backend supports the control corresponding to provided oid. */
399  private boolean backendSupportsControl(final String oid)
400  {
401    return backend != null && backend.supportsControl(oid);
402  }
403}