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