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.Arrays; 033 import java.util.HashSet; 034 import java.util.Iterator; 035 import java.util.LinkedHashSet; 036 import java.util.LinkedList; 037 import java.util.List; 038 import java.util.concurrent.locks.Lock; 039 040 import org.opends.messages.Message; 041 import org.opends.messages.MessageBuilder; 042 import org.opends.server.api.AttributeSyntax; 043 import org.opends.server.api.Backend; 044 import org.opends.server.api.ChangeNotificationListener; 045 import org.opends.server.api.ClientConnection; 046 import org.opends.server.api.PasswordStorageScheme; 047 import org.opends.server.api.SynchronizationProvider; 048 import org.opends.server.api.plugin.PluginResult; 049 import org.opends.server.controls.LDAPAssertionRequestControl; 050 import org.opends.server.controls.LDAPPostReadRequestControl; 051 import org.opends.server.controls.LDAPPostReadResponseControl; 052 import org.opends.server.controls.LDAPPreReadRequestControl; 053 import org.opends.server.controls.LDAPPreReadResponseControl; 054 import org.opends.server.controls.PasswordPolicyErrorType; 055 import org.opends.server.controls.PasswordPolicyResponseControl; 056 import org.opends.server.controls.ProxiedAuthV1Control; 057 import org.opends.server.controls.ProxiedAuthV2Control; 058 import org.opends.server.core.AccessControlConfigManager; 059 import org.opends.server.core.DirectoryServer; 060 import org.opends.server.core.ModifyOperation; 061 import org.opends.server.core.ModifyOperationWrapper; 062 import org.opends.server.core.PasswordPolicyState; 063 import org.opends.server.core.PluginConfigManager; 064 import org.opends.server.loggers.debug.DebugTracer; 065 import org.opends.server.protocols.asn1.ASN1OctetString; 066 import org.opends.server.schema.AuthPasswordSyntax; 067 import org.opends.server.schema.BooleanSyntax; 068 import org.opends.server.schema.UserPasswordSyntax; 069 import org.opends.server.types.AccountStatusNotification; 070 import org.opends.server.types.AccountStatusNotificationType; 071 import org.opends.server.types.AcceptRejectWarn; 072 import org.opends.server.types.Attribute; 073 import org.opends.server.types.AttributeType; 074 import org.opends.server.types.AttributeValue; 075 import org.opends.server.types.AuthenticationInfo; 076 import org.opends.server.types.ByteString; 077 import org.opends.server.types.CanceledOperationException; 078 import org.opends.server.types.Control; 079 import org.opends.server.types.DebugLogLevel; 080 import org.opends.server.types.DirectoryException; 081 import org.opends.server.types.DN; 082 import org.opends.server.types.Entry; 083 import org.opends.server.types.LDAPException; 084 import org.opends.server.types.LockManager; 085 import org.opends.server.types.Modification; 086 import org.opends.server.types.ModificationType; 087 import org.opends.server.types.Privilege; 088 import org.opends.server.types.RDN; 089 import org.opends.server.types.ResultCode; 090 import org.opends.server.types.SearchFilter; 091 import org.opends.server.types.SearchResultEntry; 092 import org.opends.server.types.SynchronizationProviderResult; 093 import org.opends.server.types.operation.PostOperationModifyOperation; 094 import org.opends.server.types.operation.PostResponseModifyOperation; 095 import org.opends.server.types.operation.PreOperationModifyOperation; 096 import org.opends.server.types.operation.PostSynchronizationModifyOperation; 097 import org.opends.server.util.TimeThread; 098 099 import static org.opends.messages.CoreMessages.*; 100 import static org.opends.server.config.ConfigConstants.*; 101 import static org.opends.server.loggers.ErrorLogger.*; 102 import static org.opends.server.loggers.debug.DebugLogger.*; 103 import static org.opends.server.util.ServerConstants.*; 104 import static org.opends.server.util.StaticUtils.*; 105 106 107 108 /** 109 * This class defines an operation used to modify an entry in a local backend 110 * of the Directory Server. 111 */ 112 public class LocalBackendModifyOperation 113 extends ModifyOperationWrapper 114 implements PreOperationModifyOperation, PostOperationModifyOperation, 115 PostResponseModifyOperation, 116 PostSynchronizationModifyOperation 117 { 118 /** 119 * The tracer object for the debug logger. 120 */ 121 private static final DebugTracer TRACER = getTracer(); 122 123 124 125 // The backend in which the target entry exists. 126 private Backend backend; 127 128 // Indicates whether the request included the user's current password. 129 private boolean currentPasswordProvided; 130 131 // Indicates whether the user's account has been enabled or disabled by this 132 // modify operation. 133 private boolean enabledStateChanged; 134 135 // Indicates whether the user's account is currently enabled. 136 private boolean isEnabled; 137 138 // Indicates whether the request included the LDAP no-op control. 139 private boolean noOp; 140 141 // Indicates whether this modify operation includees a password change. 142 private boolean passwordChanged; 143 144 // Indicates whether the request included the password policy request control. 145 private boolean pwPolicyControlRequested; 146 147 // Indicates whether the password change is a self-change. 148 private boolean selfChange; 149 150 // Indicates whether the user's account was locked before this change. 151 private boolean wasLocked; 152 153 // The client connection associated with this operation. 154 private ClientConnection clientConnection; 155 156 // The DN of the entry to modify. 157 private DN entryDN; 158 159 // The current entry, before any changes are applied. 160 private Entry currentEntry = null; 161 162 // The modified entry that will be stored in the backend. 163 private Entry modifiedEntry = null; 164 165 // The number of passwords contained in the modify operation. 166 private int numPasswords; 167 168 // The post-read request control, if present. 169 private LDAPPostReadRequestControl postReadRequest; 170 171 // The pre-read request control, if present. 172 private LDAPPreReadRequestControl preReadRequest; 173 174 // The set of clear-text current passwords (if any were provided). 175 private List<AttributeValue> currentPasswords = null; 176 177 // The set of clear-text new passwords (if any were provided). 178 private List<AttributeValue> newPasswords = null; 179 180 // The set of modifications contained in this request. 181 private List<Modification> modifications; 182 183 // The password policy error type for this operation. 184 private PasswordPolicyErrorType pwpErrorType; 185 186 // The password policy state for this modify operation. 187 private PasswordPolicyState pwPolicyState; 188 189 190 191 /** 192 * Creates a new operation that may be used to modify an entry in a 193 * local backend of the Directory Server. 194 * 195 * @param modify The operation to enhance. 196 */ 197 public LocalBackendModifyOperation(ModifyOperation modify) 198 { 199 super(modify); 200 LocalBackendWorkflowElement.attachLocalOperation (modify, this); 201 } 202 203 204 205 /** 206 * Retrieves the current entry before any modifications are applied. This 207 * will not be available to pre-parse plugins. 208 * 209 * @return The current entry, or <CODE>null</CODE> if it is not yet 210 * available. 211 */ 212 public final Entry getCurrentEntry() 213 { 214 return currentEntry; 215 } 216 217 218 219 /** 220 * Retrieves the set of clear-text current passwords for the user, if 221 * available. This will only be available if the modify operation contains 222 * one or more delete elements that target the password attribute and provide 223 * the values to delete in the clear. It will not be available to pre-parse 224 * plugins. 225 * 226 * @return The set of clear-text current password values as provided in the 227 * modify request, or <CODE>null</CODE> if there were none or this 228 * information is not yet available. 229 */ 230 public final List<AttributeValue> getCurrentPasswords() 231 { 232 return currentPasswords; 233 } 234 235 236 237 /** 238 * Retrieves the modified entry that is to be written to the backend. This 239 * will be available to pre-operation plugins, and if such a plugin does make 240 * a change to this entry, then it is also necessary to add that change to 241 * the set of modifications to ensure that the update will be consistent. 242 * 243 * @return The modified entry that is to be written to the backend, or 244 * <CODE>null</CODE> if it is not yet available. 245 */ 246 public final Entry getModifiedEntry() 247 { 248 return modifiedEntry; 249 } 250 251 252 253 /** 254 * Retrieves the set of clear-text new passwords for the user, if available. 255 * This will only be available if the modify operation contains one or more 256 * add or replace elements that target the password attribute and provide the 257 * values in the clear. It will not be available to pre-parse plugins. 258 * 259 * @return The set of clear-text new passwords as provided in the modify 260 * request, or <CODE>null</CODE> if there were none or this 261 * information is not yet available. 262 */ 263 public final List<AttributeValue> getNewPasswords() 264 { 265 return newPasswords; 266 } 267 268 269 270 /** 271 * Adds the provided modification to the set of modifications to this modify 272 * operation. 273 * In addition, the modification is applied to the modified entry. 274 * 275 * This may only be called by pre-operation plugins. 276 * 277 * @param modification The modification to add to the set of changes for 278 * this modify operation. 279 * 280 * @throws DirectoryException If an unexpected problem occurs while applying 281 * the modification to the entry. 282 */ 283 public void addModification(Modification modification) 284 throws DirectoryException 285 { 286 modifiedEntry.applyModification(modification); 287 super.addModification(modification); 288 } 289 290 291 292 /** 293 * Process this modify operation against a local backend. 294 * 295 * @param backend The backend in which the modify operation should be 296 * performed. 297 * 298 * @throws CanceledOperationException if this operation should be 299 * cancelled 300 */ 301 void processLocalModify(Backend backend) throws CanceledOperationException { 302 boolean executePostOpPlugins = false; 303 304 this.backend = backend; 305 306 clientConnection = getClientConnection(); 307 308 // Get the plugin config manager that will be used for invoking plugins. 309 PluginConfigManager pluginConfigManager = 310 DirectoryServer.getPluginConfigManager(); 311 312 // Check for a request to cancel this operation. 313 checkIfCanceled(false); 314 315 // Create a labeled block of code that we can break out of if a problem is 316 // detected. 317 modifyProcessing: 318 { 319 entryDN = getEntryDN(); 320 if (entryDN == null){ 321 break modifyProcessing; 322 } 323 324 // Process the modifications to convert them from their raw form to the 325 // form required for the rest of the modify processing. 326 modifications = getModifications(); 327 if (modifications == null) 328 { 329 break modifyProcessing; 330 } 331 332 if (modifications.isEmpty()) 333 { 334 setResultCode(ResultCode.CONSTRAINT_VIOLATION); 335 appendErrorMessage(ERR_MODIFY_NO_MODIFICATIONS.get( 336 String.valueOf(entryDN))); 337 break modifyProcessing; 338 } 339 340 341 // If the user must change their password before doing anything else, and 342 // if the target of the modify operation isn't the user's own entry, then 343 // reject the request. 344 if ((! isInternalOperation()) && clientConnection.mustChangePassword()) 345 { 346 DN authzDN = getAuthorizationDN(); 347 if ((authzDN != null) && (! authzDN.equals(entryDN))) 348 { 349 // The user will not be allowed to do anything else before the 350 // password gets changed. Also note that we haven't yet checked the 351 // request controls so we need to do that now to see if the password 352 // policy request control was provided. 353 for (Control c : getRequestControls()) 354 { 355 if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL)) 356 { 357 pwPolicyControlRequested = true; 358 pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; 359 break; 360 } 361 } 362 363 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 364 appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get()); 365 break modifyProcessing; 366 } 367 } 368 369 370 // Check for a request to cancel this operation. 371 checkIfCanceled(false); 372 373 // Acquire a write lock on the target entry. 374 Lock entryLock = null; 375 for (int i=0; i < 3; i++) 376 { 377 entryLock = LockManager.lockWrite(entryDN); 378 if (entryLock != null) 379 { 380 break; 381 } 382 } 383 384 if (entryLock == null) 385 { 386 setResultCode(DirectoryServer.getServerErrorResultCode()); 387 appendErrorMessage(ERR_MODIFY_CANNOT_LOCK_ENTRY.get( 388 String.valueOf(entryDN))); 389 break modifyProcessing; 390 } 391 392 393 try 394 { 395 // Check for a request to cancel this operation. 396 checkIfCanceled(false); 397 398 399 try 400 { 401 // Get the entry to modify. If it does not exist, then fail. 402 currentEntry = backend.getEntry(entryDN); 403 404 if (currentEntry == null) 405 { 406 setResultCode(ResultCode.NO_SUCH_OBJECT); 407 appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get( 408 String.valueOf(entryDN))); 409 410 // See if one of the entry's ancestors exists. 411 try 412 { 413 DN parentDN = entryDN.getParentDNInSuffix(); 414 while (parentDN != null) 415 { 416 if (DirectoryServer.entryExists(parentDN)) 417 { 418 setMatchedDN(parentDN); 419 break; 420 } 421 422 parentDN = parentDN.getParentDNInSuffix(); 423 } 424 } 425 catch (Exception e) 426 { 427 if (debugEnabled()) 428 { 429 TRACER.debugCaught(DebugLogLevel.ERROR, e); 430 } 431 } 432 433 break modifyProcessing; 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 processRequestControls(); 439 440 // Get the password policy state object for the entry that can be used 441 // to perform any appropriate password policy processing. Also, see 442 // if the entry is being updated by the end user or an administrator. 443 selfChange = entryDN.equals(getAuthorizationDN()); 444 445 // FIXME -- Need a way to enable debug mode. 446 pwPolicyState = new PasswordPolicyState(currentEntry, false, 447 TimeThread.getTime(), true); 448 } 449 catch (DirectoryException de) 450 { 451 if (debugEnabled()) 452 { 453 TRACER.debugCaught(DebugLogLevel.ERROR, de); 454 } 455 456 setResponseData(de); 457 break modifyProcessing; 458 } 459 460 461 // Create a duplicate of the entry and apply the changes to it. 462 modifiedEntry = currentEntry.duplicate(false); 463 464 if (! noOp) 465 { 466 // Invoke any conflict resolution processing that might be needed by 467 // the synchronization provider. 468 for (SynchronizationProvider provider : 469 DirectoryServer.getSynchronizationProviders()) 470 { 471 try 472 { 473 SynchronizationProviderResult result = 474 provider.handleConflictResolution(this); 475 if (! result.continueProcessing()) 476 { 477 setResultCode(result.getResultCode()); 478 appendErrorMessage(result.getErrorMessage()); 479 setMatchedDN(result.getMatchedDN()); 480 setReferralURLs(result.getReferralURLs()); 481 break modifyProcessing; 482 } 483 } 484 catch (DirectoryException de) 485 { 486 if (debugEnabled()) 487 { 488 TRACER.debugCaught(DebugLogLevel.ERROR, de); 489 } 490 491 logError(ERR_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED.get( 492 getConnectionID(), getOperationID(), 493 getExceptionMessage(de))); 494 setResponseData(de); 495 break modifyProcessing; 496 } 497 } 498 } 499 500 501 try 502 { 503 handleSchemaProcessing(); 504 } 505 catch (DirectoryException de) 506 { 507 if (debugEnabled()) 508 { 509 TRACER.debugCaught(DebugLogLevel.ERROR, de); 510 } 511 512 setResponseData(de); 513 break modifyProcessing; 514 } 515 516 517 // Check to see if the client has permission to perform the modify. 518 // The access control check is not made any earlier because the handler 519 // needs access to the modified entry. 520 521 // FIXME: for now assume that this will check all permissions 522 // pertinent to the operation. This includes proxy authorization 523 // and any other controls specified. 524 525 // FIXME: earlier checks to see if the entry already exists may have 526 // already exposed sensitive information to the client. 527 if (! AccessControlConfigManager.getInstance(). 528 getAccessControlHandler().isAllowed(this)) 529 { 530 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); 531 appendErrorMessage(ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( 532 String.valueOf(entryDN))); 533 break modifyProcessing; 534 } 535 536 537 try 538 { 539 handleInitialPasswordPolicyProcessing(); 540 541 wasLocked = false; 542 if (passwordChanged) 543 { 544 performAdditionalPasswordChangedProcessing(); 545 } 546 } 547 catch (DirectoryException de) 548 { 549 if (debugEnabled()) 550 { 551 TRACER.debugCaught(DebugLogLevel.ERROR, de); 552 } 553 554 setResponseData(de); 555 break modifyProcessing; 556 } 557 558 559 if ((! passwordChanged) && (! isInternalOperation()) && 560 pwPolicyState.mustChangePassword()) 561 { 562 // The user will not be allowed to do anything else before the 563 // password gets changed. 564 pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; 565 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 566 appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get()); 567 break modifyProcessing; 568 } 569 570 571 // If the server is configured to check the schema and the 572 // operation is not a sycnhronization operation, 573 // make sure that the new entry is valid per the server schema. 574 if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation())) 575 { 576 MessageBuilder invalidReason = new MessageBuilder(); 577 if (! modifiedEntry.conformsToSchema(null, false, false, false, 578 invalidReason)) 579 { 580 setResultCode(ResultCode.OBJECTCLASS_VIOLATION); 581 appendErrorMessage(ERR_MODIFY_VIOLATES_SCHEMA.get( 582 String.valueOf(entryDN), invalidReason)); 583 break modifyProcessing; 584 } 585 } 586 587 588 // Check for a request to cancel this operation. 589 checkIfCanceled(false); 590 591 // If the operation is not a synchronization operation, 592 // Invoke the pre-operation modify plugins. 593 if (! isSynchronizationOperation()) 594 { 595 executePostOpPlugins = true; 596 PluginResult.PreOperation preOpResult = 597 pluginConfigManager.invokePreOperationModifyPlugins(this); 598 if (!preOpResult.continueProcessing()) 599 { 600 setResultCode(preOpResult.getResultCode()); 601 appendErrorMessage(preOpResult.getErrorMessage()); 602 setMatchedDN(preOpResult.getMatchedDN()); 603 setReferralURLs(preOpResult.getReferralURLs()); 604 break modifyProcessing; 605 } 606 } 607 608 609 // Check for a request to cancel this operation. 610 checkIfCanceled(true); 611 612 // Actually perform the modify operation. This should also include 613 // taking care of any synchronization that might be needed. 614 if (backend == null) 615 { 616 setResultCode(ResultCode.NO_SUCH_OBJECT); 617 appendErrorMessage(ERR_MODIFY_NO_BACKEND_FOR_ENTRY.get( 618 String.valueOf(entryDN))); 619 break modifyProcessing; 620 } 621 622 try 623 { 624 try 625 { 626 checkWritability(); 627 } 628 catch (DirectoryException de) 629 { 630 if (debugEnabled()) 631 { 632 TRACER.debugCaught(DebugLogLevel.ERROR, de); 633 } 634 635 setResponseData(de); 636 break modifyProcessing; 637 } 638 639 640 if (noOp) 641 { 642 appendErrorMessage(INFO_MODIFY_NOOP.get()); 643 setResultCode(ResultCode.NO_OPERATION); 644 } 645 else 646 { 647 for (SynchronizationProvider provider : 648 DirectoryServer.getSynchronizationProviders()) 649 { 650 try 651 { 652 SynchronizationProviderResult result = 653 provider.doPreOperation(this); 654 if (! result.continueProcessing()) 655 { 656 setResultCode(result.getResultCode()); 657 appendErrorMessage(result.getErrorMessage()); 658 setMatchedDN(result.getMatchedDN()); 659 setReferralURLs(result.getReferralURLs()); 660 break modifyProcessing; 661 } 662 } 663 catch (DirectoryException de) 664 { 665 if (debugEnabled()) 666 { 667 TRACER.debugCaught(DebugLogLevel.ERROR, de); 668 } 669 670 logError(ERR_MODIFY_SYNCH_PREOP_FAILED.get(getConnectionID(), 671 getOperationID(), getExceptionMessage(de))); 672 setResponseData(de); 673 break modifyProcessing; 674 } 675 } 676 677 backend.replaceEntry(modifiedEntry, this); 678 679 680 681 // See if we need to generate any account status notifications as a 682 // result of the changes. 683 if (passwordChanged || enabledStateChanged || wasLocked) 684 { 685 handleAccountStatusNotifications(); 686 } 687 } 688 689 690 // Handle any processing that may be needed for the pre-read and/or 691 // post-read controls. 692 handleReadEntryProcessing(); 693 694 695 if (! noOp) 696 { 697 setResultCode(ResultCode.SUCCESS); 698 } 699 } 700 catch (DirectoryException de) 701 { 702 if (debugEnabled()) 703 { 704 TRACER.debugCaught(DebugLogLevel.ERROR, de); 705 } 706 707 setResponseData(de); 708 break modifyProcessing; 709 } 710 } 711 finally 712 { 713 LockManager.unlock(entryDN, entryLock); 714 } 715 } 716 717 for (SynchronizationProvider provider : 718 DirectoryServer.getSynchronizationProviders()) 719 { 720 try 721 { 722 provider.doPostOperation(this); 723 } 724 catch (DirectoryException de) 725 { 726 if (debugEnabled()) 727 { 728 TRACER.debugCaught(DebugLogLevel.ERROR, de); 729 } 730 731 logError(ERR_MODIFY_SYNCH_POSTOP_FAILED.get(getConnectionID(), 732 getOperationID(), getExceptionMessage(de))); 733 setResponseData(de); 734 break; 735 } 736 } 737 738 // If the password policy request control was included, then make sure we 739 // send the corresponding response control. 740 if (pwPolicyControlRequested) 741 { 742 addResponseControl(new PasswordPolicyResponseControl(null, 0, 743 pwpErrorType)); 744 } 745 746 // Invoke the post-operation or post-synchronization modify plugins. 747 if (isSynchronizationOperation()) 748 { 749 if (getResultCode() == ResultCode.SUCCESS) 750 { 751 pluginConfigManager.invokePostSynchronizationModifyPlugins(this); 752 } 753 } 754 else if (executePostOpPlugins) 755 { 756 // FIXME -- Should this also be done while holding the locks? 757 PluginResult.PostOperation postOpResult = 758 pluginConfigManager.invokePostOperationModifyPlugins(this); 759 if (!postOpResult.continueProcessing()) 760 { 761 setResultCode(postOpResult.getResultCode()); 762 appendErrorMessage(postOpResult.getErrorMessage()); 763 setMatchedDN(postOpResult.getMatchedDN()); 764 setReferralURLs(postOpResult.getReferralURLs()); 765 return; 766 } 767 } 768 769 770 // Notify any change notification listeners that might be registered with 771 // the server. 772 if (getResultCode() == ResultCode.SUCCESS) 773 { 774 notifyChangeListeners(); 775 } 776 } 777 778 779 780 /** 781 * Processes any controls contained in the modify request. 782 * 783 * @throws DirectoryException If a problem is encountered with any of the 784 * controls. 785 */ 786 private void processRequestControls() 787 throws DirectoryException 788 { 789 List<Control> requestControls = getRequestControls(); 790 if ((requestControls != null) && (! requestControls.isEmpty())) 791 { 792 for (int i=0; i < requestControls.size(); i++) 793 { 794 Control c = requestControls.get(i); 795 String oid = c.getOID(); 796 797 if (! AccessControlConfigManager.getInstance(). 798 getAccessControlHandler().isAllowed(entryDN, this, c)) 799 { 800 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 801 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); 802 } 803 804 805 if (oid.equals(OID_LDAP_ASSERTION)) 806 { 807 LDAPAssertionRequestControl assertControl; 808 if (c instanceof LDAPAssertionRequestControl) 809 { 810 assertControl = (LDAPAssertionRequestControl) c; 811 } 812 else 813 { 814 try 815 { 816 assertControl = LDAPAssertionRequestControl.decodeControl(c); 817 requestControls.set(i, assertControl); 818 } 819 catch (LDAPException le) 820 { 821 if (debugEnabled()) 822 { 823 TRACER.debugCaught(DebugLogLevel.ERROR, le); 824 } 825 826 throw new DirectoryException( 827 ResultCode.valueOf(le.getResultCode()), 828 le.getMessageObject()); 829 } 830 } 831 832 try 833 { 834 // FIXME -- We need to determine whether the current user has 835 // permission to make this determination. 836 SearchFilter filter = assertControl.getSearchFilter(); 837 if (! filter.matchesEntry(currentEntry)) 838 { 839 throw new DirectoryException(ResultCode.ASSERTION_FAILED, 840 ERR_MODIFY_ASSERTION_FAILED.get( 841 String.valueOf(entryDN))); 842 } 843 } 844 catch (DirectoryException de) 845 { 846 if (de.getResultCode() == ResultCode.ASSERTION_FAILED) 847 { 848 throw de; 849 } 850 851 if (debugEnabled()) 852 { 853 TRACER.debugCaught(DebugLogLevel.ERROR, de); 854 } 855 856 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 857 ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get( 858 String.valueOf(entryDN), 859 de.getMessageObject())); 860 } 861 } 862 else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED)) 863 { 864 noOp = true; 865 } 866 else if (oid.equals(OID_LDAP_READENTRY_PREREAD)) 867 { 868 if (c instanceof LDAPPreReadRequestControl) 869 { 870 preReadRequest = (LDAPPreReadRequestControl) c; 871 } 872 else 873 { 874 try 875 { 876 preReadRequest = LDAPPreReadRequestControl.decodeControl(c); 877 requestControls.set(i, preReadRequest); 878 } 879 catch (LDAPException le) 880 { 881 if (debugEnabled()) 882 { 883 TRACER.debugCaught(DebugLogLevel.ERROR, le); 884 } 885 886 throw new DirectoryException( 887 ResultCode.valueOf(le.getResultCode()), 888 le.getMessageObject()); 889 } 890 } 891 } 892 else if (oid.equals(OID_LDAP_READENTRY_POSTREAD)) 893 { 894 if (c instanceof LDAPPostReadRequestControl) 895 { 896 postReadRequest = (LDAPPostReadRequestControl) c; 897 } 898 else 899 { 900 try 901 { 902 postReadRequest = LDAPPostReadRequestControl.decodeControl(c); 903 requestControls.set(i, postReadRequest); 904 } 905 catch (LDAPException le) 906 { 907 if (debugEnabled()) 908 { 909 TRACER.debugCaught(DebugLogLevel.ERROR, le); 910 } 911 912 throw new DirectoryException( 913 ResultCode.valueOf(le.getResultCode()), 914 le.getMessageObject()); 915 } 916 } 917 } 918 else if (oid.equals(OID_PROXIED_AUTH_V1)) 919 { 920 // The requester must have the PROXIED_AUTH privilige in order to 921 // be able to use this control. 922 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) 923 { 924 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, 925 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); 926 } 927 928 929 ProxiedAuthV1Control proxyControl; 930 if (c instanceof ProxiedAuthV1Control) 931 { 932 proxyControl = (ProxiedAuthV1Control) c; 933 } 934 else 935 { 936 try 937 { 938 proxyControl = ProxiedAuthV1Control.decodeControl(c); 939 } 940 catch (LDAPException le) 941 { 942 if (debugEnabled()) 943 { 944 TRACER.debugCaught(DebugLogLevel.ERROR, le); 945 } 946 947 throw new DirectoryException( 948 ResultCode.valueOf(le.getResultCode()), 949 le.getMessageObject()); 950 } 951 } 952 953 954 Entry authorizationEntry = proxyControl.getAuthorizationEntry(); 955 setAuthorizationEntry(authorizationEntry); 956 if (authorizationEntry == null) 957 { 958 setProxiedAuthorizationDN(DN.nullDN()); 959 } 960 else 961 { 962 setProxiedAuthorizationDN(authorizationEntry.getDN()); 963 } 964 } 965 else if (oid.equals(OID_PROXIED_AUTH_V2)) 966 { 967 // The requester must have the PROXIED_AUTH privilige in order to 968 // be able to use this control. 969 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this)) 970 { 971 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, 972 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); 973 } 974 975 976 ProxiedAuthV2Control proxyControl; 977 if (c instanceof ProxiedAuthV2Control) 978 { 979 proxyControl = (ProxiedAuthV2Control) c; 980 } 981 else 982 { 983 try 984 { 985 proxyControl = ProxiedAuthV2Control.decodeControl(c); 986 } 987 catch (LDAPException le) 988 { 989 if (debugEnabled()) 990 { 991 TRACER.debugCaught(DebugLogLevel.ERROR, le); 992 } 993 994 throw new DirectoryException( 995 ResultCode.valueOf(le.getResultCode()), 996 le.getMessageObject()); 997 } 998 } 999 1000 1001 Entry authorizationEntry = proxyControl.getAuthorizationEntry(); 1002 setAuthorizationEntry(authorizationEntry); 1003 if (authorizationEntry == null) 1004 { 1005 setProxiedAuthorizationDN(DN.nullDN()); 1006 } 1007 else 1008 { 1009 setProxiedAuthorizationDN(authorizationEntry.getDN()); 1010 } 1011 } 1012 else if (oid.equals(OID_PASSWORD_POLICY_CONTROL)) 1013 { 1014 pwPolicyControlRequested = true; 1015 } 1016 1017 // NYI -- Add support for additional controls. 1018 else if (c.isCritical()) 1019 { 1020 if ((backend == null) || (! backend.supportsControl(oid))) 1021 { 1022 throw new DirectoryException( 1023 ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 1024 ERR_MODIFY_UNSUPPORTED_CRITICAL_CONTROL.get( 1025 String.valueOf(entryDN), oid)); 1026 } 1027 } 1028 } 1029 } 1030 } 1031 1032 /** 1033 * Handles schema processing for non-password modifications. 1034 * 1035 * @throws DirectoryException If a problem is encountered that should cause 1036 * the modify operation to fail. 1037 */ 1038 private void handleSchemaProcessing() throws DirectoryException 1039 { 1040 1041 for (Modification m : modifications) 1042 { 1043 Attribute a = m.getAttribute(); 1044 AttributeType t = a.getAttributeType(); 1045 1046 1047 // If the attribute type is marked "NO-USER-MODIFICATION" then fail unless 1048 // this is an internal operation or is related to synchronization in some 1049 // way. 1050 if (t.isNoUserModification()) 1051 { 1052 if (! (isInternalOperation() || isSynchronizationOperation() || 1053 m.isInternal())) 1054 { 1055 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1056 ERR_MODIFY_ATTR_IS_NO_USER_MOD.get( 1057 String.valueOf(entryDN), a.getName())); 1058 } 1059 } 1060 1061 // If the attribute type is marked "OBSOLETE" and the modification is 1062 // setting new values, then fail unless this is an internal operation or 1063 // is related to synchronization in some way. 1064 if (t.isObsolete()) 1065 { 1066 if (a.hasValue() && 1067 (m.getModificationType() != ModificationType.DELETE)) 1068 { 1069 if (! (isInternalOperation() || isSynchronizationOperation() || 1070 m.isInternal())) 1071 { 1072 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1073 ERR_MODIFY_ATTR_IS_OBSOLETE.get( 1074 String.valueOf(entryDN), a.getName())); 1075 } 1076 } 1077 } 1078 1079 1080 // See if the attribute is one which controls the privileges available for 1081 // a user. If it is, then the client must have the PRIVILEGE_CHANGE 1082 // privilege. 1083 if (t.hasName(OP_ATTR_PRIVILEGE_NAME)) 1084 { 1085 if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)) 1086 { 1087 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1088 ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get()); 1089 } 1090 } 1091 1092 // If the modification is not updating the password attribute, 1093 // then check if the isEnabled flag should be set and then perform any 1094 // schema processing. 1095 boolean isPassword = 1096 t.equals(pwPolicyState.getPolicy().getPasswordAttribute()); 1097 if (!isPassword ) 1098 { 1099 // See if it's an attribute used to maintain the account 1100 // enabled/disabled state. 1101 AttributeType disabledAttr = 1102 DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true); 1103 if (t.equals(disabledAttr)) 1104 { 1105 enabledStateChanged = true; 1106 for (AttributeValue v : a.getValues()) 1107 { 1108 try 1109 { 1110 isEnabled = 1111 (! BooleanSyntax.decodeBooleanValue(v.getNormalizedValue())); 1112 } 1113 catch (DirectoryException de) 1114 { 1115 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1116 ERR_MODIFY_INVALID_DISABLED_VALUE.get( 1117 OP_ATTR_ACCOUNT_DISABLED, 1118 String.valueOf(de.getMessageObject())), de); 1119 } 1120 } 1121 } 1122 1123 switch (m.getModificationType()) 1124 { 1125 case ADD: 1126 processInitialAddSchema(a); 1127 break; 1128 1129 case DELETE: 1130 processInitialDeleteSchema(a); 1131 break; 1132 1133 case REPLACE: 1134 processInitialReplaceSchema(a); 1135 break; 1136 1137 case INCREMENT: 1138 processInitialIncrementSchema(a); 1139 break; 1140 } 1141 } 1142 } 1143 } 1144 1145 /** 1146 * Handles the initial set of password policy for this modify operation. 1147 * 1148 * @throws DirectoryException If a problem is encountered that should cause 1149 * the modify operation to fail. 1150 */ 1151 private void handleInitialPasswordPolicyProcessing() 1152 throws DirectoryException 1153 { 1154 // Declare variables used for password policy state processing. 1155 currentPasswordProvided = false; 1156 isEnabled = true; 1157 enabledStateChanged = false; 1158 if (currentEntry.hasAttribute( 1159 pwPolicyState.getPolicy().getPasswordAttribute())) 1160 { 1161 // It may actually have more than one, but we can't tell the difference if 1162 // the values are encoded, and its enough for our purposes just to know 1163 // that there is at least one. 1164 numPasswords = 1; 1165 } 1166 else 1167 { 1168 numPasswords = 0; 1169 } 1170 1171 1172 // If it's not an internal or synchronization operation, then iterate 1173 // through the set of modifications to see if a password is included in the 1174 // changes. If so, then add the appropriate state changes to the set of 1175 // modifications. 1176 if (! (isInternalOperation() || isSynchronizationOperation())) 1177 { 1178 for (Modification m : modifications) 1179 { 1180 AttributeType t = m.getAttribute().getAttributeType(); 1181 boolean isPassword = 1182 t.equals(pwPolicyState.getPolicy().getPasswordAttribute()); 1183 if (isPassword) 1184 { 1185 passwordChanged = true; 1186 if (! selfChange) 1187 { 1188 if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET, this)) 1189 { 1190 pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; 1191 throw new DirectoryException( 1192 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1193 ERR_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES.get()); 1194 } 1195 } 1196 1197 break; 1198 } 1199 } 1200 } 1201 1202 1203 for (Modification m : modifications) 1204 { 1205 Attribute a = m.getAttribute(); 1206 AttributeType t = a.getAttributeType(); 1207 1208 1209 // If the modification is updating the password attribute, then perform 1210 // any necessary password policy processing. This processing should be 1211 // skipped for synchronization operations. 1212 boolean isPassword = 1213 t.equals(pwPolicyState.getPolicy().getPasswordAttribute()); 1214 if (isPassword) 1215 { 1216 if (!isSynchronizationOperation()) 1217 { 1218 // If the attribute contains any options, then reject it. Passwords 1219 // will not be allowed to have options. 1220 // Skipped for internal operations. 1221 if (!isInternalOperation()) 1222 { 1223 if (a.hasOptions()) 1224 { 1225 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1226 ERR_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS.get()); 1227 } 1228 1229 1230 // If it's a self change, then see if that's allowed. 1231 if (selfChange && 1232 (! pwPolicyState.getPolicy().allowUserPasswordChanges())) 1233 { 1234 pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; 1235 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1236 ERR_MODIFY_NO_USER_PW_CHANGES.get()); 1237 } 1238 1239 1240 // If we require secure password changes, then makes sure it's a 1241 // secure communication channel. 1242 if (pwPolicyState.getPolicy().requireSecurePasswordChanges() && 1243 (! clientConnection.isSecure())) 1244 { 1245 pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; 1246 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1247 ERR_MODIFY_REQUIRE_SECURE_CHANGES.get()); 1248 } 1249 1250 1251 // If it's a self change and it's not been long enough since the 1252 // previous change, then reject it. 1253 if (selfChange && pwPolicyState.isWithinMinimumAge()) 1254 { 1255 pwpErrorType = PasswordPolicyErrorType.PASSWORD_TOO_YOUNG; 1256 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1257 ERR_MODIFY_WITHIN_MINIMUM_AGE.get()); 1258 } 1259 } 1260 1261 // Check to see whether this will adding, deleting, or replacing 1262 // password values (increment doesn't make any sense for passwords). 1263 // Then perform the appropriate type of processing for that kind of 1264 // modification. 1265 boolean isAdd = (m.getModificationType() == ModificationType.ADD); 1266 LinkedHashSet<AttributeValue> pwValues = a.getValues(); 1267 LinkedHashSet<AttributeValue> encodedValues = 1268 new LinkedHashSet<AttributeValue>(); 1269 switch (m.getModificationType()) 1270 { 1271 case ADD: 1272 case REPLACE: 1273 processInitialAddOrReplacePW(isAdd, pwValues, encodedValues, a); 1274 break; 1275 1276 case DELETE: 1277 processInitialDeletePW(pwValues, encodedValues, a); 1278 break; 1279 1280 default: 1281 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1282 ERR_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD.get( 1283 String.valueOf(m.getModificationType()), 1284 a.getName())); 1285 } 1286 } 1287 1288 switch (m.getModificationType()) 1289 { 1290 case ADD: 1291 processInitialAddSchema(a); 1292 break; 1293 1294 case DELETE: 1295 processInitialDeleteSchema(a); 1296 break; 1297 1298 case REPLACE: 1299 processInitialReplaceSchema(a); 1300 break; 1301 1302 case INCREMENT: 1303 processInitialIncrementSchema(a); 1304 break; 1305 } 1306 } 1307 } 1308 } 1309 1310 1311 1312 /** 1313 * Performs the initial password policy add or replace processing. 1314 * 1315 * @param isAdd Indicates whether it is an add or replace update. 1316 * @param pwValues The set of password values as included in the 1317 * request. 1318 * @param encodedValues The set of encoded password values. 1319 * @param pwAttr The attribute involved in the password change. 1320 * 1321 * @throws DirectoryException If a problem occurs that should cause the 1322 * modify operation to fail. 1323 */ 1324 private void processInitialAddOrReplacePW(boolean isAdd, 1325 LinkedHashSet<AttributeValue> pwValues, 1326 LinkedHashSet<AttributeValue> encodedValues, 1327 Attribute pwAttr) 1328 throws DirectoryException 1329 { 1330 int passwordsToAdd = pwValues.size(); 1331 1332 if (isAdd) 1333 { 1334 numPasswords += passwordsToAdd; 1335 } 1336 else 1337 { 1338 numPasswords = passwordsToAdd; 1339 } 1340 1341 1342 // If there were multiple password values, then make sure that's OK. 1343 if ((! isInternalOperation()) && 1344 (! pwPolicyState.getPolicy().allowMultiplePasswordValues()) && 1345 (passwordsToAdd > 1)) 1346 { 1347 pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; 1348 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1349 ERR_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED.get()); 1350 } 1351 1352 1353 // Iterate through the password values and see if any of them are 1354 // pre-encoded. If so, then check to see if we'll allow it. Otherwise, 1355 // store the clear-text values for later validation and update the attribute 1356 // with the encoded values. 1357 for (AttributeValue v : pwValues) 1358 { 1359 if (pwPolicyState.passwordIsPreEncoded(v.getValue())) 1360 { 1361 if ((! isInternalOperation()) && 1362 ! pwPolicyState.getPolicy().allowPreEncodedPasswords()) 1363 { 1364 pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY; 1365 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1366 ERR_MODIFY_NO_PREENCODED_PASSWORDS.get()); 1367 } 1368 else 1369 { 1370 encodedValues.add(v); 1371 } 1372 } 1373 else 1374 { 1375 if (isAdd) 1376 { 1377 // Make sure that the password value doesn't already exist. 1378 if (pwPolicyState.passwordMatches(v.getValue())) 1379 { 1380 pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY; 1381 throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS, 1382 ERR_MODIFY_PASSWORD_EXISTS.get()); 1383 } 1384 } 1385 1386 if (newPasswords == null) 1387 { 1388 newPasswords = new LinkedList<AttributeValue>(); 1389 } 1390 1391 newPasswords.add(v); 1392 1393 for (ByteString s : pwPolicyState.encodePassword(v.getValue())) 1394 { 1395 encodedValues.add(new AttributeValue(pwAttr.getAttributeType(), s)); 1396 } 1397 } 1398 } 1399 1400 pwAttr.setValues(encodedValues); 1401 } 1402 1403 1404 1405 /** 1406 * Performs the initial password policy delete processing. 1407 * 1408 * @param pwValues The set of password values as included in the 1409 * request. 1410 * @param encodedValues The set of encoded password values. 1411 * @param pwAttr The attribute involved in the password change. 1412 * 1413 * @throws DirectoryException If a problem occurs that should cause the 1414 * modify operation to fail. 1415 */ 1416 private void processInitialDeletePW(LinkedHashSet<AttributeValue> pwValues, 1417 LinkedHashSet<AttributeValue> encodedValues, 1418 Attribute pwAttr) 1419 throws DirectoryException 1420 { 1421 // Iterate through the password values and see if any of them are 1422 // pre-encoded. We will never allow pre-encoded passwords for user password 1423 // changes, but we will allow them for administrators. For each clear-text 1424 // value, verify that at least one value in the entry matches and replace 1425 // the clear-text value with the appropriate encoded forms. 1426 for (AttributeValue v : pwValues) 1427 { 1428 if (pwPolicyState.passwordIsPreEncoded(v.getValue())) 1429 { 1430 if ((! isInternalOperation()) && selfChange) 1431 { 1432 pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY; 1433 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1434 ERR_MODIFY_NO_PREENCODED_PASSWORDS.get()); 1435 } 1436 else 1437 { 1438 encodedValues.add(v); 1439 } 1440 } 1441 else 1442 { 1443 List<Attribute> attrList = 1444 currentEntry.getAttribute(pwAttr.getAttributeType()); 1445 if ((attrList == null) || (attrList.isEmpty())) 1446 { 1447 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1448 ERR_MODIFY_NO_EXISTING_VALUES.get()); 1449 } 1450 boolean found = false; 1451 for (Attribute attr : attrList) 1452 { 1453 for (AttributeValue av : attr.getValues()) 1454 { 1455 if (pwPolicyState.getPolicy().usesAuthPasswordSyntax()) 1456 { 1457 if (AuthPasswordSyntax.isEncoded(av.getValue())) 1458 { 1459 StringBuilder[] compoenents = 1460 AuthPasswordSyntax.decodeAuthPassword(av.getStringValue()); 1461 PasswordStorageScheme scheme = 1462 DirectoryServer.getAuthPasswordStorageScheme( 1463 compoenents[0].toString()); 1464 if (scheme != null) 1465 { 1466 if (scheme.authPasswordMatches(v.getValue(), 1467 compoenents[1].toString(), 1468 compoenents[2].toString())) 1469 { 1470 encodedValues.add(av); 1471 found = true; 1472 } 1473 } 1474 } 1475 else 1476 { 1477 if (av.equals(v)) 1478 { 1479 encodedValues.add(v); 1480 found = true; 1481 } 1482 } 1483 } 1484 else 1485 { 1486 if (UserPasswordSyntax.isEncoded(av.getValue())) 1487 { 1488 String[] compoenents = UserPasswordSyntax.decodeUserPassword( 1489 av.getStringValue()); 1490 PasswordStorageScheme scheme = 1491 DirectoryServer.getPasswordStorageScheme( 1492 toLowerCase(compoenents[0])); 1493 if (scheme != null) 1494 { 1495 if (scheme.passwordMatches(v.getValue(), 1496 new ASN1OctetString(compoenents[1]))) 1497 { 1498 encodedValues.add(av); 1499 found = true; 1500 } 1501 } 1502 } 1503 else 1504 { 1505 if (av.equals(v)) 1506 { 1507 encodedValues.add(v); 1508 found = true; 1509 } 1510 } 1511 } 1512 } 1513 } 1514 1515 if (found) 1516 { 1517 if (currentPasswords == null) 1518 { 1519 currentPasswords = new LinkedList<AttributeValue>(); 1520 } 1521 currentPasswords.add(v); 1522 numPasswords--; 1523 } 1524 else 1525 { 1526 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1527 ERR_MODIFY_INVALID_PASSWORD.get()); 1528 } 1529 1530 currentPasswordProvided = true; 1531 } 1532 } 1533 1534 pwAttr.setValues(encodedValues); 1535 } 1536 1537 1538 1539 /** 1540 * Performs the initial schema processing for an add modification and updates 1541 * the entry appropriately. 1542 * 1543 * @param attr The attribute being added. 1544 * 1545 * @throws DirectoryException If a problem occurs that should cause the 1546 * modify operation to fail. 1547 */ 1548 private void processInitialAddSchema(Attribute attr) 1549 throws DirectoryException 1550 { 1551 // Make sure that one or more values have been provided for the attribute. 1552 LinkedHashSet<AttributeValue> newValues = attr.getValues(); 1553 if ((newValues == null) || newValues.isEmpty()) 1554 { 1555 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1556 ERR_MODIFY_ADD_NO_VALUES.get(String.valueOf(entryDN), 1557 attr.getName())); 1558 } 1559 1560 // If the server is configured to check schema and the operation is not a 1561 // synchronization operation, make sure that all the new values are valid 1562 // according to the associated syntax. 1563 if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation())) 1564 { 1565 AcceptRejectWarn syntaxPolicy = 1566 DirectoryServer.getSyntaxEnforcementPolicy(); 1567 AttributeSyntax syntax = attr.getAttributeType().getSyntax(); 1568 1569 if (syntaxPolicy == AcceptRejectWarn.REJECT) 1570 { 1571 MessageBuilder invalidReason = new MessageBuilder(); 1572 for (AttributeValue v : newValues) 1573 { 1574 if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) 1575 { 1576 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1577 ERR_MODIFY_ADD_INVALID_SYNTAX.get( 1578 String.valueOf(entryDN), attr.getName(), 1579 v.getStringValue(), invalidReason)); 1580 } 1581 } 1582 } 1583 else if (syntaxPolicy == AcceptRejectWarn.WARN) 1584 { 1585 MessageBuilder invalidReason = new MessageBuilder(); 1586 for (AttributeValue v : newValues) 1587 { 1588 if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) 1589 { 1590 setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 1591 logError(ERR_MODIFY_ADD_INVALID_SYNTAX.get(String.valueOf(entryDN), 1592 attr.getName(), v.getStringValue(), invalidReason)); 1593 invalidReason = new MessageBuilder(); 1594 } 1595 } 1596 } 1597 } 1598 1599 1600 // Add the provided attribute or merge an existing attribute with 1601 // the values of the new attribute. If there are any duplicates, 1602 // then fail. 1603 if (attr.getAttributeType().isObjectClassType()) 1604 { 1605 modifiedEntry.addObjectClasses(newValues); 1606 } 1607 else 1608 { 1609 LinkedList<AttributeValue> duplicateValues = 1610 new LinkedList<AttributeValue>(); 1611 modifiedEntry.addAttribute(attr, duplicateValues); 1612 if (! duplicateValues.isEmpty()) 1613 { 1614 StringBuilder buffer = new StringBuilder(); 1615 Iterator<AttributeValue> iterator = duplicateValues.iterator(); 1616 buffer.append(iterator.next().getStringValue()); 1617 while (iterator.hasNext()) 1618 { 1619 buffer.append(", "); 1620 buffer.append(iterator.next().getStringValue()); 1621 } 1622 1623 throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS, 1624 ERR_MODIFY_ADD_DUPLICATE_VALUE.get( 1625 String.valueOf(entryDN), attr.getName(), buffer)); 1626 } 1627 } 1628 } 1629 1630 1631 1632 /** 1633 * Performs the initial schema processing for a delete modification and 1634 * updates the entry appropriately. 1635 * 1636 * @param attr The attribute being deleted. 1637 * 1638 * @throws DirectoryException If a problem occurs that should cause the 1639 * modify operation to fail. 1640 */ 1641 private void processInitialDeleteSchema(Attribute attr) 1642 throws DirectoryException 1643 { 1644 // Remove the specified attribute values or the entire attribute from the 1645 // value. If there are any specified values that were not present, then 1646 // fail. If the RDN attribute value would be removed, then fail. 1647 LinkedList<AttributeValue> missingValues = new LinkedList<AttributeValue>(); 1648 boolean attrExists = modifiedEntry.removeAttribute(attr, missingValues); 1649 1650 if (attrExists) 1651 { 1652 if (missingValues.isEmpty()) 1653 { 1654 AttributeType t = attr.getAttributeType(); 1655 1656 RDN rdn = modifiedEntry.getDN().getRDN(); 1657 if ((rdn != null) && rdn.hasAttributeType(t) && 1658 (! modifiedEntry.hasValue(t, attr.getOptions(), 1659 rdn.getAttributeValue(t)))) 1660 { 1661 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN, 1662 ERR_MODIFY_DELETE_RDN_ATTR.get( 1663 String.valueOf(entryDN), 1664 attr.getName())); 1665 } 1666 } 1667 else 1668 { 1669 StringBuilder buffer = new StringBuilder(); 1670 Iterator<AttributeValue> iterator = missingValues.iterator(); 1671 buffer.append(iterator.next().getStringValue()); 1672 while (iterator.hasNext()) 1673 { 1674 buffer.append(", "); 1675 buffer.append(iterator.next().getStringValue()); 1676 } 1677 1678 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, 1679 ERR_MODIFY_DELETE_MISSING_VALUES.get( 1680 String.valueOf(entryDN), attr.getName(), buffer)); 1681 } 1682 } 1683 else 1684 { 1685 throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, 1686 ERR_MODIFY_DELETE_NO_SUCH_ATTR.get( 1687 String.valueOf(entryDN), attr.getName())); 1688 } 1689 } 1690 1691 1692 1693 /** 1694 * Performs the initial schema processing for a replace modification and 1695 * updates the entry appropriately. 1696 * 1697 * @param attr The attribute being replaced. 1698 * 1699 * @throws DirectoryException If a problem occurs that should cause the 1700 * modify operation to fail. 1701 */ 1702 private void processInitialReplaceSchema(Attribute attr) 1703 throws DirectoryException 1704 { 1705 // If it is the objectclass attribute, then treat that separately. 1706 if (attr.getAttributeType().isObjectClassType()) 1707 { 1708 modifiedEntry.setObjectClasses(attr.getValues()); 1709 return; 1710 } 1711 1712 1713 // If the provided attribute does not have any values, then we will simply 1714 // remove the attribute from the entry (if it exists). 1715 AttributeType t = attr.getAttributeType(); 1716 if (! attr.hasValue()) 1717 { 1718 modifiedEntry.removeAttribute(t, attr.getOptions()); 1719 RDN rdn = modifiedEntry.getDN().getRDN(); 1720 if ((rdn != null) && rdn.hasAttributeType(t) && 1721 (! modifiedEntry.hasValue(t, attr.getOptions(), 1722 rdn.getAttributeValue(t)))) 1723 { 1724 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN, 1725 ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN), 1726 attr.getName())); 1727 } 1728 1729 return; 1730 } 1731 1732 // If the server is configured to check schema and the operation is not a 1733 // synchronization operation, make sure that all the new values are valid 1734 // according to the associated syntax. 1735 LinkedHashSet<AttributeValue> newValues = attr.getValues(); 1736 if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation())) 1737 { 1738 AcceptRejectWarn syntaxPolicy = 1739 DirectoryServer.getSyntaxEnforcementPolicy(); 1740 AttributeSyntax syntax = t.getSyntax(); 1741 1742 if (syntaxPolicy == AcceptRejectWarn.REJECT) 1743 { 1744 MessageBuilder invalidReason = new MessageBuilder(); 1745 for (AttributeValue v : newValues) 1746 { 1747 if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) 1748 { 1749 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1750 ERR_MODIFY_REPLACE_INVALID_SYNTAX.get( 1751 String.valueOf(entryDN), attr.getName(), 1752 v.getStringValue(), invalidReason)); 1753 } 1754 } 1755 } 1756 else if (syntaxPolicy == AcceptRejectWarn.WARN) 1757 { 1758 MessageBuilder invalidReason = new MessageBuilder(); 1759 for (AttributeValue v : newValues) 1760 { 1761 if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) 1762 { 1763 setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 1764 logError(ERR_MODIFY_REPLACE_INVALID_SYNTAX.get( 1765 String.valueOf(entryDN), attr.getName(), 1766 v.getStringValue(), invalidReason)); 1767 invalidReason = new MessageBuilder(); 1768 } 1769 } 1770 } 1771 } 1772 1773 1774 // If the provided attribute does not have any options, then we will simply 1775 // use it in place of any existing attribute of the provided type (or add it 1776 // if it doesn't exist). 1777 if (! attr.hasOptions()) 1778 { 1779 List<Attribute> attrList = new ArrayList<Attribute>(1); 1780 attrList.add(attr); 1781 modifiedEntry.putAttribute(t, attrList); 1782 1783 RDN rdn = modifiedEntry.getDN().getRDN(); 1784 if ((rdn != null) && rdn.hasAttributeType(t) && 1785 (! modifiedEntry.hasValue(t, attr.getOptions(), 1786 rdn.getAttributeValue(t)))) 1787 { 1788 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN, 1789 ERR_MODIFY_DELETE_RDN_ATTR.get( 1790 String.valueOf(entryDN), 1791 attr.getName())); 1792 } 1793 1794 return; 1795 } 1796 1797 1798 // See if there is an existing attribute of the provided type. If not, then 1799 // we'll use the new one. 1800 List<Attribute> attrList = modifiedEntry.getAttribute(t); 1801 if ((attrList == null) || attrList.isEmpty()) 1802 { 1803 attrList = new ArrayList<Attribute>(1); 1804 attrList.add(attr); 1805 modifiedEntry.putAttribute(t, attrList); 1806 1807 RDN rdn = modifiedEntry.getDN().getRDN(); 1808 if ((rdn != null) && rdn.hasAttributeType(t) && 1809 (! modifiedEntry.hasValue(t, attr.getOptions(), 1810 rdn.getAttributeValue(t)))) 1811 { 1812 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN, 1813 ERR_MODIFY_DELETE_RDN_ATTR.get( 1814 String.valueOf(entryDN), 1815 attr.getName())); 1816 } 1817 1818 return; 1819 } 1820 1821 1822 // There must be an existing occurrence of the provided attribute in the 1823 // entry. If there is a version with exactly the set of options provided, 1824 // then replace it. Otherwise, add a new one. 1825 boolean found = false; 1826 for (int i=0; i < attrList.size(); i++) 1827 { 1828 if (attrList.get(i).optionsEqual(attr.getOptions())) 1829 { 1830 attrList.set(i, attr); 1831 found = true; 1832 break; 1833 } 1834 } 1835 1836 if (! found) 1837 { 1838 attrList.add(attr); 1839 } 1840 1841 RDN rdn = modifiedEntry.getDN().getRDN(); 1842 if ((rdn != null) && rdn.hasAttributeType(t) && 1843 (! modifiedEntry.hasValue(t, attr.getOptions(), 1844 rdn.getAttributeValue(t)))) 1845 { 1846 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN, 1847 ERR_MODIFY_DELETE_RDN_ATTR.get( 1848 String.valueOf(entryDN), 1849 attr.getName())); 1850 } 1851 } 1852 1853 1854 1855 /** 1856 * Performs the initial schema processing for an increment modification and 1857 * updates the entry appropriately. 1858 * 1859 * @param attr The attribute being incremented. 1860 * 1861 * @throws DirectoryException If a problem occurs that should cause the 1862 * modify operation to fail. 1863 */ 1864 private void processInitialIncrementSchema(Attribute attr) 1865 throws DirectoryException 1866 { 1867 // The specified attribute type must not be an RDN attribute. 1868 AttributeType t = attr.getAttributeType(); 1869 RDN rdn = modifiedEntry.getDN().getRDN(); 1870 if ((rdn != null) && rdn.hasAttributeType(t)) 1871 { 1872 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN, 1873 ERR_MODIFY_INCREMENT_RDN.get( 1874 String.valueOf(entryDN), 1875 attr.getName())); 1876 } 1877 1878 1879 // The provided attribute must have a single value, and it must be an 1880 // integer. 1881 LinkedHashSet<AttributeValue> values = attr.getValues(); 1882 if ((values == null) || values.isEmpty()) 1883 { 1884 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1885 ERR_MODIFY_INCREMENT_REQUIRES_VALUE.get( 1886 String.valueOf(entryDN), 1887 attr.getName())); 1888 } 1889 else if (values.size() > 1) 1890 { 1891 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1892 ERR_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE.get( 1893 String.valueOf(entryDN), attr.getName())); 1894 } 1895 1896 AttributeValue v = values.iterator().next(); 1897 1898 long incrementValue; 1899 try 1900 { 1901 incrementValue = Long.parseLong(v.getNormalizedStringValue()); 1902 } 1903 catch (Exception e) 1904 { 1905 if (debugEnabled()) 1906 { 1907 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1908 } 1909 1910 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1911 ERR_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER.get( 1912 String.valueOf(entryDN), attr.getName(), 1913 v.getStringValue()), e); 1914 } 1915 1916 1917 // Get the corresponding attribute from the entry and make sure that it has 1918 // a single integer value. 1919 List<Attribute> attrList = modifiedEntry.getAttribute(t, attr.getOptions()); 1920 if ((attrList == null) || attrList.isEmpty()) 1921 { 1922 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1923 ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get( 1924 String.valueOf(entryDN), attr.getName())); 1925 } 1926 1927 boolean updated = false; 1928 for (Attribute a : attrList) 1929 { 1930 LinkedHashSet<AttributeValue> valueList = a.getValues(); 1931 if ((valueList == null) || valueList.isEmpty()) 1932 { 1933 continue; 1934 } 1935 1936 LinkedHashSet<AttributeValue> newValueList = 1937 new LinkedHashSet<AttributeValue>(valueList.size()); 1938 for (AttributeValue existingValue : valueList) 1939 { 1940 long newIntValue; 1941 try 1942 { 1943 long existingIntValue = 1944 Long.parseLong(existingValue.getStringValue()); 1945 newIntValue = existingIntValue + incrementValue; 1946 } 1947 catch (Exception e) 1948 { 1949 if (debugEnabled()) 1950 { 1951 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1952 } 1953 1954 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1955 ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get( 1956 String.valueOf(entryDN), a.getName(), 1957 existingValue.getStringValue()), e); 1958 } 1959 1960 ByteString newValue = new ASN1OctetString(String.valueOf(newIntValue)); 1961 newValueList.add(new AttributeValue(t, newValue)); 1962 } 1963 1964 a.setValues(newValueList); 1965 updated = true; 1966 } 1967 1968 if (! updated) 1969 { 1970 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1971 ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get( 1972 String.valueOf(entryDN), attr.getName())); 1973 } 1974 } 1975 1976 1977 1978 /** 1979 * Performs additional preliminary processing that is required for a password 1980 * change. 1981 * 1982 * @throws DirectoryException If a problem occurs that should cause the 1983 * modify operation to fail. 1984 */ 1985 public void performAdditionalPasswordChangedProcessing() 1986 throws DirectoryException 1987 { 1988 // If it was a self change, then see if the current password was provided 1989 // and handle accordingly. 1990 if (selfChange && 1991 pwPolicyState.getPolicy().requireCurrentPassword() && 1992 (! currentPasswordProvided)) 1993 { 1994 pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD; 1995 1996 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1997 ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get()); 1998 } 1999 2000 2001 // If this change would result in multiple password values, then see if 2002 // that's OK. 2003 if ((numPasswords > 1) && 2004 (! pwPolicyState.getPolicy().allowMultiplePasswordValues())) 2005 { 2006 pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; 2007 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2008 ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get()); 2009 } 2010 2011 2012 // If any of the password values should be validated, then do so now. 2013 if (selfChange || 2014 (! pwPolicyState.getPolicy().skipValidationForAdministrators())) 2015 { 2016 if (newPasswords != null) 2017 { 2018 HashSet<ByteString> clearPasswords = new HashSet<ByteString>(); 2019 clearPasswords.addAll(pwPolicyState.getClearPasswords()); 2020 2021 if (currentPasswords != null) 2022 { 2023 if (clearPasswords.isEmpty()) 2024 { 2025 for (AttributeValue v : currentPasswords) 2026 { 2027 clearPasswords.add(v.getValue()); 2028 } 2029 } 2030 else 2031 { 2032 // NOTE: We can't rely on the fact that Set doesn't allow 2033 // duplicates because technically it's possible that the values 2034 // aren't duplicates if they are ASN.1 elements with different types 2035 // (like 0x04 for a standard universal octet string type versus 0x80 2036 // for a simple password in a bind operation). So we have to 2037 // manually check for duplicates. 2038 for (AttributeValue v : currentPasswords) 2039 { 2040 ByteString pw = v.getValue(); 2041 2042 boolean found = false; 2043 for (ByteString s : clearPasswords) 2044 { 2045 if (Arrays.equals(s.value(), pw.value())) 2046 { 2047 found = true; 2048 break; 2049 } 2050 } 2051 2052 if (! found) 2053 { 2054 clearPasswords.add(pw); 2055 } 2056 } 2057 } 2058 } 2059 2060 for (AttributeValue v : newPasswords) 2061 { 2062 MessageBuilder invalidReason = new MessageBuilder(); 2063 if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry, 2064 v.getValue(), clearPasswords, invalidReason)) 2065 { 2066 pwpErrorType = 2067 PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY; 2068 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2069 ERR_MODIFY_PW_VALIDATION_FAILED.get( 2070 invalidReason)); 2071 } 2072 } 2073 } 2074 } 2075 2076 2077 // If we should check the password history, then do so now. 2078 if (pwPolicyState.maintainHistory()) 2079 { 2080 if (newPasswords != null) 2081 { 2082 for (AttributeValue v : newPasswords) 2083 { 2084 if (pwPolicyState.isPasswordInHistory(v.getValue())) 2085 { 2086 if (selfChange || (! pwPolicyState.getPolicy(). 2087 skipValidationForAdministrators())) 2088 { 2089 pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY; 2090 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2091 ERR_MODIFY_PW_IN_HISTORY.get()); 2092 } 2093 } 2094 } 2095 2096 pwPolicyState.updatePasswordHistory(); 2097 } 2098 } 2099 2100 2101 // See if the account was locked for any reason. 2102 wasLocked = pwPolicyState.lockedDueToIdleInterval() || 2103 pwPolicyState.lockedDueToMaximumResetAge() || 2104 pwPolicyState.lockedDueToFailures(); 2105 2106 // Update the password policy state attributes in the user's entry. If the 2107 // modification fails, then these changes won't be applied. 2108 pwPolicyState.setPasswordChangedTime(); 2109 pwPolicyState.clearFailureLockout(); 2110 pwPolicyState.clearGraceLoginTimes(); 2111 pwPolicyState.clearWarnedTime(); 2112 2113 if (pwPolicyState.getPolicy().forceChangeOnAdd() || 2114 pwPolicyState.getPolicy().forceChangeOnReset()) 2115 { 2116 if (selfChange) 2117 { 2118 pwPolicyState.setMustChangePassword(false); 2119 } 2120 else 2121 { 2122 if ((pwpErrorType == null) && 2123 pwPolicyState.getPolicy().forceChangeOnReset()) 2124 { 2125 pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; 2126 } 2127 2128 pwPolicyState.setMustChangePassword( 2129 pwPolicyState.getPolicy().forceChangeOnReset()); 2130 } 2131 } 2132 2133 if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0) 2134 { 2135 pwPolicyState.setRequiredChangeTime(); 2136 } 2137 2138 modifications.addAll(pwPolicyState.getModifications()); 2139 modifiedEntry.applyModifications(pwPolicyState.getModifications()); 2140 } 2141 2142 2143 2144 /** 2145 * Checks to ensure that both the Directory Server and the backend are 2146 * writable. 2147 * 2148 * @throws DirectoryException If the modify operation should not be allowed 2149 * as a result of the writability check. 2150 */ 2151 private void checkWritability() 2152 throws DirectoryException 2153 { 2154 // If it is not a private backend, then check to see if the server or 2155 // backend is operating in read-only mode. 2156 if (! backend.isPrivateBackend()) 2157 { 2158 switch (DirectoryServer.getWritabilityMode()) 2159 { 2160 case DISABLED: 2161 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2162 ERR_MODIFY_SERVER_READONLY.get( 2163 String.valueOf(entryDN))); 2164 2165 case INTERNAL_ONLY: 2166 if (! (isInternalOperation() || isSynchronizationOperation())) 2167 { 2168 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2169 ERR_MODIFY_SERVER_READONLY.get( 2170 String.valueOf(entryDN))); 2171 } 2172 } 2173 2174 switch (backend.getWritabilityMode()) 2175 { 2176 case DISABLED: 2177 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2178 ERR_MODIFY_BACKEND_READONLY.get( 2179 String.valueOf(entryDN))); 2180 2181 case INTERNAL_ONLY: 2182 if (! isInternalOperation() || isSynchronizationOperation()) 2183 { 2184 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 2185 ERR_MODIFY_BACKEND_READONLY.get( 2186 String.valueOf(entryDN))); 2187 } 2188 } 2189 } 2190 } 2191 2192 2193 2194 /** 2195 * Handles any account status notifications that may be needed as a result of 2196 * modify processing. 2197 */ 2198 private void handleAccountStatusNotifications() 2199 { 2200 if (passwordChanged) 2201 { 2202 if (selfChange) 2203 { 2204 AuthenticationInfo authInfo = clientConnection.getAuthenticationInfo(); 2205 if (authInfo.getAuthenticationDN().equals(modifiedEntry.getDN())) 2206 { 2207 clientConnection.setMustChangePassword(false); 2208 } 2209 2210 Message message = INFO_MODIFY_PASSWORD_CHANGED.get(); 2211 pwPolicyState.generateAccountStatusNotification( 2212 AccountStatusNotificationType.PASSWORD_CHANGED, 2213 modifiedEntry, message, 2214 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 2215 currentPasswords, newPasswords)); 2216 } 2217 else 2218 { 2219 Message message = INFO_MODIFY_PASSWORD_RESET.get(); 2220 pwPolicyState.generateAccountStatusNotification( 2221 AccountStatusNotificationType.PASSWORD_RESET, modifiedEntry, 2222 message, 2223 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 2224 currentPasswords, newPasswords)); 2225 } 2226 } 2227 2228 if (enabledStateChanged) 2229 { 2230 if (isEnabled) 2231 { 2232 Message message = INFO_MODIFY_ACCOUNT_ENABLED.get(); 2233 pwPolicyState.generateAccountStatusNotification( 2234 AccountStatusNotificationType.ACCOUNT_ENABLED, 2235 modifiedEntry, message, 2236 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 2237 null, null)); 2238 } 2239 else 2240 { 2241 Message message = INFO_MODIFY_ACCOUNT_DISABLED.get(); 2242 pwPolicyState.generateAccountStatusNotification( 2243 AccountStatusNotificationType.ACCOUNT_DISABLED, 2244 modifiedEntry, message, 2245 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 2246 null, null)); 2247 } 2248 } 2249 2250 if (wasLocked) 2251 { 2252 Message message = INFO_MODIFY_ACCOUNT_UNLOCKED.get(); 2253 pwPolicyState.generateAccountStatusNotification( 2254 AccountStatusNotificationType.ACCOUNT_UNLOCKED, modifiedEntry, 2255 message, 2256 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 2257 null, null)); 2258 } 2259 } 2260 2261 2262 2263 /** 2264 * Handles any processing that is required for the LDAP pre-read and/or 2265 * post-read controls. 2266 */ 2267 private void handleReadEntryProcessing() 2268 { 2269 if (preReadRequest != null) 2270 { 2271 Entry entry = currentEntry.duplicate(true); 2272 2273 if (! preReadRequest.allowsAttribute( 2274 DirectoryServer.getObjectClassAttributeType())) 2275 { 2276 entry.removeAttribute( 2277 DirectoryServer.getObjectClassAttributeType()); 2278 } 2279 2280 if (! preReadRequest.returnAllUserAttributes()) 2281 { 2282 Iterator<AttributeType> iterator = 2283 entry.getUserAttributes().keySet().iterator(); 2284 while (iterator.hasNext()) 2285 { 2286 AttributeType attrType = iterator.next(); 2287 if (! preReadRequest.allowsAttribute(attrType)) 2288 { 2289 iterator.remove(); 2290 } 2291 } 2292 } 2293 2294 if (! preReadRequest.returnAllOperationalAttributes()) 2295 { 2296 Iterator<AttributeType> iterator = 2297 entry.getOperationalAttributes().keySet().iterator(); 2298 while (iterator.hasNext()) 2299 { 2300 AttributeType attrType = iterator.next(); 2301 if (! preReadRequest.allowsAttribute(attrType)) 2302 { 2303 iterator.remove(); 2304 } 2305 } 2306 } 2307 2308 // FIXME -- Check access controls on the entry to see if it should be 2309 // returned or if any attributes need to be stripped out.. 2310 SearchResultEntry searchEntry = new SearchResultEntry(entry); 2311 LDAPPreReadResponseControl responseControl = 2312 new LDAPPreReadResponseControl(preReadRequest.getOID(), 2313 preReadRequest.isCritical(), 2314 searchEntry); 2315 getResponseControls().add(responseControl); 2316 } 2317 2318 if (postReadRequest != null) 2319 { 2320 Entry entry = modifiedEntry.duplicate(true); 2321 2322 if (! postReadRequest.allowsAttribute( 2323 DirectoryServer.getObjectClassAttributeType())) 2324 { 2325 entry.removeAttribute( 2326 DirectoryServer.getObjectClassAttributeType()); 2327 } 2328 2329 if (! postReadRequest.returnAllUserAttributes()) 2330 { 2331 Iterator<AttributeType> iterator = 2332 entry.getUserAttributes().keySet().iterator(); 2333 while (iterator.hasNext()) 2334 { 2335 AttributeType attrType = iterator.next(); 2336 if (! postReadRequest.allowsAttribute(attrType)) 2337 { 2338 iterator.remove(); 2339 } 2340 } 2341 } 2342 2343 if (! postReadRequest.returnAllOperationalAttributes()) 2344 { 2345 Iterator<AttributeType> iterator = 2346 entry.getOperationalAttributes().keySet().iterator(); 2347 while (iterator.hasNext()) 2348 { 2349 AttributeType attrType = iterator.next(); 2350 if (! postReadRequest.allowsAttribute(attrType)) 2351 { 2352 iterator.remove(); 2353 } 2354 } 2355 } 2356 2357 // FIXME -- Check access controls on the entry to see if it should be 2358 // returned or if any attributes need to be stripped out.. 2359 SearchResultEntry searchEntry = new SearchResultEntry(entry); 2360 LDAPPostReadResponseControl responseControl = 2361 new LDAPPostReadResponseControl(postReadRequest.getOID(), 2362 postReadRequest.isCritical(), 2363 searchEntry); 2364 2365 getResponseControls().add(responseControl); 2366 } 2367 } 2368 2369 2370 2371 /** 2372 * Notify any registered change listeners about this update. 2373 */ 2374 private void notifyChangeListeners() 2375 { 2376 for (ChangeNotificationListener changeListener : 2377 DirectoryServer.getChangeNotificationListeners()) 2378 { 2379 try 2380 { 2381 changeListener.handleModifyOperation(this, currentEntry, modifiedEntry); 2382 } 2383 catch (Exception e) 2384 { 2385 if (debugEnabled()) 2386 { 2387 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2388 } 2389 2390 Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER.get( 2391 getExceptionMessage(e)); 2392 logError(message); 2393 } 2394 } 2395 } 2396 } 2397