001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.workflowelement.localbackend; 028 029 030 031 import java.util.ArrayList; 032 import java.util.Iterator; 033 import java.util.LinkedHashSet; 034 import java.util.LinkedList; 035 import java.util.List; 036 import java.util.concurrent.locks.Lock; 037 038 import org.opends.messages.Message; 039 import org.opends.messages.MessageBuilder; 040 import org.opends.server.api.Backend; 041 import org.opends.server.api.ChangeNotificationListener; 042 import org.opends.server.api.ClientConnection; 043 import org.opends.server.api.SynchronizationProvider; 044 import org.opends.server.api.plugin.PluginResult; 045 import org.opends.server.controls.LDAPAssertionRequestControl; 046 import org.opends.server.controls.LDAPPostReadRequestControl; 047 import org.opends.server.controls.LDAPPostReadResponseControl; 048 import org.opends.server.controls.LDAPPreReadRequestControl; 049 import org.opends.server.controls.LDAPPreReadResponseControl; 050 import org.opends.server.controls.ProxiedAuthV1Control; 051 import org.opends.server.controls.ProxiedAuthV2Control; 052 import org.opends.server.core.AccessControlConfigManager; 053 import org.opends.server.core.DirectoryServer; 054 import org.opends.server.core.ModifyDNOperation; 055 import org.opends.server.core.ModifyDNOperationWrapper; 056 import org.opends.server.core.PluginConfigManager; 057 import org.opends.server.loggers.debug.DebugTracer; 058 import org.opends.server.protocols.asn1.ASN1OctetString; 059 import org.opends.server.types.Attribute; 060 import org.opends.server.types.AttributeType; 061 import org.opends.server.types.AttributeValue; 062 import org.opends.server.types.ByteString; 063 import org.opends.server.types.CanceledOperationException; 064 import org.opends.server.types.Control; 065 import org.opends.server.types.DebugLogLevel; 066 import org.opends.server.types.DirectoryException; 067 import org.opends.server.types.DN; 068 import org.opends.server.types.Entry; 069 import org.opends.server.types.LDAPException; 070 import org.opends.server.types.LockManager; 071 import org.opends.server.types.Modification; 072 import org.opends.server.types.ModificationType; 073 import org.opends.server.types.Privilege; 074 import org.opends.server.types.RDN; 075 import org.opends.server.types.ResultCode; 076 import org.opends.server.types.SearchFilter; 077 import org.opends.server.types.SearchResultEntry; 078 import org.opends.server.types.SynchronizationProviderResult; 079 import org.opends.server.types.operation.PostOperationModifyDNOperation; 080 import org.opends.server.types.operation.PostResponseModifyDNOperation; 081 import org.opends.server.types.operation.PreOperationModifyDNOperation; 082 import org.opends.server.types.operation.PostSynchronizationModifyDNOperation; 083 084 import static org.opends.messages.CoreMessages.*; 085 import static org.opends.server.loggers.ErrorLogger.*; 086 import static org.opends.server.loggers.debug.DebugLogger.*; 087 import static org.opends.server.util.ServerConstants.*; 088 import static org.opends.server.util.StaticUtils.*; 089 090 091 092 /** 093 * This class defines an operation used to move an entry in a local backend 094 * of the Directory Server. 095 */ 096 public class LocalBackendModifyDNOperation 097 extends ModifyDNOperationWrapper 098 implements PreOperationModifyDNOperation, 099 PostOperationModifyDNOperation, 100 PostResponseModifyDNOperation, 101 PostSynchronizationModifyDNOperation 102 { 103 /** 104 * The tracer object for the debug logger. 105 */ 106 private static final DebugTracer TRACER = getTracer(); 107 108 109 110 // The backend in which the operation is to be processed. 111 private Backend backend; 112 113 // Indicates whether the no-op control was included in the request. 114 private boolean noOp; 115 116 // The client connection on which this operation was requested. 117 private ClientConnection clientConnection; 118 119 // The original DN of the entry. 120 DN entryDN; 121 122 // The current entry, before it is renamed. 123 private Entry currentEntry; 124 125 // The new entry, as it will appear after it has been renamed. 126 private Entry newEntry; 127 128 // The LDAP post-read request control, if present in the request. 129 private LDAPPostReadRequestControl postReadRequest; 130 131 // The LDAP pre-read request control, if present in the request. 132 private LDAPPreReadRequestControl preReadRequest; 133 134 // The new RDN for the entry. 135 private RDN newRDN; 136 137 138 139 /** 140 * Creates a new operation that may be used to move an entry in a 141 * local backend of the Directory Server. 142 * 143 * @param operation The operation to enhance. 144 */ 145 public LocalBackendModifyDNOperation (ModifyDNOperation operation) 146 { 147 super(operation); 148 LocalBackendWorkflowElement.attachLocalOperation (operation, this); 149 } 150 151 152 153 /** 154 * Retrieves the current entry, before it is renamed. This will not be 155 * available to pre-parse plugins or during the conflict resolution portion of 156 * the synchronization processing. 157 * 158 * @return The current entry, or <CODE>null</CODE> if it is not yet 159 * available. 160 */ 161 public final Entry getOriginalEntry() 162 { 163 return currentEntry; 164 } 165 166 167 168 /** 169 * Retrieves the new entry, as it will appear after it is renamed. This will 170 * not be available to pre-parse plugins or during the conflict resolution 171 * portion of the synchronization processing. 172 * 173 * @return The updated entry, or <CODE>null</CODE> if it is not yet 174 * available. 175 */ 176 public final Entry getUpdatedEntry() 177 { 178 return newEntry; 179 } 180 181 182 183 /** 184 * Process this modify DN operation in a local backend. 185 * 186 * @param backend The backend in which the modify DN operation should be 187 * processed. 188 * 189 * @throws CanceledOperationException if this operation should be 190 * cancelled 191 */ 192 void processLocalModifyDN(Backend backend) throws CanceledOperationException { 193 boolean executePostOpPlugins = false; 194 195 this.backend = backend; 196 197 clientConnection = getClientConnection(); 198 199 // Get the plugin config manager that will be used for invoking plugins. 200 PluginConfigManager pluginConfigManager = 201 DirectoryServer.getPluginConfigManager(); 202 203 // Check for a request to cancel this operation. 204 checkIfCanceled(false); 205 206 // Create a labeled block of code that we can break out of if a problem is 207 // detected. 208 modifyDNProcessing: 209 { 210 // Process the entry DN, newRDN, and newSuperior elements from their raw 211 // forms as provided by the client to the forms required for the rest of 212 // the modify DN processing. 213 entryDN = getEntryDN(); 214 215 newRDN = getNewRDN(); 216 if (newRDN == null) 217 { 218 break modifyDNProcessing; 219 } 220 221 DN newSuperior = getNewSuperior(); 222 if ((newSuperior == null) && 223 (getRawNewSuperior() != null)) 224 { 225 break modifyDNProcessing; 226 } 227 228 // Construct the new DN to use for the entry. 229 DN parentDN; 230 if (newSuperior == null) 231 { 232 parentDN = entryDN.getParentDNInSuffix(); 233 } 234 else 235 { 236 parentDN = newSuperior; 237 } 238 239 if ((parentDN == null) || parentDN.isNullDN()) 240 { 241 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 242 appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN))); 243 break modifyDNProcessing; 244 } 245 246 DN newDN = parentDN.concat(newRDN); 247 248 // Get the backend for the current entry, and the backend for the new 249 // entry. If either is null, or if they are different, then fail. 250 Backend currentBackend = backend; 251 if (currentBackend == null) 252 { 253 setResultCode(ResultCode.NO_SUCH_OBJECT); 254 appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get( 255 String.valueOf(entryDN))); 256 break modifyDNProcessing; 257 } 258 259 Backend newBackend = DirectoryServer.getBackend(newDN); 260 if (newBackend == null) 261 { 262 setResultCode(ResultCode.NO_SUCH_OBJECT); 263 appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get( 264 String.valueOf(entryDN), 265 String.valueOf(newDN))); 266 break modifyDNProcessing; 267 } 268 else if (! currentBackend.equals(newBackend)) 269 { 270 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 271 appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get( 272 String.valueOf(entryDN), 273 String.valueOf(newDN))); 274 break modifyDNProcessing; 275 } 276 277 278 // Check for a request to cancel this operation. 279 checkIfCanceled(false); 280 281 282 // Acquire write locks for the current and new DN. 283 Lock currentLock = null; 284 for (int i=0; i < 3; i++) 285 { 286 currentLock = LockManager.lockWrite(entryDN); 287 if (currentLock != null) 288 { 289 break; 290 } 291 } 292 293 if (currentLock == null) 294 { 295 setResultCode(DirectoryServer.getServerErrorResultCode()); 296 appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get( 297 String.valueOf(entryDN))); 298 break modifyDNProcessing; 299 } 300 301 Lock newLock = null; 302 try 303 { 304 for (int i=0; i < 3; i++) 305 { 306 newLock = LockManager.lockWrite(newDN); 307 if (newLock != null) 308 { 309 break; 310 } 311 } 312 } 313 catch (Exception e) 314 { 315 if (debugEnabled()) 316 { 317 TRACER.debugCaught(DebugLogLevel.ERROR, e); 318 } 319 320 LockManager.unlock(entryDN, currentLock); 321 322 if (newLock != null) 323 { 324 LockManager.unlock(newDN, newLock); 325 } 326 327 setResultCode(DirectoryServer.getServerErrorResultCode()); 328 appendErrorMessage(ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get( 329 String.valueOf(entryDN), String.valueOf(newDN), 330 getExceptionMessage(e))); 331 break modifyDNProcessing; 332 } 333 334 if (newLock == null) 335 { 336 LockManager.unlock(entryDN, currentLock); 337 338 setResultCode(DirectoryServer.getServerErrorResultCode()); 339 appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get( 340 String.valueOf(entryDN), 341 String.valueOf(newDN))); 342 break modifyDNProcessing; 343 } 344 345 try 346 { 347 // Check for a request to cancel this operation. 348 checkIfCanceled(false); 349 350 351 // Get the current entry from the appropriate backend. If it doesn't 352 // exist, then fail. 353 try 354 { 355 currentEntry = currentBackend.getEntry(entryDN); 356 } 357 catch (DirectoryException de) 358 { 359 if (debugEnabled()) 360 { 361 TRACER.debugCaught(DebugLogLevel.ERROR, de); 362 } 363 364 setResponseData(de); 365 break modifyDNProcessing; 366 } 367 368 if (getOriginalEntry() == null) 369 { 370 // See if one of the entry's ancestors exists. 371 parentDN = entryDN.getParentDNInSuffix(); 372 while (parentDN != null) 373 { 374 try 375 { 376 if (DirectoryServer.entryExists(parentDN)) 377 { 378 setMatchedDN(parentDN); 379 break; 380 } 381 } 382 catch (Exception e) 383 { 384 if (debugEnabled()) 385 { 386 TRACER.debugCaught(DebugLogLevel.ERROR, e); 387 } 388 break; 389 } 390 391 parentDN = parentDN.getParentDNInSuffix(); 392 } 393 394 setResultCode(ResultCode.NO_SUCH_OBJECT); 395 appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get( 396 String.valueOf(entryDN))); 397 break modifyDNProcessing; 398 } 399 400 401 // Invoke any conflict resolution processing that might be needed by the 402 // synchronization provider. 403 for (SynchronizationProvider provider : 404 DirectoryServer.getSynchronizationProviders()) 405 { 406 try 407 { 408 SynchronizationProviderResult result = 409 provider.handleConflictResolution(this); 410 if (! result.continueProcessing()) 411 { 412 setResultCode(result.getResultCode()); 413 appendErrorMessage(result.getErrorMessage()); 414 setMatchedDN(result.getMatchedDN()); 415 setReferralURLs(result.getReferralURLs()); 416 break modifyDNProcessing; 417 } 418 } 419 catch (DirectoryException de) 420 { 421 if (debugEnabled()) 422 { 423 TRACER.debugCaught(DebugLogLevel.ERROR, de); 424 } 425 426 logError(ERR_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED.get( 427 getConnectionID(), getOperationID(), 428 getExceptionMessage(de))); 429 430 setResponseData(de); 431 break modifyDNProcessing; 432 } 433 } 434 435 436 // Check to see if there are any controls in the request. If so, then 437 // see if there is any special processing required. 438 try 439 { 440 handleRequestControls(); 441 } 442 catch (DirectoryException de) 443 { 444 if (debugEnabled()) 445 { 446 TRACER.debugCaught(DebugLogLevel.ERROR, de); 447 } 448 449 setResponseData(de); 450 break modifyDNProcessing; 451 } 452 453 454 // Check to see if the client has permission to perform the 455 // modify DN. 456 457 // FIXME: for now assume that this will check all permission 458 // pertinent to the operation. This includes proxy authorization 459 // and any other controls specified. 460 461 // FIXME: earlier checks to see if the entry or new superior 462 // already exists may have already exposed sensitive information 463 // to the client. 464 if (! AccessControlConfigManager.getInstance(). 465 getAccessControlHandler().isAllowed(this)) 466 { 467 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); 468 appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( 469 String.valueOf(entryDN))); 470 break modifyDNProcessing; 471 } 472 473 // Duplicate the entry and set its new DN. Also, create an empty list 474 // to hold the attribute-level modifications. 475 newEntry = currentEntry.duplicate(false); 476 newEntry.setDN(newDN); 477 478 // init the modifications 479 addModification(null); 480 List<Modification> modifications = this.getModifications(); 481 482 483 484 // Apply any changes to the entry based on the change in its RDN. Also, 485 // perform schema checking on the updated entry. 486 try 487 { 488 applyRDNChanges(modifications); 489 } 490 catch (DirectoryException de) 491 { 492 if (debugEnabled()) 493 { 494 TRACER.debugCaught(DebugLogLevel.ERROR, de); 495 } 496 497 setResponseData(de); 498 break modifyDNProcessing; 499 } 500 501 502 // Check for a request to cancel this operation. 503 checkIfCanceled(false); 504 505 // Get a count of the current number of modifications. The 506 // pre-operation plugins may alter this list, and we need to be able to 507 // identify which changes were made after they're done. 508 int modCount = modifications.size(); 509 510 511 // If the operation is not a synchronization operation, 512 // Invoke the pre-operation modify DN plugins. 513 if (! isSynchronizationOperation()) 514 { 515 executePostOpPlugins = true; 516 PluginResult.PreOperation preOpResult = 517 pluginConfigManager.invokePreOperationModifyDNPlugins(this); 518 if (!preOpResult.continueProcessing()) 519 { 520 setResultCode(preOpResult.getResultCode()); 521 appendErrorMessage(preOpResult.getErrorMessage()); 522 setMatchedDN(preOpResult.getMatchedDN()); 523 setReferralURLs(preOpResult.getReferralURLs()); 524 break modifyDNProcessing; 525 } 526 } 527 528 529 // Check to see if any of the pre-operation plugins made any changes to 530 // the entry. If so, then apply them. 531 if (modifications.size() > modCount) 532 { 533 try 534 { 535 applyPreOpModifications(modifications, modCount); 536 } 537 catch (DirectoryException de) 538 { 539 if (debugEnabled()) 540 { 541 TRACER.debugCaught(DebugLogLevel.ERROR, de); 542 } 543 544 setResponseData(de); 545 break modifyDNProcessing; 546 } 547 } 548 549 550 // Check for a request to cancel this operation. 551 checkIfCanceled(true); 552 553 // Actually perform the modify DN operation. 554 // This should include taking 555 // care of any synchronization that might be needed. 556 try 557 { 558 // If it is not a private backend, then check to see if the server or 559 // backend is operating in read-only mode. 560 if (! currentBackend.isPrivateBackend()) 561 { 562 switch (DirectoryServer.getWritabilityMode()) 563 { 564 case DISABLED: 565 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 566 appendErrorMessage(ERR_MODDN_SERVER_READONLY.get( 567 String.valueOf(entryDN))); 568 break modifyDNProcessing; 569 570 case INTERNAL_ONLY: 571 if (! (isInternalOperation() || isSynchronizationOperation())) 572 { 573 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 574 appendErrorMessage(ERR_MODDN_SERVER_READONLY.get( 575 String.valueOf(entryDN))); 576 break modifyDNProcessing; 577 } 578 } 579 580 switch (currentBackend.getWritabilityMode()) 581 { 582 case DISABLED: 583 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 584 appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get( 585 String.valueOf(entryDN))); 586 break modifyDNProcessing; 587 588 case INTERNAL_ONLY: 589 if (! (isInternalOperation() || isSynchronizationOperation())) 590 { 591 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 592 appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get( 593 String.valueOf(entryDN))); 594 break modifyDNProcessing; 595 } 596 } 597 } 598 599 600 if (noOp) 601 { 602 appendErrorMessage(INFO_MODDN_NOOP.get()); 603 setResultCode(ResultCode.NO_OPERATION); 604 } 605 else 606 { 607 for (SynchronizationProvider provider : 608 DirectoryServer.getSynchronizationProviders()) 609 { 610 try 611 { 612 SynchronizationProviderResult result = 613 provider.doPreOperation(this); 614 if (! result.continueProcessing()) 615 { 616 setResultCode(result.getResultCode()); 617 appendErrorMessage(result.getErrorMessage()); 618 setMatchedDN(result.getMatchedDN()); 619 setReferralURLs(result.getReferralURLs()); 620 break modifyDNProcessing; 621 } 622 } 623 catch (DirectoryException de) 624 { 625 if (debugEnabled()) 626 { 627 TRACER.debugCaught(DebugLogLevel.ERROR, de); 628 } 629 630 logError(ERR_MODDN_SYNCH_PREOP_FAILED.get(getConnectionID(), 631 getOperationID(), getExceptionMessage(de))); 632 setResponseData(de); 633 break modifyDNProcessing; 634 } 635 } 636 637 currentBackend.renameEntry(entryDN, newEntry, this); 638 } 639 640 641 // Attach the pre-read and/or post-read controls to the response if 642 // appropriate. 643 processReadEntryControls(); 644 645 646 if (! noOp) 647 { 648 setResultCode(ResultCode.SUCCESS); 649 } 650 } 651 catch (DirectoryException de) 652 { 653 if (debugEnabled()) 654 { 655 TRACER.debugCaught(DebugLogLevel.ERROR, de); 656 } 657 658 setResponseData(de); 659 break modifyDNProcessing; 660 } 661 } 662 finally 663 { 664 LockManager.unlock(entryDN, currentLock); 665 LockManager.unlock(newDN, newLock); 666 } 667 } 668 669 for (SynchronizationProvider provider : 670 DirectoryServer.getSynchronizationProviders()) 671 { 672 try 673 { 674 provider.doPostOperation(this); 675 } 676 catch (DirectoryException de) 677 { 678 if (debugEnabled()) 679 { 680 TRACER.debugCaught(DebugLogLevel.ERROR, de); 681 } 682 683 logError(ERR_MODDN_SYNCH_POSTOP_FAILED.get(getConnectionID(), 684 getOperationID(), getExceptionMessage(de))); 685 setResponseData(de); 686 break; 687 } 688 } 689 690 // Invoke the post-operation or post-synchronization modify DN plugins. 691 if (isSynchronizationOperation()) 692 { 693 if (getResultCode() == ResultCode.SUCCESS) 694 { 695 pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this); 696 } 697 } 698 else if (executePostOpPlugins) 699 { 700 PluginResult.PostOperation postOpResult = 701 pluginConfigManager.invokePostOperationModifyDNPlugins(this); 702 if (!postOpResult.continueProcessing()) 703 { 704 setResultCode(postOpResult.getResultCode()); 705 appendErrorMessage(postOpResult.getErrorMessage()); 706 setMatchedDN(postOpResult.getMatchedDN()); 707 setReferralURLs(postOpResult.getReferralURLs()); 708 return; 709 } 710 } 711 712 713 // Notify any change notification listeners that might be registered with 714 // the server. 715 if (getResultCode() == ResultCode.SUCCESS) 716 { 717 for (ChangeNotificationListener changeListener : 718 DirectoryServer.getChangeNotificationListeners()) 719 { 720 try 721 { 722 changeListener.handleModifyDNOperation(this, currentEntry, newEntry); 723 } 724 catch (Exception e) 725 { 726 if (debugEnabled()) 727 { 728 TRACER.debugCaught(DebugLogLevel.ERROR, e); 729 } 730 731 Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get( 732 getExceptionMessage(e)); 733 logError(message); 734 } 735 } 736 } 737 } 738 739 740 741 /** 742 * Processes the set of controls included in the request. 743 * 744 * @throws DirectoryException If a problem occurs that should cause the 745 * modify DN operation to fail. 746 */ 747 private void handleRequestControls() 748 throws DirectoryException 749 { 750 List<Control> requestControls = getRequestControls(); 751 if ((requestControls != null) && (! requestControls.isEmpty())) 752 { 753 for (int i=0; i < requestControls.size(); i++) 754 { 755 Control c = requestControls.get(i); 756 String oid = c.getOID(); 757 758 if (! AccessControlConfigManager.getInstance(). 759 getAccessControlHandler().isAllowed(entryDN, this, c)) 760 { 761 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 762 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); 763 } 764 765 if (oid.equals(OID_LDAP_ASSERTION)) 766 { 767 LDAPAssertionRequestControl assertControl; 768 if (c instanceof LDAPAssertionRequestControl) 769 { 770 assertControl = (LDAPAssertionRequestControl) c; 771 } 772 else 773 { 774 try 775 { 776 assertControl = LDAPAssertionRequestControl.decodeControl(c); 777 requestControls.set(i, assertControl); 778 } 779 catch (LDAPException le) 780 { 781 if (debugEnabled()) 782 { 783 TRACER.debugCaught(DebugLogLevel.ERROR, le); 784 } 785 786 throw new DirectoryException( 787 ResultCode.valueOf(le.getResultCode()), 788 le.getMessageObject()); 789 } 790 } 791 792 try 793 { 794 // FIXME -- We need to determine whether the current user has 795 // permission to make this determination. 796 SearchFilter filter = assertControl.getSearchFilter(); 797 if (! filter.matchesEntry(currentEntry)) 798 { 799 throw new DirectoryException(ResultCode.ASSERTION_FAILED, 800 ERR_MODDN_ASSERTION_FAILED.get( 801 String.valueOf(entryDN))); 802 } 803 } 804 catch (DirectoryException de) 805 { 806 if (de.getResultCode() == ResultCode.ASSERTION_FAILED) 807 { 808 throw de; 809 } 810 811 if (debugEnabled()) 812 { 813 TRACER.debugCaught(DebugLogLevel.ERROR, de); 814 } 815 816 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 817 ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get( 818 String.valueOf(entryDN), 819 de.getMessageObject())); 820 } 821 } 822 else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED)) 823 { 824 noOp = true; 825 } 826 else if (oid.equals(OID_LDAP_READENTRY_PREREAD)) 827 { 828 if (c instanceof LDAPPreReadRequestControl) 829 { 830 preReadRequest = (LDAPPreReadRequestControl) c; 831 } 832 else 833 { 834 try 835 { 836 preReadRequest = LDAPPreReadRequestControl.decodeControl(c); 837 requestControls.set(i, preReadRequest); 838 } 839 catch (LDAPException le) 840 { 841 if (debugEnabled()) 842 { 843 TRACER.debugCaught(DebugLogLevel.ERROR, le); 844 } 845 846 throw new DirectoryException( 847 ResultCode.valueOf(le.getResultCode()), 848 le.getMessageObject()); 849 } 850 } 851 } 852 else if (oid.equals(OID_LDAP_READENTRY_POSTREAD)) 853 { 854 if (c instanceof LDAPPostReadRequestControl) 855 { 856 postReadRequest = (LDAPPostReadRequestControl) c; 857 } 858 else 859 { 860 try 861 { 862 postReadRequest = LDAPPostReadRequestControl.decodeControl(c); 863 requestControls.set(i, postReadRequest); 864 } 865 catch (LDAPException le) 866 { 867 if (debugEnabled()) 868 { 869 TRACER.debugCaught(DebugLogLevel.ERROR, le); 870 } 871 872 throw new DirectoryException( 873 ResultCode.valueOf(le.getResultCode()), 874 le.getMessageObject()); 875 } 876 } 877 } 878 else if (oid.equals(OID_PROXIED_AUTH_V1)) 879 { 880 // The requester must have the PROXIED_AUTH privilige in order to 881 // be able to use this control. 882 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) 883 { 884 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, 885 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); 886 } 887 888 889 ProxiedAuthV1Control proxyControl; 890 if (c instanceof ProxiedAuthV1Control) 891 { 892 proxyControl = (ProxiedAuthV1Control) c; 893 } 894 else 895 { 896 try 897 { 898 proxyControl = ProxiedAuthV1Control.decodeControl(c); 899 } 900 catch (LDAPException le) 901 { 902 if (debugEnabled()) 903 { 904 TRACER.debugCaught(DebugLogLevel.ERROR, le); 905 } 906 907 throw new DirectoryException( 908 ResultCode.valueOf(le.getResultCode()), 909 le.getMessageObject()); 910 } 911 } 912 913 914 Entry authorizationEntry = proxyControl.getAuthorizationEntry(); 915 setAuthorizationEntry(authorizationEntry); 916 if (authorizationEntry == null) 917 { 918 setProxiedAuthorizationDN(DN.nullDN()); 919 } 920 else 921 { 922 setProxiedAuthorizationDN(authorizationEntry.getDN()); 923 } 924 } 925 else if (oid.equals(OID_PROXIED_AUTH_V2)) 926 { 927 // The requester must have the PROXIED_AUTH privilige in order to 928 // be able to use this control. 929 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) 930 { 931 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, 932 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); 933 } 934 935 936 ProxiedAuthV2Control proxyControl; 937 if (c instanceof ProxiedAuthV2Control) 938 { 939 proxyControl = (ProxiedAuthV2Control) c; 940 } 941 else 942 { 943 try 944 { 945 proxyControl = ProxiedAuthV2Control.decodeControl(c); 946 } 947 catch (LDAPException le) 948 { 949 if (debugEnabled()) 950 { 951 TRACER.debugCaught(DebugLogLevel.ERROR, le); 952 } 953 954 throw new DirectoryException( 955 ResultCode.valueOf(le.getResultCode()), 956 le.getMessageObject()); 957 } 958 } 959 960 961 Entry authorizationEntry = proxyControl.getAuthorizationEntry(); 962 setAuthorizationEntry(authorizationEntry); 963 if (authorizationEntry == null) 964 { 965 setProxiedAuthorizationDN(DN.nullDN()); 966 } 967 else 968 { 969 setProxiedAuthorizationDN(authorizationEntry.getDN()); 970 } 971 } 972 973 // NYI -- Add support for additional controls. 974 975 else if (c.isCritical()) 976 { 977 if ((backend == null) || (! backend.supportsControl(oid))) 978 { 979 throw new DirectoryException( 980 ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 981 ERR_MODDN_UNSUPPORTED_CRITICAL_CONTROL.get( 982 String.valueOf(entryDN), oid)); 983 } 984 } 985 } 986 } 987 } 988 989 990 991 /** 992 * Updates the entry so that its attributes are changed to reflect the changes 993 * to the RDN. This also performs schema checking on the updated entry. 994 * 995 * @param modifications A list to hold the modifications made to the entry. 996 * 997 * @throws DirectoryException If a problem occurs that should cause the 998 * modify DN operation to fail. 999 */ 1000 private void applyRDNChanges(List<Modification> modifications) 1001 throws DirectoryException 1002 { 1003 // If we should delete the old RDN values from the entry, then do so. 1004 if (deleteOldRDN()) 1005 { 1006 RDN currentRDN = entryDN.getRDN(); 1007 int numValues = currentRDN.getNumValues(); 1008 for (int i=0; i < numValues; i++) 1009 { 1010 LinkedHashSet<AttributeValue> valueSet = 1011 new LinkedHashSet<AttributeValue>(1); 1012 valueSet.add(currentRDN.getAttributeValue(i)); 1013 1014 Attribute a = new Attribute(currentRDN.getAttributeType(i), 1015 currentRDN.getAttributeName(i), valueSet); 1016 1017 // If the associated attribute type is marked NO-USER-MODIFICATION, then 1018 // refuse the update. 1019 if (a.getAttributeType().isNoUserModification()) 1020 { 1021 if (! (isInternalOperation() || isSynchronizationOperation())) 1022 { 1023 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1024 ERR_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD.get( 1025 String.valueOf(entryDN), a.getName())); 1026 } 1027 } 1028 1029 LinkedList<AttributeValue> missingValues = 1030 new LinkedList<AttributeValue>(); 1031 newEntry.removeAttribute(a, missingValues); 1032 1033 if (missingValues.isEmpty()) 1034 { 1035 modifications.add(new Modification(ModificationType.DELETE, a)); 1036 } 1037 } 1038 } 1039 1040 1041 // Add the new RDN values to the entry. 1042 int newRDNValues = newRDN.getNumValues(); 1043 for (int i=0; i < newRDNValues; i++) 1044 { 1045 LinkedHashSet<AttributeValue> valueSet = 1046 new LinkedHashSet<AttributeValue>(1); 1047 valueSet.add(newRDN.getAttributeValue(i)); 1048 1049 Attribute a = new Attribute(newRDN.getAttributeType(i), 1050 newRDN.getAttributeName(i), valueSet); 1051 1052 LinkedList<AttributeValue> duplicateValues = 1053 new LinkedList<AttributeValue>(); 1054 newEntry.addAttribute(a, duplicateValues); 1055 1056 if (duplicateValues.isEmpty()) 1057 { 1058 // If the associated attribute type is marked NO-USER-MODIFICATION, then 1059 // refuse the update. 1060 if (a.getAttributeType().isNoUserModification()) 1061 { 1062 if (! (isInternalOperation() || isSynchronizationOperation())) 1063 { 1064 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1065 ERR_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD.get( 1066 String.valueOf(entryDN), a.getName())); 1067 } 1068 } 1069 else 1070 { 1071 modifications.add(new Modification(ModificationType.ADD, a)); 1072 } 1073 } 1074 } 1075 1076 // If the server is configured to check the schema and the operation is not 1077 // a synchronization operation, make sure that the resulting entry is valid 1078 // as per the server schema. 1079 if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation())) 1080 { 1081 MessageBuilder invalidReason = new MessageBuilder(); 1082 if (! newEntry.conformsToSchema(null, false, true, true, 1083 invalidReason)) 1084 { 1085 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, 1086 ERR_MODDN_VIOLATES_SCHEMA.get( 1087 String.valueOf(entryDN), 1088 String.valueOf(invalidReason))); 1089 } 1090 1091 for (int i=0; i < newRDNValues; i++) 1092 { 1093 AttributeType at = newRDN.getAttributeType(i); 1094 if (at.isObsolete()) 1095 { 1096 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1097 ERR_MODDN_NEWRDN_ATTR_IS_OBSOLETE.get( 1098 String.valueOf(entryDN), 1099 at.getNameOrOID())); 1100 } 1101 } 1102 } 1103 } 1104 1105 1106 1107 /** 1108 * Applies any modifications performed during pre-operation plugin processing. 1109 * This also performs schema checking for the updated entry. 1110 * 1111 * @param modifications A list containing the modifications made to the 1112 * entry. 1113 * @param startPos The position in the list at which the pre-operation 1114 * modifications start. 1115 * 1116 * @throws DirectoryException If a problem occurs that should cause the 1117 * modify DN operation to fail. 1118 */ 1119 private void applyPreOpModifications(List<Modification> modifications, 1120 int startPos) 1121 throws DirectoryException 1122 { 1123 for (int i=startPos; i < modifications.size(); i++) 1124 { 1125 Modification m = modifications.get(i); 1126 Attribute a = m.getAttribute(); 1127 1128 switch (m.getModificationType()) 1129 { 1130 case ADD: 1131 LinkedList<AttributeValue> duplicateValues = 1132 new LinkedList<AttributeValue>(); 1133 newEntry.addAttribute(a, duplicateValues); 1134 break; 1135 1136 case DELETE: 1137 LinkedList<AttributeValue> missingValues = 1138 new LinkedList<AttributeValue>(); 1139 newEntry.removeAttribute(a, missingValues); 1140 break; 1141 1142 case REPLACE: 1143 duplicateValues = new LinkedList<AttributeValue>(); 1144 newEntry.removeAttribute(a.getAttributeType(), a.getOptions()); 1145 newEntry.addAttribute(a, duplicateValues); 1146 break; 1147 1148 case INCREMENT: 1149 List<Attribute> attrList = 1150 newEntry.getAttribute(a.getAttributeType(), 1151 a.getOptions()); 1152 if ((attrList == null) || attrList.isEmpty()) 1153 { 1154 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, 1155 ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get( 1156 String.valueOf(entryDN), 1157 a.getName())); 1158 } 1159 else if (attrList.size() > 1) 1160 { 1161 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1162 ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get( 1163 String.valueOf(entryDN), a.getName())); 1164 } 1165 1166 LinkedHashSet<AttributeValue> values = 1167 attrList.get(0).getValues(); 1168 if ((values == null) || values.isEmpty()) 1169 { 1170 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, 1171 ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get( 1172 String.valueOf(entryDN), 1173 a.getName())); 1174 } 1175 else if (values.size() > 1) 1176 { 1177 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1178 ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get( 1179 String.valueOf(entryDN), a.getName())); 1180 } 1181 1182 long currentLongValue; 1183 try 1184 { 1185 AttributeValue v = values.iterator().next(); 1186 currentLongValue = Long.parseLong(v.getStringValue()); 1187 } 1188 catch (Exception e) 1189 { 1190 if (debugEnabled()) 1191 { 1192 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1193 } 1194 1195 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1196 ERR_MODDN_PREOP_INCREMENT_VALUE_NOT_INTEGER.get( 1197 String.valueOf(entryDN), a.getName())); 1198 } 1199 1200 LinkedHashSet<AttributeValue> newValues = a.getValues(); 1201 if ((newValues == null) || newValues.isEmpty()) 1202 { 1203 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1204 ERR_MODDN_PREOP_INCREMENT_NO_AMOUNT.get( 1205 String.valueOf(entryDN), a.getName())); 1206 } 1207 else if (newValues.size() > 1) 1208 { 1209 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1210 ERR_MODDN_PREOP_INCREMENT_MULTIPLE_AMOUNTS.get( 1211 String.valueOf(entryDN), a.getName())); 1212 } 1213 1214 long incrementAmount; 1215 try 1216 { 1217 AttributeValue v = values.iterator().next(); 1218 incrementAmount = Long.parseLong(v.getStringValue()); 1219 } 1220 catch (Exception e) 1221 { 1222 if (debugEnabled()) 1223 { 1224 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1225 } 1226 1227 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1228 ERR_MODDN_PREOP_INCREMENT_AMOUNT_NOT_INTEGER.get( 1229 String.valueOf(entryDN), a.getName())); 1230 } 1231 1232 long newLongValue = currentLongValue + incrementAmount; 1233 ByteString newValueOS = 1234 new ASN1OctetString(String.valueOf(newLongValue)); 1235 1236 newValues = new LinkedHashSet<AttributeValue>(1); 1237 newValues.add(new AttributeValue(a.getAttributeType(), 1238 newValueOS)); 1239 1240 List<Attribute> newAttrList = new ArrayList<Attribute>(1); 1241 newAttrList.add(new Attribute(a.getAttributeType(), 1242 a.getName(), newValues)); 1243 newEntry.putAttribute(a.getAttributeType(), newAttrList); 1244 1245 break; 1246 } 1247 } 1248 1249 1250 // Make sure that the updated entry still conforms to the server 1251 // schema. 1252 if (DirectoryServer.checkSchema()) 1253 { 1254 MessageBuilder invalidReason = new MessageBuilder(); 1255 if (! newEntry.conformsToSchema(null, false, true, true, 1256 invalidReason)) 1257 { 1258 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, 1259 ERR_MODDN_PREOP_VIOLATES_SCHEMA.get( 1260 String.valueOf(entryDN), 1261 String.valueOf(invalidReason))); 1262 } 1263 } 1264 } 1265 1266 1267 1268 /** 1269 * Performs any necessary processing to create the pre-read and/or post-read 1270 * response controls and attach them to the response. 1271 */ 1272 private void processReadEntryControls() 1273 { 1274 if (preReadRequest != null) 1275 { 1276 Entry entry = currentEntry.duplicate(true); 1277 1278 if (! preReadRequest.allowsAttribute( 1279 DirectoryServer.getObjectClassAttributeType())) 1280 { 1281 entry.removeAttribute( 1282 DirectoryServer.getObjectClassAttributeType()); 1283 } 1284 1285 if (! preReadRequest.returnAllUserAttributes()) 1286 { 1287 Iterator<AttributeType> iterator = 1288 entry.getUserAttributes().keySet().iterator(); 1289 while (iterator.hasNext()) 1290 { 1291 AttributeType attrType = iterator.next(); 1292 if (! preReadRequest.allowsAttribute(attrType)) 1293 { 1294 iterator.remove(); 1295 } 1296 } 1297 } 1298 1299 if (! preReadRequest.returnAllOperationalAttributes()) 1300 { 1301 Iterator<AttributeType> iterator = 1302 entry.getOperationalAttributes().keySet().iterator(); 1303 while (iterator.hasNext()) 1304 { 1305 AttributeType attrType = iterator.next(); 1306 if (! preReadRequest.allowsAttribute(attrType)) 1307 { 1308 iterator.remove(); 1309 } 1310 } 1311 } 1312 1313 // FIXME -- Check access controls on the entry to see if it should 1314 // be returned or if any attributes need to be stripped 1315 // out.. 1316 SearchResultEntry searchEntry = new SearchResultEntry(entry); 1317 LDAPPreReadResponseControl responseControl = 1318 new LDAPPreReadResponseControl(preReadRequest.getOID(), 1319 preReadRequest.isCritical(), 1320 searchEntry); 1321 1322 addResponseControl(responseControl); 1323 } 1324 1325 if (postReadRequest != null) 1326 { 1327 Entry entry = newEntry.duplicate(true); 1328 1329 if (! postReadRequest.allowsAttribute( 1330 DirectoryServer.getObjectClassAttributeType())) 1331 { 1332 entry.removeAttribute( 1333 DirectoryServer.getObjectClassAttributeType()); 1334 } 1335 1336 if (! postReadRequest.returnAllUserAttributes()) 1337 { 1338 Iterator<AttributeType> iterator = 1339 entry.getUserAttributes().keySet().iterator(); 1340 while (iterator.hasNext()) 1341 { 1342 AttributeType attrType = iterator.next(); 1343 if (! postReadRequest.allowsAttribute(attrType)) 1344 { 1345 iterator.remove(); 1346 } 1347 } 1348 } 1349 1350 if (! postReadRequest.returnAllOperationalAttributes()) 1351 { 1352 Iterator<AttributeType> iterator = 1353 entry.getOperationalAttributes().keySet().iterator(); 1354 while (iterator.hasNext()) 1355 { 1356 AttributeType attrType = iterator.next(); 1357 if (! postReadRequest.allowsAttribute(attrType)) 1358 { 1359 iterator.remove(); 1360 } 1361 } 1362 } 1363 1364 // FIXME -- Check access controls on the entry to see if it should 1365 // be returned or if any attributes need to be stripped 1366 // out.. 1367 SearchResultEntry searchEntry = new SearchResultEntry(entry); 1368 LDAPPostReadResponseControl responseControl = 1369 new LDAPPostReadResponseControl(postReadRequest.getOID(), 1370 postReadRequest.isCritical(), 1371 searchEntry); 1372 1373 addResponseControl(responseControl); 1374 } 1375 } 1376 } 1377