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
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.workflowelement.localbackend;
028
029
030
031 import java.util.List;
032
033 import org.opends.server.api.Backend;
034 import org.opends.server.api.ClientConnection;
035 import org.opends.server.api.plugin.PluginResult;
036 import org.opends.server.controls.LDAPAssertionRequestControl;
037 import org.opends.server.controls.MatchedValuesControl;
038 import org.opends.server.controls.PersistentSearchControl;
039 import org.opends.server.controls.ProxiedAuthV1Control;
040 import org.opends.server.controls.ProxiedAuthV2Control;
041 import org.opends.server.core.AccessControlConfigManager;
042 import org.opends.server.core.DirectoryServer;
043 import org.opends.server.core.PersistentSearch;
044 import org.opends.server.core.PluginConfigManager;
045 import org.opends.server.core.SearchOperationWrapper;
046 import org.opends.server.core.SearchOperation;
047 import org.opends.server.loggers.debug.DebugTracer;
048 import org.opends.server.types.CanceledOperationException;
049 import org.opends.server.types.Control;
050 import org.opends.server.types.DebugLogLevel;
051 import org.opends.server.types.DirectoryException;
052 import org.opends.server.types.DN;
053 import org.opends.server.types.Entry;
054 import org.opends.server.types.LDAPException;
055 import org.opends.server.types.Privilege;
056 import org.opends.server.types.ResultCode;
057 import org.opends.server.types.SearchFilter;
058 import org.opends.server.types.operation.PostOperationSearchOperation;
059 import org.opends.server.types.operation.PreOperationSearchOperation;
060 import org.opends.server.types.operation.SearchEntrySearchOperation;
061 import org.opends.server.types.operation.SearchReferenceSearchOperation;
062
063 import static org.opends.messages.CoreMessages.*;
064 import static org.opends.server.loggers.debug.DebugLogger.*;
065 import static org.opends.server.util.ServerConstants.*;
066 import static org.opends.server.util.StaticUtils.*;
067
068
069
070 /**
071 * This class defines an operation used to search for entries in a local backend
072 * of the Directory Server.
073 */
074 public class LocalBackendSearchOperation
075 extends SearchOperationWrapper
076 implements PreOperationSearchOperation, PostOperationSearchOperation,
077 SearchEntrySearchOperation, SearchReferenceSearchOperation
078 {
079 /**
080 * The tracer object for the debug logger.
081 */
082 private static final DebugTracer TRACER = getTracer();
083
084
085
086 // The backend in which the search is to be performed.
087 private Backend backend;
088
089 // Indicates whether we should actually process the search. This should
090 // only be false if it's a persistent search with changesOnly=true.
091 private boolean processSearch;
092
093 // The client connection for the search operation.
094 private ClientConnection clientConnection;
095
096 // The base DN for the search.
097 private DN baseDN;
098
099 // The persistent search request, if applicable.
100 private PersistentSearch persistentSearch;
101
102 // The filter for the search.
103 private SearchFilter filter;
104
105
106
107 /**
108 * Creates a new operation that may be used to search for entries in a local
109 * backend of the Directory Server.
110 *
111 * @param search The operation to process.
112 */
113 public LocalBackendSearchOperation(SearchOperation search)
114 {
115 super(search);
116 LocalBackendWorkflowElement.attachLocalOperation(search, this);
117 }
118
119
120
121 /**
122 * Process this search operation against a local backend.
123 *
124 * @param backend The backend in which the search operation should be
125 * performed.
126 *
127 * @throws CanceledOperationException if this operation should be
128 * cancelled
129 */
130 void processLocalSearch(Backend backend) throws CanceledOperationException {
131 boolean executePostOpPlugins = false;
132
133 this.backend = backend;
134
135 clientConnection = getClientConnection();
136
137 // Get the plugin config manager that will be used for invoking plugins.
138 PluginConfigManager pluginConfigManager =
139 DirectoryServer.getPluginConfigManager();
140 processSearch = true;
141
142 // Check for a request to cancel this operation.
143 checkIfCanceled(false);
144
145 // Create a labeled block of code that we can break out of if a problem is
146 // detected.
147 searchProcessing:
148 {
149 // Process the search base and filter to convert them from their raw forms
150 // as provided by the client to the forms required for the rest of the
151 // search processing.
152 baseDN = getBaseDN();
153 filter = getFilter();
154
155 if ((baseDN == null) || (filter == null)){
156 break searchProcessing;
157 }
158
159 // Check to see if there are any controls in the request. If so, then
160 // see if there is any special processing required.
161 try
162 {
163 handleRequestControls();
164 }
165 catch (DirectoryException de)
166 {
167 if (debugEnabled())
168 {
169 TRACER.debugCaught(DebugLogLevel.ERROR, de);
170 }
171
172 setResponseData(de);
173 break searchProcessing;
174 }
175
176
177 // Check to see if the client has permission to perform the
178 // search.
179
180 // FIXME: for now assume that this will check all permission
181 // pertinent to the operation. This includes proxy authorization
182 // and any other controls specified.
183 if (! AccessControlConfigManager.getInstance().getAccessControlHandler().
184 isAllowed(this))
185 {
186 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
187 appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
188 String.valueOf(baseDN)));
189 break searchProcessing;
190 }
191
192 // Check for a request to cancel this operation.
193 checkIfCanceled(false);
194
195
196 // Invoke the pre-operation search plugins.
197 executePostOpPlugins = true;
198 PluginResult.PreOperation preOpResult =
199 pluginConfigManager.invokePreOperationSearchPlugins(this);
200 if (!preOpResult.continueProcessing())
201 {
202 setResultCode(preOpResult.getResultCode());
203 appendErrorMessage(preOpResult.getErrorMessage());
204 setMatchedDN(preOpResult.getMatchedDN());
205 setReferralURLs(preOpResult.getReferralURLs());
206 break searchProcessing;
207 }
208
209
210 // Check for a request to cancel this operation.
211 checkIfCanceled(false);
212
213
214 // Get the backend that should hold the search base. If there is none,
215 // then fail.
216 if (backend == null)
217 {
218 setResultCode(ResultCode.NO_SUCH_OBJECT);
219 appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
220 String.valueOf(baseDN)));
221 break searchProcessing;
222 }
223
224
225 // We'll set the result code to "success". If a problem occurs, then it
226 // will be overwritten.
227 setResultCode(ResultCode.SUCCESS);
228
229
230 // If there's a persistent search, then register it with the server.
231 if (persistentSearch != null)
232 {
233 DirectoryServer.registerPersistentSearch(persistentSearch);
234 setSendResponse(false);
235 }
236
237
238 // Process the search in the backend and all its subordinates.
239 try
240 {
241 if (processSearch)
242 {
243 backend.search(this);
244 }
245 }
246 catch (DirectoryException de)
247 {
248 if (debugEnabled())
249 {
250 TRACER.debugCaught(DebugLogLevel.VERBOSE, de);
251 }
252
253 setResponseData(de);
254
255 if (persistentSearch != null)
256 {
257 DirectoryServer.deregisterPersistentSearch(persistentSearch);
258 setSendResponse(true);
259 }
260
261 break searchProcessing;
262 }
263 catch (Exception e)
264 {
265 if (debugEnabled())
266 {
267 TRACER.debugCaught(DebugLogLevel.ERROR, e);
268 }
269
270 setResultCode(DirectoryServer.getServerErrorResultCode());
271 appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(
272 getExceptionMessage(e)));
273
274 if (persistentSearch != null)
275 {
276 DirectoryServer.deregisterPersistentSearch(persistentSearch);
277 setSendResponse(true);
278 }
279
280 break searchProcessing;
281 }
282 }
283
284
285 // Check for a request to cancel this operation.
286 checkIfCanceled(false);
287
288 // Invoke the post-operation search plugins.
289 if (executePostOpPlugins)
290 {
291 PluginResult.PostOperation postOpResult =
292 pluginConfigManager.invokePostOperationSearchPlugins(this);
293 if (!postOpResult.continueProcessing())
294 {
295 setResultCode(postOpResult.getResultCode());
296 appendErrorMessage(postOpResult.getErrorMessage());
297 setMatchedDN(postOpResult.getMatchedDN());
298 setReferralURLs(postOpResult.getReferralURLs());
299 }
300 }
301 }
302
303
304 /**
305 * Handles any controls contained in the request.
306 *
307 * @throws DirectoryException If there is a problem with any of the request
308 * controls.
309 */
310 private void handleRequestControls()
311 throws DirectoryException
312 {
313 List<Control> requestControls = getRequestControls();
314 if ((requestControls != null) && (! requestControls.isEmpty()))
315 {
316 for (int i=0; i < requestControls.size(); i++)
317 {
318 Control c = requestControls.get(i);
319 String oid = c.getOID();
320 if (! AccessControlConfigManager.getInstance().
321 getAccessControlHandler().isAllowed(baseDN, this, c))
322 {
323 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
324 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
325 }
326
327 if (oid.equals(OID_LDAP_ASSERTION))
328 {
329 LDAPAssertionRequestControl assertControl;
330 if (c instanceof LDAPAssertionRequestControl)
331 {
332 assertControl = (LDAPAssertionRequestControl) c;
333 }
334 else
335 {
336 try
337 {
338 assertControl = LDAPAssertionRequestControl.decodeControl(c);
339 requestControls.set(i, assertControl);
340 }
341 catch (LDAPException le)
342 {
343 if (debugEnabled())
344 {
345 TRACER.debugCaught(DebugLogLevel.ERROR, le);
346 }
347
348 throw new DirectoryException(
349 ResultCode.valueOf(le.getResultCode()),
350 le.getMessageObject(), le);
351 }
352 }
353
354 try
355 {
356 // FIXME -- We need to determine whether the current user has
357 // permission to make this determination.
358 SearchFilter assertionFilter = assertControl.getSearchFilter();
359 Entry entry;
360 try
361 {
362 entry = DirectoryServer.getEntry(baseDN);
363 }
364 catch (DirectoryException de)
365 {
366 if (debugEnabled())
367 {
368 TRACER.debugCaught(DebugLogLevel.ERROR, de);
369 }
370
371 throw new DirectoryException(de.getResultCode(),
372 ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
373 de.getMessageObject()));
374 }
375
376 if (entry == null)
377 {
378 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
379 ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
380 }
381
382 if (! assertionFilter.matchesEntry(entry))
383 {
384 throw new DirectoryException(ResultCode.ASSERTION_FAILED,
385 ERR_SEARCH_ASSERTION_FAILED.get());
386 }
387 }
388 catch (DirectoryException de)
389 {
390 if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
391 {
392 throw de;
393 }
394
395 if (debugEnabled())
396 {
397 TRACER.debugCaught(DebugLogLevel.ERROR, de);
398 }
399
400 throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
401 ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
402 de.getMessageObject()), de);
403 }
404 }
405 else if (oid.equals(OID_PROXIED_AUTH_V1))
406 {
407 // The requester must have the PROXIED_AUTH privilige in order to be
408 // able to use this control.
409 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
410 {
411 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
412 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
413 }
414
415
416 ProxiedAuthV1Control proxyControl;
417 if (c instanceof ProxiedAuthV1Control)
418 {
419 proxyControl = (ProxiedAuthV1Control) c;
420 }
421 else
422 {
423 try
424 {
425 proxyControl = ProxiedAuthV1Control.decodeControl(c);
426 }
427 catch (LDAPException le)
428 {
429 if (debugEnabled())
430 {
431 TRACER.debugCaught(DebugLogLevel.ERROR, le);
432 }
433
434 throw new DirectoryException(
435 ResultCode.valueOf(le.getResultCode()),
436 le.getMessageObject(), le);
437 }
438 }
439
440
441 Entry authorizationEntry = proxyControl.getAuthorizationEntry();
442 setAuthorizationEntry(authorizationEntry);
443 if (authorizationEntry == null)
444 {
445 setProxiedAuthorizationDN(DN.nullDN());
446 }
447 else
448 {
449 setProxiedAuthorizationDN(authorizationEntry.getDN());
450 }
451 }
452 else if (oid.equals(OID_PROXIED_AUTH_V2))
453 {
454 // The requester must have the PROXIED_AUTH privilige in order to be
455 // able to use this control.
456 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
457 {
458 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
459 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
460 }
461
462
463 ProxiedAuthV2Control proxyControl;
464 if (c instanceof ProxiedAuthV2Control)
465 {
466 proxyControl = (ProxiedAuthV2Control) c;
467 }
468 else
469 {
470 try
471 {
472 proxyControl = ProxiedAuthV2Control.decodeControl(c);
473 }
474 catch (LDAPException le)
475 {
476 if (debugEnabled())
477 {
478 TRACER.debugCaught(DebugLogLevel.ERROR, le);
479 }
480
481 throw new DirectoryException(
482 ResultCode.valueOf(le.getResultCode()),
483 le.getMessageObject(), le);
484 }
485 }
486
487
488 Entry authorizationEntry = proxyControl.getAuthorizationEntry();
489 setAuthorizationEntry(authorizationEntry);
490 if (authorizationEntry == null)
491 {
492 setProxiedAuthorizationDN(DN.nullDN());
493 }
494 else
495 {
496 setProxiedAuthorizationDN(authorizationEntry.getDN());
497 }
498 }
499 else if (oid.equals(OID_PERSISTENT_SEARCH))
500 {
501 PersistentSearchControl psearchControl;
502 if (c instanceof PersistentSearchControl)
503 {
504 psearchControl = (PersistentSearchControl) c;
505 }
506 else
507 {
508 try
509 {
510 psearchControl = PersistentSearchControl.decodeControl(c);
511 }
512 catch (LDAPException le)
513 {
514 if (debugEnabled())
515 {
516 TRACER.debugCaught(DebugLogLevel.ERROR, le);
517 }
518
519 throw new DirectoryException(
520 ResultCode.valueOf(le.getResultCode()),
521 le.getMessageObject(), le);
522 }
523 }
524
525 persistentSearch = new PersistentSearch(this,
526 psearchControl.getChangeTypes(),
527 psearchControl.getReturnECs());
528 setPersistentSearch(persistentSearch);
529
530 // If we're only interested in changes, then we don't actually want
531 // to process the search now.
532 if (psearchControl.getChangesOnly())
533 {
534 processSearch = false;
535 }
536 }
537 else if (oid.equals(OID_LDAP_SUBENTRIES))
538 {
539 setReturnLDAPSubentries(true);
540 }
541 else if (oid.equals(OID_MATCHED_VALUES))
542 {
543 if (c instanceof MatchedValuesControl)
544 {
545 setMatchedValuesControl((MatchedValuesControl) c);
546 }
547 else
548 {
549 try
550 {
551 MatchedValuesControl matchedValuesControl =
552 MatchedValuesControl.decodeControl(c);
553 setMatchedValuesControl(matchedValuesControl);
554 }
555 catch (LDAPException le)
556 {
557 if (debugEnabled())
558 {
559 TRACER.debugCaught(DebugLogLevel.ERROR, le);
560 }
561
562 throw new DirectoryException(
563 ResultCode.valueOf(le.getResultCode()),
564 le.getMessageObject(), le);
565 }
566 }
567 }
568 else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
569 {
570 setIncludeUsableControl(true);
571 }
572 else if (oid.equals(OID_REAL_ATTRS_ONLY))
573 {
574 setRealAttributesOnly(true);
575 }
576 else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
577 {
578 setVirtualAttributesOnly(true);
579 }
580 else if (oid.equals(OID_GET_EFFECTIVE_RIGHTS) &&
581 DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
582 {
583 // Do nothing here and let AciHandler deal with it.
584 }
585
586 // NYI -- Add support for additional controls.
587
588 else if (c.isCritical())
589 {
590 if ((backend == null) || (! backend.supportsControl(oid)))
591 {
592 throw new DirectoryException(
593 ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
594 ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
595 }
596 }
597 }
598 }
599 }
600 }
601