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