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