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}