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}