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.ArrayList; 020import java.util.Collection; 021import java.util.Iterator; 022import java.util.List; 023import java.util.TreeMap; 024 025import org.forgerock.i18n.LocalizableMessage; 026import org.forgerock.i18n.LocalizableMessageBuilder; 027import org.forgerock.i18n.LocalizableMessageDescriptor; 028import org.forgerock.i18n.slf4j.LocalizedLogger; 029import org.forgerock.opendj.ldap.DN; 030import org.forgerock.opendj.ldap.ResultCode; 031import org.forgerock.opendj.ldap.SearchScope; 032import org.opends.server.api.AccessControlHandler; 033import org.opends.server.api.Backend; 034import org.opends.server.backends.RootDSEBackend; 035import org.opends.server.controls.LDAPPostReadRequestControl; 036import org.opends.server.controls.LDAPPostReadResponseControl; 037import org.opends.server.controls.LDAPPreReadRequestControl; 038import org.opends.server.controls.LDAPPreReadResponseControl; 039import org.opends.server.controls.ProxiedAuthV1Control; 040import org.opends.server.controls.ProxiedAuthV2Control; 041import org.opends.server.core.AccessControlConfigManager; 042import org.opends.server.core.AddOperation; 043import org.opends.server.core.BindOperation; 044import org.opends.server.core.CompareOperation; 045import org.opends.server.core.DeleteOperation; 046import org.opends.server.core.DirectoryServer; 047import org.opends.server.core.ModifyDNOperation; 048import org.opends.server.core.ModifyOperation; 049import org.opends.server.core.SearchOperation; 050import org.opends.server.types.AbstractOperation; 051import org.opends.server.types.AdditionalLogItem; 052import org.opends.server.types.CanceledOperationException; 053import org.opends.server.types.Control; 054import org.opends.server.types.DirectoryException; 055import org.opends.server.types.Entry; 056import org.opends.server.types.Operation; 057import org.opends.server.types.OperationType; 058import org.opends.server.types.Privilege; 059import org.opends.server.types.SearchResultEntry; 060import org.opends.server.types.WritabilityMode; 061 062import static org.opends.messages.CoreMessages.*; 063import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH_AUTHZ_NOT_PERMITTED; 064import static org.opends.server.util.ServerConstants.*; 065 066/** 067 * This class defines a local backend workflow element; e-g an entity that 068 * handle the processing of an operation against a local backend. 069 */ 070public class LocalBackendWorkflowElement 071{ 072 /** 073 * This class implements the workflow result code. The workflow result code 074 * contains an LDAP result code along with an LDAP error message. 075 */ 076 private static class SearchResultCode 077 { 078 /** The global result code. */ 079 private ResultCode resultCode = ResultCode.UNDEFINED; 080 081 /** The global error message. */ 082 private LocalizableMessageBuilder errorMessage = new LocalizableMessageBuilder(LocalizableMessage.EMPTY); 083 084 /** 085 * Creates a new instance of a workflow result code and initializes it with 086 * a result code and an error message. 087 * 088 * @param resultCode 089 * the initial value for the result code 090 * @param errorMessage 091 * the initial value for the error message 092 */ 093 SearchResultCode(ResultCode resultCode, LocalizableMessageBuilder errorMessage) 094 { 095 this.resultCode = resultCode; 096 this.errorMessage = errorMessage; 097 } 098 099 /** 100 * Elaborates a global result code. A workflow may execute an operation on 101 * several subordinate workflows. In such case, the parent workflow has to 102 * take into account all the subordinate result codes to elaborate a global 103 * result code. Sometimes, a referral result code has to be turned into a 104 * reference entry. When such case is occurring the 105 * elaborateGlobalResultCode method will return true. The global result code 106 * is elaborated as follows: 107 * 108 * <PRE> 109 * -----------+------------+------------+------------------------------- 110 * new | current | resulting | 111 * resultCode | resultCode | resultCode | action 112 * -----------+------------+------------+------------------------------- 113 * SUCCESS NO_SUCH_OBJ SUCCESS - 114 * REFERRAL SUCCESS send reference entry to client 115 * other [unchanged] - 116 * --------------------------------------------------------------------- 117 * NO_SUCH_OBJ SUCCESS [unchanged] - 118 * REFERRAL [unchanged] - 119 * other [unchanged] - 120 * --------------------------------------------------------------------- 121 * REFERRAL SUCCESS [unchanged] send reference entry to client 122 * REFERRAL SUCCESS send reference entry to client 123 * NO_SUCH_OBJ REFERRAL - 124 * other [unchanged] send reference entry to client 125 * --------------------------------------------------------------------- 126 * others SUCCESS other - 127 * REFERRAL other send reference entry to client 128 * NO_SUCH_OBJ other - 129 * other2 [unchanged] - 130 * --------------------------------------------------------------------- 131 * </PRE> 132 * 133 * @param newResultCode 134 * the new result code to take into account 135 * @param newErrorMessage 136 * the new error message associated to the new error code 137 * @return <code>true</code> if a referral result code must be turned into a 138 * reference entry 139 */ 140 private boolean elaborateGlobalResultCode(ResultCode newResultCode, LocalizableMessageBuilder newErrorMessage) 141 { 142 // if global result code has not been set yet then just take the new 143 // result code as is 144 if (resultCode == ResultCode.UNDEFINED) 145 { 146 resultCode = newResultCode; 147 errorMessage = new LocalizableMessageBuilder(newErrorMessage); 148 return false; 149 } 150 151 // Elaborate the new result code (see table in the description header). 152 switch (newResultCode.asEnum()) 153 { 154 case SUCCESS: 155 switch (resultCode.asEnum()) 156 { 157 case NO_SUCH_OBJECT: 158 resultCode = ResultCode.SUCCESS; 159 errorMessage = new LocalizableMessageBuilder(LocalizableMessage.EMPTY); 160 return false; 161 case REFERRAL: 162 resultCode = ResultCode.SUCCESS; 163 errorMessage = new LocalizableMessageBuilder(LocalizableMessage.EMPTY); 164 return true; 165 default: 166 // global resultCode remains the same 167 return false; 168 } 169 170 case NO_SUCH_OBJECT: 171 // global resultCode remains the same 172 return false; 173 174 case REFERRAL: 175 switch (resultCode.asEnum()) 176 { 177 case REFERRAL: 178 resultCode = ResultCode.SUCCESS; 179 errorMessage = new LocalizableMessageBuilder(LocalizableMessage.EMPTY); 180 return true; 181 case NO_SUCH_OBJECT: 182 resultCode = ResultCode.REFERRAL; 183 errorMessage = new LocalizableMessageBuilder(LocalizableMessage.EMPTY); 184 return false; 185 default: 186 // global resultCode remains the same 187 return true; 188 } 189 190 default: 191 switch (resultCode.asEnum()) 192 { 193 case REFERRAL: 194 resultCode = newResultCode; 195 errorMessage = new LocalizableMessageBuilder(newErrorMessage); 196 return true; 197 case SUCCESS: 198 case NO_SUCH_OBJECT: 199 resultCode = newResultCode; 200 errorMessage = new LocalizableMessageBuilder(newErrorMessage); 201 return false; 202 default: 203 // Do nothing (we don't want to override the first error) 204 return false; 205 } 206 } 207 } 208 } 209 210 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 211 212 /** The backend's baseDN mapped by this object. */ 213 private final DN baseDN; 214 215 /** The backend associated with the local workflow element. */ 216 private final Backend<?> backend; 217 218 /** The set of local backend workflow elements registered with the server. */ 219 private static TreeMap<DN, LocalBackendWorkflowElement> registeredLocalBackends = new TreeMap<>(); 220 221 /** A lock to guarantee safe concurrent access to the registeredLocalBackends variable. */ 222 private static final Object registeredLocalBackendsLock = new Object(); 223 224 /** 225 * Creates a new instance of the local backend workflow element. 226 * 227 * @param baseDN 228 * the backend's baseDN mapped by this object 229 * @param backend 230 * the backend associated to that workflow element 231 */ 232 private LocalBackendWorkflowElement(DN baseDN, Backend<?> backend) 233 { 234 this.baseDN = baseDN; 235 this.backend = backend; 236 } 237 238 /** 239 * Indicates whether the workflow element encapsulates a private local backend. 240 * 241 * @return <code>true</code> if the workflow element encapsulates a private 242 * local backend, <code>false</code> otherwise 243 */ 244 public boolean isPrivate() 245 { 246 return this.backend != null && this.backend.isPrivateBackend(); 247 } 248 249 /** 250 * Creates and registers a local backend with the server. 251 * 252 * @param baseDN 253 * the backend's baseDN mapped by this object 254 * @param backend 255 * the backend to associate with the local backend workflow element 256 * @return the existing local backend workflow element if it was already 257 * created or a newly created local backend workflow element. 258 */ 259 public static LocalBackendWorkflowElement createAndRegister(DN baseDN, Backend<?> backend) 260 { 261 LocalBackendWorkflowElement localBackend = registeredLocalBackends.get(baseDN); 262 if (localBackend == null) 263 { 264 localBackend = new LocalBackendWorkflowElement(baseDN, backend); 265 registerLocalBackend(localBackend); 266 } 267 return localBackend; 268 } 269 270 /** 271 * Removes a local backend that was registered with the server. 272 * 273 * @param baseDN 274 * the identifier of the workflow to remove 275 */ 276 public static void remove(DN baseDN) 277 { 278 deregisterLocalBackend(baseDN); 279 } 280 281 /** 282 * Removes all the local backends that were registered with the server. 283 * This function is intended to be called when the server is shutting down. 284 */ 285 public static void removeAll() 286 { 287 synchronized (registeredLocalBackendsLock) 288 { 289 for (LocalBackendWorkflowElement localBackend : registeredLocalBackends.values()) 290 { 291 deregisterLocalBackend(localBackend.getBaseDN()); 292 } 293 } 294 } 295 296 /** 297 * Check if an OID is for a proxy authorization control. 298 * 299 * @param oid The OID to check 300 * @return <code>true</code> if the OID is for a proxy auth v1 or v2 control, 301 * <code>false</code> otherwise. 302 */ 303 static boolean isProxyAuthzControl(String oid) 304 { 305 return OID_PROXIED_AUTH_V1.equals(oid) || OID_PROXIED_AUTH_V2.equals(oid); 306 } 307 308 /** 309 * Removes all the disallowed request controls from the provided operation. 310 * <p> 311 * As per RFC 4511 4.1.11, if a disallowed request control is critical, then a 312 * DirectoryException is thrown with unavailableCriticalExtension. Otherwise, 313 * if the disallowed request control is non critical, it is removed because we 314 * do not want the backend to process it. 315 * 316 * @param operation 317 * the operation currently processed 318 * @throws DirectoryException 319 * If a disallowed request control is critical, thrown with 320 * unavailableCriticalExtension. If an error occurred while 321 * performing the access control check. For example, if an attribute 322 * could not be decoded. Care must be taken not to expose any 323 * potentially sensitive information in the exception. 324 */ 325 static void removeAllDisallowedControls(DN targetDN, Operation operation) throws DirectoryException 326 { 327 for (Iterator<Control> iter = operation.getRequestControls().iterator(); iter.hasNext();) 328 { 329 final Control control = iter.next(); 330 if (isProxyAuthzControl(control.getOID())) 331 { 332 continue; 333 } 334 335 if (!getAccessControlHandler().isAllowed(targetDN, operation, control)) 336 { 337 // As per RFC 4511 4.1.11. 338 if (control.isCritical()) 339 { 340 throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 341 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(control.getOID())); 342 } 343 344 // We do not want the backend to process this non-critical control, so remove it. 345 iter.remove(); 346 } 347 } 348 } 349 350 /** 351 * Evaluate all aci and privilege checks for any proxy auth controls. 352 * This must be done before evaluating all other controls so that their aci 353 * can then be checked correctly. 354 * 355 * @param operation The operation containing the controls 356 * @throws DirectoryException if a proxy auth control is found but cannot 357 * be used. 358 */ 359 static void evaluateProxyAuthControls(Operation operation) throws DirectoryException 360 { 361 for (Control control : operation.getRequestControls()) 362 { 363 final String oid = control.getOID(); 364 if (isProxyAuthzControl(oid)) 365 { 366 DN authDN = operation.getClientConnection().getAuthenticationInfo().getAuthenticationDN(); 367 if (getAccessControlHandler().isAllowed(authDN, operation, control)) 368 { 369 processProxyAuthControls(operation, oid); 370 } 371 else 372 { 373 // As per RFC 4511 4.1.11. 374 if (control.isCritical()) 375 { 376 throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 377 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(control.getOID())); 378 } 379 } 380 } 381 } 382 } 383 384 /** 385 * Check the requester has the PROXIED_AUTH privilege in order to be able to use a proxy auth control. 386 * 387 * @param operation The operation being checked 388 * @throws DirectoryException If insufficient privileges are detected 389 */ 390 private static void checkPrivilegeForProxyAuthControl(Operation operation) throws DirectoryException 391 { 392 if (! operation.getClientConnection().hasPrivilege(Privilege.PROXIED_AUTH, operation)) 393 { 394 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, 395 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); 396 } 397 } 398 399 /** 400 * Check the requester has the authorization user in scope of proxy aci. 401 * 402 * @param operation The operation being checked 403 * @param authorizationEntry The entry being authorized as (e.g. from a proxy auth control) 404 * @throws DirectoryException If no proxy permission is allowed 405 */ 406 private static void checkAciForProxyAuthControl(Operation operation, Entry authorizationEntry) 407 throws DirectoryException 408 { 409 if (! AccessControlConfigManager.getInstance().getAccessControlHandler() 410 .mayProxy(operation.getClientConnection().getAuthenticationInfo().getAuthenticationEntry(), 411 authorizationEntry, operation)) 412 { 413 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, 414 ERR_PROXYAUTH_AUTHZ_NOT_PERMITTED.get(authorizationEntry.getName())); 415 } 416 } 417 /** 418 * Process the operation control with the given oid if it is a proxy auth control. 419 * 420 * Privilege and initial aci checks on the authenticating user are performed. The authenticating 421 * user must have the proxied-auth privilege, and the authz user must be in the scope of aci 422 * allowing the proxy right to the authenticating user. 423 * 424 * @param operation The operation containing the control(s) 425 * @param oid The OID of the detected proxy auth control 426 * @throws DirectoryException 427 */ 428 private static void processProxyAuthControls(Operation operation, String oid) 429 throws DirectoryException 430 { 431 final Entry authorizationEntry; 432 433 if (OID_PROXIED_AUTH_V1.equals(oid)) 434 { 435 final ProxiedAuthV1Control proxyControlV1 = operation.getRequestControl(ProxiedAuthV1Control.DECODER); 436 // Log usage of legacy proxy authz V1 control. 437 operation.addAdditionalLogItem(AdditionalLogItem.keyOnly(operation.getClass(), 438 "obsoleteProxiedAuthzV1Control")); 439 checkPrivilegeForProxyAuthControl(operation); 440 authorizationEntry = proxyControlV1.getAuthorizationEntry(); 441 } 442 else if (OID_PROXIED_AUTH_V2.equals(oid)) 443 { 444 final ProxiedAuthV2Control proxyControlV2 = operation.getRequestControl(ProxiedAuthV2Control.DECODER); 445 checkPrivilegeForProxyAuthControl(operation); 446 authorizationEntry = proxyControlV2.getAuthorizationEntry(); 447 } 448 else 449 { 450 return; 451 } 452 453 checkAciForProxyAuthControl(operation, authorizationEntry); 454 operation.setAuthorizationEntry(authorizationEntry); 455 456 operation.setProxiedAuthorizationDN( 457 authorizationEntry != null ? authorizationEntry.getName() : DN.rootDN()); 458 } 459 460 /** 461 * Returns a new {@link DirectoryException} built from the provided 462 * resultCodes and messages. Depending on whether ACIs prevent information 463 * disclosure, the provided resultCode and message will be masked and 464 * altResultCode and altMessage will be used instead. 465 * 466 * @param operation 467 * the operation for which to check if ACIs prevent information 468 * disclosure 469 * @param entry 470 * the entry for which to check if ACIs prevent information 471 * disclosure, if null, then a fake entry will be created from the 472 * entryDN parameter 473 * @param entryDN 474 * the entry dn for which to check if ACIs prevent information 475 * disclosure. Only used if entry is null. 476 * @param resultCode 477 * the result code to put on the DirectoryException if ACIs allow 478 * disclosure. Otherwise it will be put on the DirectoryException as 479 * a masked result code. 480 * @param message 481 * the message to put on the DirectoryException if ACIs allow 482 * disclosure. Otherwise it will be put on the DirectoryException as 483 * a masked message. 484 * @param altResultCode 485 * the result code to put on the DirectoryException if ACIs do not 486 * allow disclosing the resultCode. 487 * @param altMessage 488 * the result code to put on the DirectoryException if ACIs do not 489 * allow disclosing the message. 490 * @return a new DirectoryException containing the provided resultCodes and 491 * messages depending on ACI allowing disclosure or not 492 * @throws DirectoryException 493 * If an error occurred while performing the access control check. 494 */ 495 static DirectoryException newDirectoryException(Operation operation, 496 Entry entry, DN entryDN, ResultCode resultCode, LocalizableMessage message, 497 ResultCode altResultCode, LocalizableMessage altMessage) throws DirectoryException 498 { 499 if (getAccessControlHandler().canDiscloseInformation(entry, entryDN, operation)) 500 { 501 return new DirectoryException(resultCode, message); 502 } 503 // replacement reason returned to the user 504 final DirectoryException ex = new DirectoryException(altResultCode, altMessage); 505 // real underlying reason 506 ex.setMaskedResultCode(resultCode); 507 ex.setMaskedMessage(message); 508 return ex; 509 } 510 511 /** 512 * Sets the provided resultCodes and messages on the provided operation. 513 * Depending on whether ACIs prevent information disclosure, the provided 514 * resultCode and message will be masked and altResultCode and altMessage will 515 * be used instead. 516 * 517 * @param operation 518 * the operation for which to check if ACIs prevent information 519 * disclosure 520 * @param entry 521 * the entry for which to check if ACIs prevent information 522 * disclosure, if null, then a fake entry will be created from the 523 * entryDN parameter 524 * @param entryDN 525 * the entry dn for which to check if ACIs prevent information 526 * disclosure. Only used if entry is null. 527 * @param resultCode 528 * the result code to put on the DirectoryException if ACIs allow 529 * disclosure. Otherwise it will be put on the DirectoryException as 530 * a masked result code. 531 * @param message 532 * the message to put on the DirectoryException if ACIs allow 533 * disclosure. Otherwise it will be put on the DirectoryException as 534 * a masked message. 535 * @param altResultCode 536 * the result code to put on the DirectoryException if ACIs do not 537 * allow disclosing the resultCode. 538 * @param altMessage 539 * the result code to put on the DirectoryException if ACIs do not 540 * allow disclosing the message. 541 * @throws DirectoryException 542 * If an error occurred while performing the access control check. 543 */ 544 static void setResultCodeAndMessageNoInfoDisclosure(Operation operation, 545 Entry entry, DN entryDN, ResultCode resultCode, LocalizableMessage message, 546 ResultCode altResultCode, LocalizableMessage altMessage) throws DirectoryException 547 { 548 if (getAccessControlHandler().canDiscloseInformation(entry, entryDN, operation)) 549 { 550 operation.setResultCode(resultCode); 551 operation.appendErrorMessage(message); 552 } 553 else 554 { 555 // replacement reason returned to the user 556 operation.setResultCode(altResultCode); 557 operation.appendErrorMessage(altMessage); 558 // real underlying reason 559 operation.setMaskedResultCode(resultCode); 560 operation.appendMaskedErrorMessage(message); 561 } 562 } 563 564 /** 565 * Removes the matchedDN from the supplied operation if ACIs prevent its 566 * disclosure. 567 * 568 * @param operation 569 * where to filter the matchedDN from 570 */ 571 static void filterNonDisclosableMatchedDN(Operation operation) 572 { 573 if (operation.getMatchedDN() == null) 574 { 575 return; 576 } 577 578 try 579 { 580 if (!getAccessControlHandler().canDiscloseInformation(null, operation.getMatchedDN(), operation)) 581 { 582 operation.setMatchedDN(null); 583 } 584 } 585 catch (DirectoryException de) 586 { 587 logger.traceException(de); 588 589 operation.setResponseData(de); 590 // At this point it is impossible to tell whether the matchedDN can be 591 // disclosed. It is probably safer to hide it by default. 592 operation.setMatchedDN(null); 593 } 594 } 595 596 /** 597 * Adds the post-read response control to the response if requested. 598 * 599 * @param operation 600 * The update operation. 601 * @param postReadRequest 602 * The request control, if present. 603 * @param entry 604 * The post-update entry. 605 */ 606 static void addPostReadResponse(final Operation operation, 607 final LDAPPostReadRequestControl postReadRequest, final Entry entry) 608 { 609 if (postReadRequest == null) 610 { 611 return; 612 } 613 614 /* 615 * Virtual and collective attributes are only added to an entry when it is 616 * read from the backend, not before it is written, so we need to add them 617 * ourself. 618 */ 619 final Entry fullEntry = entry.duplicate(true); 620 621 // Even though the associated update succeeded, 622 // we should still check whether we should return the entry. 623 final SearchResultEntry unfilteredSearchEntry = new SearchResultEntry(fullEntry, null); 624 if (getAccessControlHandler().maySend(operation, unfilteredSearchEntry)) 625 { 626 // Filter the entry based on the control's attribute list. 627 final Entry filteredEntry = fullEntry.filterEntry(postReadRequest.getRequestedAttributes(), false, false, false); 628 final SearchResultEntry filteredSearchEntry = new SearchResultEntry(filteredEntry, null); 629 630 // Strip out any attributes which access control denies access to. 631 getAccessControlHandler().filterEntry(operation, unfilteredSearchEntry, filteredSearchEntry); 632 633 operation.addResponseControl(new LDAPPostReadResponseControl(filteredSearchEntry)); 634 } 635 } 636 637 /** 638 * Adds the pre-read response control to the response if requested. 639 * 640 * @param operation 641 * The update operation. 642 * @param preReadRequest 643 * The request control, if present. 644 * @param entry 645 * The pre-update entry. 646 */ 647 static void addPreReadResponse(final Operation operation, 648 final LDAPPreReadRequestControl preReadRequest, final Entry entry) 649 { 650 if (preReadRequest == null) 651 { 652 return; 653 } 654 655 // Even though the associated update succeeded, 656 // we should still check whether we should return the entry. 657 final SearchResultEntry unfilteredSearchEntry = new SearchResultEntry(entry, null); 658 if (getAccessControlHandler().maySend(operation, unfilteredSearchEntry)) 659 { 660 // Filter the entry based on the control's attribute list. 661 final Entry filteredEntry = entry.filterEntry(preReadRequest.getRequestedAttributes(), false, false, false); 662 final SearchResultEntry filteredSearchEntry = new SearchResultEntry(filteredEntry, null); 663 664 // Strip out any attributes which access control denies access to. 665 getAccessControlHandler().filterEntry(operation, unfilteredSearchEntry, filteredSearchEntry); 666 667 operation.addResponseControl(new LDAPPreReadResponseControl(filteredSearchEntry)); 668 } 669 } 670 671 private static AccessControlHandler<?> getAccessControlHandler() 672 { 673 return AccessControlConfigManager.getInstance().getAccessControlHandler(); 674 } 675 676 /** 677 * Registers a local backend with the server. 678 * 679 * @param localBackend the local backend to register with the server 680 */ 681 private static void registerLocalBackend(LocalBackendWorkflowElement localBackend) 682 { 683 synchronized (registeredLocalBackendsLock) 684 { 685 DN baseDN = localBackend.getBaseDN(); 686 LocalBackendWorkflowElement existingLocalBackend = registeredLocalBackends.get(baseDN); 687 if (existingLocalBackend == null) 688 { 689 TreeMap<DN, LocalBackendWorkflowElement> newLocalBackends = new TreeMap<>(registeredLocalBackends); 690 newLocalBackends.put(baseDN, localBackend); 691 registeredLocalBackends = newLocalBackends; 692 } 693 } 694 } 695 696 /** 697 * Deregisters a local backend with the server. 698 * 699 * @param baseDN 700 * the identifier of the local backend to remove 701 */ 702 private static void deregisterLocalBackend(DN baseDN) 703 { 704 synchronized (registeredLocalBackendsLock) 705 { 706 LocalBackendWorkflowElement existingLocalBackend = registeredLocalBackends.get(baseDN); 707 if (existingLocalBackend != null) 708 { 709 TreeMap<DN, LocalBackendWorkflowElement> newLocalBackends = new TreeMap<>(registeredLocalBackends); 710 newLocalBackends.remove(baseDN); 711 registeredLocalBackends = newLocalBackends; 712 } 713 } 714 } 715 716 /** 717 * Executes the workflow for an operation. 718 * 719 * @param operation 720 * the operation to execute 721 * @throws CanceledOperationException 722 * if this operation should be canceled 723 */ 724 private void execute(Operation operation) throws CanceledOperationException { 725 switch (operation.getOperationType()) 726 { 727 case BIND: 728 new LocalBackendBindOperation((BindOperation) operation).processLocalBind(this); 729 break; 730 731 case SEARCH: 732 new LocalBackendSearchOperation((SearchOperation) operation).processLocalSearch(this); 733 break; 734 735 case ADD: 736 new LocalBackendAddOperation((AddOperation) operation).processLocalAdd(this); 737 break; 738 739 case DELETE: 740 new LocalBackendDeleteOperation((DeleteOperation) operation).processLocalDelete(this); 741 break; 742 743 case MODIFY: 744 new LocalBackendModifyOperation((ModifyOperation) operation).processLocalModify(this); 745 break; 746 747 case MODIFY_DN: 748 new LocalBackendModifyDNOperation((ModifyDNOperation) operation).processLocalModifyDN(this); 749 break; 750 751 case COMPARE: 752 new LocalBackendCompareOperation((CompareOperation) operation).processLocalCompare(this); 753 break; 754 755 case ABANDON: 756 // There is no processing for an abandon operation. 757 break; 758 759 default: 760 throw new AssertionError("Attempted to execute an invalid operation type: " 761 + operation.getOperationType() + " (" + operation + ")"); 762 } 763 } 764 765 /** 766 * Attaches the current local operation to the global operation so that 767 * operation runner can execute local operation post response later on. 768 * 769 * @param <O> subtype of Operation 770 * @param <L> subtype of LocalBackendOperation 771 * @param globalOperation the global operation to which local operation 772 * should be attached to 773 * @param currentLocalOperation the local operation to attach to the global 774 * operation 775 */ 776 @SuppressWarnings("unchecked") 777 static <O extends Operation, L> void attachLocalOperation(O globalOperation, L currentLocalOperation) 778 { 779 List<?> existingAttachment = (List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS); 780 List<L> newAttachment = new ArrayList<>(); 781 782 if (existingAttachment != null) 783 { 784 // This line raises an unchecked conversion warning. 785 // There is nothing we can do to prevent this warning 786 // so let's get rid of it since we know the cast is safe. 787 newAttachment.addAll ((List<L>) existingAttachment); 788 } 789 newAttachment.add (currentLocalOperation); 790 globalOperation.setAttachment(Operation.LOCALBACKENDOPERATIONS, newAttachment); 791 } 792 793 /** 794 * Provides the workflow element identifier. 795 * 796 * @return the workflow element identifier 797 */ 798 public DN getBaseDN() 799 { 800 return baseDN; 801 } 802 803 /** 804 * Gets the backend associated with this local backend workflow 805 * element. 806 * 807 * @return The backend associated with this local backend workflow 808 * element. 809 */ 810 public Backend<?> getBackend() 811 { 812 return backend; 813 } 814 815 /** 816 * Checks if an update operation can be performed against a backend. The 817 * operation will be rejected based on the server and backend writability 818 * modes. 819 * 820 * @param backend 821 * The backend handling the update. 822 * @param op 823 * The update operation. 824 * @param entryDN 825 * The name of the entry being updated. 826 * @param serverMsg 827 * The message to log if the update was rejected because the server 828 * is read-only. 829 * @param backendMsg 830 * The message to log if the update was rejected because the backend 831 * is read-only. 832 * @throws DirectoryException 833 * If the update operation has been rejected. 834 */ 835 static void checkIfBackendIsWritable(Backend<?> backend, Operation op, 836 DN entryDN, LocalizableMessageDescriptor.Arg1<Object> serverMsg, 837 LocalizableMessageDescriptor.Arg1<Object> backendMsg) 838 throws DirectoryException 839 { 840 if (!backend.isPrivateBackend()) 841 { 842 checkIfWritable(DirectoryServer.getWritabilityMode(), op, serverMsg, entryDN); 843 checkIfWritable(backend.getWritabilityMode(), op, backendMsg, entryDN); 844 } 845 } 846 847 private static void checkIfWritable(WritabilityMode writabilityMode, Operation op, 848 LocalizableMessageDescriptor.Arg1<Object> errorMsg, DN entryDN) throws DirectoryException 849 { 850 switch (writabilityMode) 851 { 852 case DISABLED: 853 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, errorMsg.get(entryDN)); 854 855 case INTERNAL_ONLY: 856 if (!op.isInternalOperation() && !op.isSynchronizationOperation()) 857 { 858 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, errorMsg.get(entryDN)); 859 } 860 } 861 } 862 863 /** 864 * Executes the supplied operation. 865 * 866 * @param operation 867 * the operation to execute 868 * @param entryDN 869 * the entry DN whose backend will be used 870 * @return true if the operation successfully executed, false otherwise 871 * @throws CanceledOperationException 872 * if this operation should be cancelled. 873 */ 874 public static boolean execute(Operation operation, DN entryDN) throws CanceledOperationException 875 { 876 LocalBackendWorkflowElement workflow = getLocalBackendWorkflowElement(entryDN); 877 if (workflow == null) 878 { 879 // We have found no backend for the requested base DN, 880 // just return a no such entry result code and stop the processing. 881 if (operation instanceof AbstractOperation) 882 { 883 ((AbstractOperation) operation).updateOperationErrMsgAndResCode(); 884 } 885 return false; 886 } 887 888 if (workflow.getBaseDN().isRootDN()) 889 { 890 executeOnRootDSE(operation, workflow); 891 } 892 else 893 { 894 executeOnNonRootDSE(operation, workflow); 895 } 896 return true; 897 } 898 899 private static LocalBackendWorkflowElement getLocalBackendWorkflowElement(DN entryDN) 900 { 901 if (entryDN.isRootDN()) 902 { 903 return registeredLocalBackends.get(entryDN); 904 } 905 /* 906 * Try to minimize the number of lookups in the Map to find the backend containing the entry. 907 * If the DN contains many RDNs it is faster to iterate through the list of registered backends, 908 * otherwise iterating through the parents requires less lookups. It also avoids some attacks 909 * where we would spend time going through the list of all parents to finally decide the 910 * baseDN is absent. 911 */ 912 if (entryDN.size() <= registeredLocalBackends.size()) 913 { 914 while (!entryDN.isRootDN()) 915 { 916 final LocalBackendWorkflowElement workflow = registeredLocalBackends.get(entryDN); 917 if (workflow != null) 918 { 919 return workflow; 920 } 921 entryDN = entryDN.parent(); 922 } 923 return null; 924 } 925 else 926 { 927 LocalBackendWorkflowElement workflow = null; 928 int currentSize = 0; 929 for (DN backendDN : registeredLocalBackends.keySet()) 930 { 931 if (entryDN.isSubordinateOrEqualTo(backendDN) && backendDN.size() > currentSize) 932 { 933 workflow = registeredLocalBackends.get(backendDN); 934 currentSize = backendDN.size(); 935 } 936 } 937 return workflow; 938 } 939 } 940 941 /** 942 * Executes an operation on the root DSE entry. 943 * 944 * @param operation 945 * the operation to execute 946 * @param workflow 947 * the workflow where to execute the operation 948 * @throws CanceledOperationException 949 * if this operation should be cancelled. 950 */ 951 private static void executeOnRootDSE(Operation operation, LocalBackendWorkflowElement workflow) 952 throws CanceledOperationException 953 { 954 OperationType operationType = operation.getOperationType(); 955 if (operationType == OperationType.SEARCH) 956 { 957 executeSearch((SearchOperation) operation, workflow); 958 } 959 else 960 { 961 workflow.execute(operation); 962 } 963 } 964 965 /** 966 * Executes a search operation on the the root DSE entry. 967 * 968 * @param searchOp 969 * the operation to execute 970 * @param workflow 971 * the workflow where to execute the operation 972 * @throws CanceledOperationException 973 * if this operation should be cancelled. 974 */ 975 private static void executeSearch(SearchOperation searchOp, LocalBackendWorkflowElement workflow) 976 throws CanceledOperationException 977 { 978 // Keep a the original search scope because we will alter it in the operation 979 SearchScope originalScope = searchOp.getScope(); 980 981 // Search base? 982 // The root DSE entry itself is never returned unless the operation 983 // is a search base on the null suffix. 984 if (originalScope == SearchScope.BASE_OBJECT) 985 { 986 workflow.execute(searchOp); 987 return; 988 } 989 990 // Create a workflow result code in case we need to perform search in 991 // subordinate workflows. 992 SearchResultCode searchResultCode = 993 new SearchResultCode(searchOp.getResultCode(), searchOp.getErrorMessage()); 994 995 // The search scope is not 'base', so let's do a search on all the public 996 // naming contexts with appropriate new search scope and new base DN. 997 SearchScope newScope = elaborateScopeForSearchInSubordinates(originalScope); 998 searchOp.setScope(newScope); 999 DN originalBaseDN = searchOp.getBaseDN(); 1000 1001 for (LocalBackendWorkflowElement subordinate : getRootDSESubordinates()) 1002 { 1003 // We have to change the operation request base DN to match the 1004 // subordinate workflow base DN. Otherwise the workflow will 1005 // return a no such entry result code as the operation request 1006 // base DN is a superior of the workflow base DN! 1007 DN ncDN = subordinate.getBaseDN(); 1008 1009 // Set the new request base DN then do execute the operation 1010 // in the naming context workflow. 1011 searchOp.setBaseDN(ncDN); 1012 execute(searchOp, ncDN); 1013 boolean sendReferenceEntry = searchResultCode.elaborateGlobalResultCode( 1014 searchOp.getResultCode(), searchOp.getErrorMessage()); 1015 if (sendReferenceEntry) 1016 { 1017 // TODO jdemendi - turn a referral result code into a reference entry 1018 // and send the reference entry to the client application 1019 } 1020 } 1021 1022 // Now restore the original request base DN and original search scope 1023 searchOp.setBaseDN(originalBaseDN); 1024 searchOp.setScope(originalScope); 1025 1026 // If the result code is still uninitialized (ie no naming context), 1027 // we should return NO_SUCH_OBJECT 1028 searchResultCode.elaborateGlobalResultCode( 1029 ResultCode.NO_SUCH_OBJECT, new LocalizableMessageBuilder(LocalizableMessage.EMPTY)); 1030 1031 // Set the operation result code and error message 1032 searchOp.setResultCode(searchResultCode.resultCode); 1033 searchOp.setErrorMessage(searchResultCode.errorMessage); 1034 } 1035 1036 private static Collection<LocalBackendWorkflowElement> getRootDSESubordinates() 1037 { 1038 final RootDSEBackend rootDSEBackend = DirectoryServer.getRootDSEBackend(); 1039 1040 final List<LocalBackendWorkflowElement> results = new ArrayList<>(); 1041 for (DN subordinateBaseDN : rootDSEBackend.getSubordinateBaseDNs().keySet()) 1042 { 1043 results.add(registeredLocalBackends.get(subordinateBaseDN)); 1044 } 1045 return results; 1046 } 1047 1048 private static void executeOnNonRootDSE(Operation operation, LocalBackendWorkflowElement workflow) 1049 throws CanceledOperationException 1050 { 1051 workflow.execute(operation); 1052 1053 // For subtree search operation we need to go through the subordinate nodes. 1054 if (operation.getOperationType() == OperationType.SEARCH) 1055 { 1056 executeSearchOnSubordinates((SearchOperation) operation, workflow); 1057 } 1058 } 1059 1060 /** 1061 * Executes a search operation on the subordinate workflows. 1062 * 1063 * @param searchOp 1064 * the search operation to execute 1065 * @param workflow 1066 * the workflow element 1067 * @throws CanceledOperationException 1068 * if this operation should be canceled. 1069 */ 1070 private static void executeSearchOnSubordinates(SearchOperation searchOp, LocalBackendWorkflowElement workflow) 1071 throws CanceledOperationException { 1072 // If the scope of the search is 'base' then it's useless to search 1073 // in the subordinate workflows. 1074 SearchScope originalScope = searchOp.getScope(); 1075 if (originalScope == SearchScope.BASE_OBJECT) 1076 { 1077 return; 1078 } 1079 1080 // Elaborate the new search scope before executing the search operation 1081 // in the subordinate workflows. 1082 SearchScope newScope = elaborateScopeForSearchInSubordinates(originalScope); 1083 searchOp.setScope(newScope); 1084 1085 // Let's search in the subordinate workflows. 1086 SearchResultCode searchResultCode = new SearchResultCode(searchOp.getResultCode(), searchOp.getErrorMessage()); 1087 DN originalBaseDN = searchOp.getBaseDN(); 1088 for (LocalBackendWorkflowElement subordinate : getSubordinates(workflow)) 1089 { 1090 // We have to change the operation request base DN to match the 1091 // subordinate workflow base DN. Otherwise the workflow will 1092 // return a no such entry result code as the operation request 1093 // base DN is a superior of the subordinate workflow base DN. 1094 DN subordinateDN = subordinate.getBaseDN(); 1095 1096 // If the new search scope is 'base' and the search base DN does not 1097 // map the subordinate workflow then skip the subordinate workflow. 1098 if (newScope == SearchScope.BASE_OBJECT && !subordinateDN.parent().equals(originalBaseDN)) 1099 { 1100 continue; 1101 } 1102 1103 // If the request base DN is not a subordinate of the subordinate 1104 // workflow base DN then do not search in the subordinate workflow. 1105 if (!originalBaseDN.isSuperiorOrEqualTo(subordinateDN)) 1106 { 1107 continue; 1108 } 1109 1110 // Set the new request base DN and do execute the 1111 // operation in the subordinate workflow. 1112 searchOp.setBaseDN(subordinateDN); 1113 execute(searchOp, subordinateDN); 1114 boolean sendReferenceEntry = searchResultCode.elaborateGlobalResultCode( 1115 searchOp.getResultCode(), searchOp.getErrorMessage()); 1116 if (sendReferenceEntry) 1117 { 1118 // TODO jdemendi - turn a referral result code into a reference entry 1119 // and send the reference entry to the client application 1120 } 1121 } 1122 1123 // Now we are done with the operation, let's restore the original 1124 // base DN and search scope in the operation. 1125 searchOp.setBaseDN(originalBaseDN); 1126 searchOp.setScope(originalScope); 1127 1128 // Update the operation result code and error message 1129 searchOp.setResultCode(searchResultCode.resultCode); 1130 searchOp.setErrorMessage(searchResultCode.errorMessage); 1131 } 1132 1133 private static Collection<LocalBackendWorkflowElement> getSubordinates(LocalBackendWorkflowElement workflow) 1134 { 1135 final DN baseDN = workflow.getBaseDN(); 1136 final Backend<?> backend = workflow.getBackend(); 1137 1138 final ArrayList<LocalBackendWorkflowElement> results = new ArrayList<>(); 1139 for (Backend<?> subordinate : backend.getSubordinateBackends()) 1140 { 1141 for (DN subordinateDN : subordinate.getBaseDNs()) 1142 { 1143 if (subordinateDN.isSubordinateOrEqualTo(baseDN)) 1144 { 1145 results.add(registeredLocalBackends.get(subordinateDN)); 1146 } 1147 } 1148 } 1149 return results; 1150 } 1151 1152 /** 1153 * Elaborates a new search scope according to the current search scope. The 1154 * new scope is intended to be used for searches on subordinate workflows. 1155 * 1156 * @param currentScope 1157 * the current search scope 1158 * @return the new scope to use for searches on subordinate workflows, 1159 * <code>null</code> when current scope is 'base' 1160 */ 1161 private static SearchScope elaborateScopeForSearchInSubordinates(SearchScope currentScope) 1162 { 1163 switch (currentScope.asEnum()) 1164 { 1165 case BASE_OBJECT: 1166 return null; 1167 case SINGLE_LEVEL: 1168 return SearchScope.BASE_OBJECT; 1169 case SUBORDINATES: 1170 case WHOLE_SUBTREE: 1171 return SearchScope.WHOLE_SUBTREE; 1172 default: 1173 return currentScope; 1174 } 1175 } 1176 1177 static DN findMatchedDN(DN entryDN) 1178 { 1179 try 1180 { 1181 DN matchedDN = DirectoryServer.getParentDNInSuffix(entryDN); 1182 while (matchedDN != null) 1183 { 1184 if (DirectoryServer.entryExists(matchedDN)) 1185 { 1186 return matchedDN; 1187 } 1188 1189 matchedDN = DirectoryServer.getParentDNInSuffix(matchedDN); 1190 } 1191 } 1192 catch (Exception e) 1193 { 1194 logger.traceException(e); 1195 } 1196 return null; 1197 } 1198 1199 @Override 1200 public String toString() 1201 { 1202 return getClass().getSimpleName() 1203 + " backend=" + this.backend 1204 + " baseDN=" + this.baseDN; 1205 } 1206}