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.HashSet;
033 import java.util.Iterator;
034 import java.util.LinkedHashSet;
035 import java.util.List;
036 import java.util.Map;
037 import java.util.concurrent.CopyOnWriteArrayList;
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.PasswordValidator;
048 import org.opends.server.api.SynchronizationProvider;
049 import org.opends.server.api.plugin.PluginResult;
050 import org.opends.server.controls.LDAPAssertionRequestControl;
051 import org.opends.server.controls.LDAPPostReadRequestControl;
052 import org.opends.server.controls.LDAPPostReadResponseControl;
053 import org.opends.server.controls.PasswordPolicyErrorType;
054 import org.opends.server.controls.PasswordPolicyResponseControl;
055 import org.opends.server.controls.ProxiedAuthV1Control;
056 import org.opends.server.controls.ProxiedAuthV2Control;
057 import org.opends.server.core.AccessControlConfigManager;
058 import org.opends.server.core.AddOperation;
059 import org.opends.server.core.AddOperationWrapper;
060 import org.opends.server.core.DirectoryServer;
061 import org.opends.server.core.PasswordPolicy;
062 import org.opends.server.core.PluginConfigManager;
063 import org.opends.server.loggers.debug.DebugTracer;
064 import org.opends.server.protocols.asn1.ASN1OctetString;
065 import org.opends.server.schema.AuthPasswordSyntax;
066 import org.opends.server.schema.BooleanSyntax;
067 import org.opends.server.schema.UserPasswordSyntax;
068 import org.opends.server.types.Attribute;
069 import org.opends.server.types.AttributeType;
070 import org.opends.server.types.AttributeValue;
071 import org.opends.server.types.ByteString;
072 import org.opends.server.types.CanceledOperationException;
073 import org.opends.server.types.Control;
074 import org.opends.server.types.DebugLogLevel;
075 import org.opends.server.types.DirectoryException;
076 import org.opends.server.types.DN;
077 import org.opends.server.types.Entry;
078 import org.opends.server.types.LDAPException;
079 import org.opends.server.types.LockManager;
080 import org.opends.server.types.ObjectClass;
081 import org.opends.server.types.Privilege;
082 import org.opends.server.types.RDN;
083 import org.opends.server.types.ResultCode;
084 import org.opends.server.types.SearchFilter;
085 import org.opends.server.types.SearchResultEntry;
086 import org.opends.server.types.SynchronizationProviderResult;
087 import org.opends.server.types.operation.PostOperationAddOperation;
088 import org.opends.server.types.operation.PostResponseAddOperation;
089 import org.opends.server.types.operation.PreOperationAddOperation;
090 import org.opends.server.types.operation.PostSynchronizationAddOperation;
091 import org.opends.server.util.TimeThread;
092
093 import static org.opends.messages.CoreMessages.*;
094 import static org.opends.server.loggers.ErrorLogger.*;
095 import static org.opends.server.loggers.debug.DebugLogger.*;
096 import static org.opends.server.config.ConfigConstants.*;
097 import static org.opends.server.util.ServerConstants.*;
098 import static org.opends.server.util.StaticUtils.*;
099
100
101
102 /**
103 * This class defines an operation used to add an entry in a local backend
104 * of the Directory Server.
105 */
106 public class LocalBackendAddOperation
107 extends AddOperationWrapper
108 implements PreOperationAddOperation, PostOperationAddOperation,
109 PostResponseAddOperation, PostSynchronizationAddOperation
110 {
111 /**
112 * The tracer object for the debug logger.
113 */
114 private static final DebugTracer TRACER = getTracer();
115
116
117
118 // The backend in which the entry is to be added.
119 private Backend backend;
120
121 // Indicates whether the request includes the LDAP no-op control.
122 private boolean noOp;
123
124 // The DN of the entry to be added.
125 private DN entryDN;
126
127 // The entry being added to the server.
128 private Entry entry;
129
130 // The post-read request control included in the request, if applicable.
131 LDAPPostReadRequestControl postReadRequest;
132
133 // The set of object classes for the entry to add.
134 private Map<ObjectClass, String> objectClasses;
135
136 // The set of operational attributes for the entry to add.
137 private Map<AttributeType,List<Attribute>> operationalAttributes;
138
139 // The set of user attributes for the entry to add.
140 private Map<AttributeType,List<Attribute>> userAttributes;
141
142
143
144 /**
145 * Creates a new operation that may be used to add a new entry in a
146 * local backend of the Directory Server.
147 *
148 * @param add The operation to enhance.
149 */
150 public LocalBackendAddOperation(AddOperation add)
151 {
152 super(add);
153
154 LocalBackendWorkflowElement.attachLocalOperation (add, this);
155 }
156
157
158
159 /**
160 * Retrieves the entry to be added to the server. Note that this will not be
161 * available to pre-parse plugins or during the conflict resolution portion of
162 * the synchronization processing.
163 *
164 * @return The entry to be added to the server, or <CODE>null</CODE> if it is
165 * not yet available.
166 */
167 public final Entry getEntryToAdd()
168 {
169 return entry;
170 }
171
172
173
174 /**
175 * Process this add operation against a local backend.
176 *
177 * @param backend The backend in which the add operation should be
178 * processed.
179 *
180 * @throws CanceledOperationException if this operation should be
181 * cancelled
182 */
183 void processLocalAdd(Backend backend) throws CanceledOperationException {
184 boolean executePostOpPlugins = false;
185
186 this.backend = backend;
187 ClientConnection clientConnection = getClientConnection();
188
189 // Get the plugin config manager that will be used for invoking plugins.
190 PluginConfigManager pluginConfigManager =
191 DirectoryServer.getPluginConfigManager();
192
193 // Check for a request to cancel this operation.
194 checkIfCanceled(false);
195
196 // Create a labeled block of code that we can break out of if a problem is
197 // detected.
198 addProcessing:
199 {
200 // Process the entry DN and set of attributes to convert them from their
201 // raw forms as provided by the client to the forms required for the rest
202 // of the add processing.
203 entryDN = getEntryDN();
204 if (entryDN == null)
205 {
206 break addProcessing;
207 }
208
209 objectClasses = getObjectClasses();
210 userAttributes = getUserAttributes();
211 operationalAttributes = getOperationalAttributes();
212
213 if ((objectClasses == null ) || (userAttributes == null) ||
214 (operationalAttributes == null))
215 {
216 break addProcessing;
217 }
218
219 // Check for a request to cancel this operation.
220 checkIfCanceled(false);
221
222
223 // Grab a read lock on the parent entry, if there is one. We need to do
224 // this to ensure that the parent is not deleted or renamed while this add
225 // is in progress, and we could also need it to check the entry against
226 // a DIT structure rule.
227 Lock parentLock = null;
228 Lock entryLock = null;
229
230 DN parentDN = entryDN.getParentDNInSuffix();
231 try
232 {
233 parentLock = lockParent(parentDN);
234 }
235 catch (DirectoryException de)
236 {
237 if (debugEnabled())
238 {
239 TRACER.debugCaught(DebugLogLevel.ERROR, de);
240 }
241
242 setResponseData(de);
243 break addProcessing;
244 }
245
246 try
247 {
248 // Check for a request to cancel this operation.
249 checkIfCanceled(false);
250
251
252 // Grab a write lock on the target entry. We'll need to do this
253 // eventually anyway, and we want to make sure that the two locks are
254 // always released when exiting this method, no matter what. Since
255 // the entry shouldn't exist yet, locking earlier than necessary
256 // shouldn't cause a problem.
257 for (int i=0; i < 3; i++)
258 {
259 entryLock = LockManager.lockWrite(entryDN);
260 if (entryLock != null)
261 {
262 break;
263 }
264 }
265
266 if (entryLock == null)
267 {
268 setResultCode(DirectoryServer.getServerErrorResultCode());
269 appendErrorMessage(ERR_ADD_CANNOT_LOCK_ENTRY.get(
270 String.valueOf(entryDN)));
271
272 break addProcessing;
273 }
274
275
276 // Invoke any conflict resolution processing that might be needed by the
277 // synchronization provider.
278 for (SynchronizationProvider provider :
279 DirectoryServer.getSynchronizationProviders())
280 {
281 try
282 {
283 SynchronizationProviderResult result =
284 provider.handleConflictResolution(this);
285 if (! result.continueProcessing())
286 {
287 setResultCode(result.getResultCode());
288 appendErrorMessage(result.getErrorMessage());
289 setMatchedDN(result.getMatchedDN());
290 setReferralURLs(result.getReferralURLs());
291 break addProcessing;
292 }
293 }
294 catch (DirectoryException de)
295 {
296 if (debugEnabled())
297 {
298 TRACER.debugCaught(DebugLogLevel.ERROR, de);
299 }
300
301 logError(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
302 getConnectionID(), getOperationID(),
303 getExceptionMessage(de)));
304
305 setResponseData(de);
306 break addProcessing;
307 }
308 }
309
310 for (AttributeType at : userAttributes.keySet())
311 {
312 // If the attribute type is marked "NO-USER-MODIFICATION" then fail
313 // unless this is an internal operation or is related to
314 // synchronization in some way.
315 // This must be done before running the password policy code
316 // and any other code that may add attributes marked as
317 // "NO-USER-MODIFICATION"
318 //
319 // Note that doing this checks at this time
320 // of the processing does not make it possible for pre-parse plugins
321 // to add NO-USER-MODIFICATION attributes to the entry.
322 if (at.isNoUserModification())
323 {
324 if (! (isInternalOperation() || isSynchronizationOperation()))
325 {
326 setResultCode(ResultCode.UNWILLING_TO_PERFORM);
327 appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
328 String.valueOf(entryDN),
329 at.getNameOrOID()));
330
331 break addProcessing;
332 }
333 }
334 }
335
336 for (AttributeType at : operationalAttributes.keySet())
337 {
338 if (at.isNoUserModification())
339 {
340 if (! (isInternalOperation() || isSynchronizationOperation()))
341 {
342 setResultCode(ResultCode.UNWILLING_TO_PERFORM);
343 appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
344 String.valueOf(entryDN),
345 at.getNameOrOID()));
346
347 break addProcessing;
348 }
349 }
350 }
351
352 // Check to see if the entry already exists. We do this before
353 // checking whether the parent exists to ensure a referral entry
354 // above the parent results in a correct referral.
355 try
356 {
357 if (DirectoryServer.entryExists(entryDN))
358 {
359 setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
360 appendErrorMessage(ERR_ADD_ENTRY_ALREADY_EXISTS.get(
361 String.valueOf(entryDN)));
362 break addProcessing;
363 }
364 }
365 catch (DirectoryException de)
366 {
367 if (debugEnabled())
368 {
369 TRACER.debugCaught(DebugLogLevel.ERROR, de);
370 }
371
372 setResponseData(de);
373 break addProcessing;
374 }
375
376
377 // Get the parent entry, if it exists.
378 Entry parentEntry = null;
379 if (parentDN != null)
380 {
381 try
382 {
383 parentEntry = DirectoryServer.getEntry(parentDN);
384
385 if (parentEntry == null)
386 {
387 DN matchedDN = parentDN.getParentDNInSuffix();
388 while (matchedDN != null)
389 {
390 try
391 {
392 if (DirectoryServer.entryExists(matchedDN))
393 {
394 setMatchedDN(matchedDN);
395 break;
396 }
397 }
398 catch (Exception e)
399 {
400 if (debugEnabled())
401 {
402 TRACER.debugCaught(DebugLogLevel.ERROR, e);
403 }
404 break;
405 }
406
407 matchedDN = matchedDN.getParentDNInSuffix();
408 }
409
410
411 // The parent doesn't exist, so this add can't be successful.
412 setResultCode(ResultCode.NO_SUCH_OBJECT);
413 appendErrorMessage(ERR_ADD_NO_PARENT.get(String.valueOf(entryDN),
414 String.valueOf(parentDN)));
415 break addProcessing;
416 }
417 }
418 catch (DirectoryException de)
419 {
420 if (debugEnabled())
421 {
422 TRACER.debugCaught(DebugLogLevel.ERROR, de);
423 }
424
425 setResponseData(de);
426 break addProcessing;
427 }
428 }
429
430
431 // Check to make sure that all of the RDN attributes are included as
432 // attribute values. If not, then either add them or report an error.
433 try
434 {
435 addRDNAttributesIfNecessary();
436 }
437 catch (DirectoryException de)
438 {
439 if (debugEnabled())
440 {
441 TRACER.debugCaught(DebugLogLevel.ERROR, de);
442 }
443
444 setResponseData(de);
445 break addProcessing;
446 }
447
448
449 // Check to make sure that all objectclasses have their superior classes
450 // listed in the entry. If not, then add them.
451 HashSet<ObjectClass> additionalClasses = null;
452 for (ObjectClass oc : objectClasses.keySet())
453 {
454 ObjectClass superiorClass = oc.getSuperiorClass();
455 if ((superiorClass != null) &&
456 (! objectClasses.containsKey(superiorClass)))
457 {
458 if (additionalClasses == null)
459 {
460 additionalClasses = new HashSet<ObjectClass>();
461 }
462
463 additionalClasses.add(superiorClass);
464 }
465 }
466
467 if (additionalClasses != null)
468 {
469 for (ObjectClass oc : additionalClasses)
470 {
471 addObjectClassChain(oc);
472 }
473 }
474
475
476 // Create an entry object to encapsulate the set of attributes and
477 // objectclasses.
478 entry = new Entry(entryDN, objectClasses, userAttributes,
479 operationalAttributes);
480
481 // Check to see if the entry includes a privilege specification. If so,
482 // then the requester must have the PRIVILEGE_CHANGE privilege.
483 AttributeType privType =
484 DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
485 if (entry.hasAttribute(privType) &&
486 (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)))
487 {
488
489 appendErrorMessage(
490 ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
491 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
492 break addProcessing;
493 }
494
495
496 // If it's not a synchronization operation, then check
497 // to see if the entry contains one or more passwords and if they
498 // are valid in accordance with the password policies associated with
499 // the user. Also perform any encoding that might be required by
500 // password storage schemes.
501 if (! isSynchronizationOperation())
502 {
503 try
504 {
505 handlePasswordPolicy();
506 }
507 catch (DirectoryException de)
508 {
509 if (debugEnabled())
510 {
511 TRACER.debugCaught(DebugLogLevel.ERROR, de);
512 }
513
514 setResponseData(de);
515 break addProcessing;
516 }
517 }
518
519
520 // If the server is configured to check schema and the
521 // operation is not a synchronization operation,
522 // check to see if the entry is valid according to the server schema,
523 // and also whether its attributes are valid according to their syntax.
524 if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
525 {
526 try
527 {
528 checkSchema(parentEntry);
529 }
530 catch (DirectoryException de)
531 {
532 if (debugEnabled())
533 {
534 TRACER.debugCaught(DebugLogLevel.ERROR, de);
535 }
536
537 setResponseData(de);
538 break addProcessing;
539 }
540 }
541
542
543 // Get the backend in which the add is to be performed.
544 if (backend == null)
545 {
546 setResultCode(ResultCode.NO_SUCH_OBJECT);
547 appendErrorMessage(Message.raw("No backend for entry " +
548 entryDN.toString())); // TODO: i18n
549 break addProcessing;
550 }
551
552
553 // Check to see if there are any controls in the request. If so, then
554 // see if there is any special processing required.
555 try
556 {
557 processControls(parentDN);
558 }
559 catch (DirectoryException de)
560 {
561 if (debugEnabled())
562 {
563 TRACER.debugCaught(DebugLogLevel.ERROR, de);
564 }
565
566 setResponseData(de);
567 break addProcessing;
568 }
569
570
571 // Check to see if the client has permission to perform the add.
572
573 // FIXME: for now assume that this will check all permission
574 // pertinent to the operation. This includes proxy authorization
575 // and any other controls specified.
576
577 // FIXME: earlier checks to see if the entry already exists or
578 // if the parent entry does not exist may have already exposed
579 // sensitive information to the client.
580 if (AccessControlConfigManager.getInstance().getAccessControlHandler().
581 isAllowed(this) == false)
582 {
583 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
584 appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
585 String.valueOf(entryDN)));
586 break addProcessing;
587 }
588
589 // Check for a request to cancel this operation.
590 checkIfCanceled(false);
591
592 // If the operation is not a synchronization operation,
593 // Invoke the pre-operation add plugins.
594 if (! isSynchronizationOperation())
595 {
596 executePostOpPlugins = true;
597 PluginResult.PreOperation preOpResult =
598 pluginConfigManager.invokePreOperationAddPlugins(this);
599 if (!preOpResult.continueProcessing())
600 {
601 setResultCode(preOpResult.getResultCode());
602 appendErrorMessage(preOpResult.getErrorMessage());
603 setMatchedDN(preOpResult.getMatchedDN());
604 setReferralURLs(preOpResult.getReferralURLs());
605 break addProcessing;
606 }
607 }
608
609
610 // Check for a request to cancel this operation.
611 checkIfCanceled(true);
612
613
614 // If it is not a private backend, then check to see if the server or
615 // backend is operating in read-only mode.
616 if (! backend.isPrivateBackend())
617 {
618 switch (DirectoryServer.getWritabilityMode())
619 {
620 case DISABLED:
621 setResultCode(ResultCode.UNWILLING_TO_PERFORM);
622 appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
623 String.valueOf(entryDN)));
624 break addProcessing;
625
626 case INTERNAL_ONLY:
627 if (! (isInternalOperation() || isSynchronizationOperation()))
628 {
629 setResultCode(ResultCode.UNWILLING_TO_PERFORM);
630 appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
631 String.valueOf(entryDN)));
632 break addProcessing;
633 }
634 break;
635 }
636
637 switch (backend.getWritabilityMode())
638 {
639 case DISABLED:
640 setResultCode(ResultCode.UNWILLING_TO_PERFORM);
641 appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
642 String.valueOf(entryDN)));
643 break addProcessing;
644
645 case INTERNAL_ONLY:
646 if (! (isInternalOperation() || isSynchronizationOperation()))
647 {
648 setResultCode(ResultCode.UNWILLING_TO_PERFORM);
649 appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
650 String.valueOf(entryDN)));
651 break addProcessing;
652 }
653 break;
654 }
655 }
656
657
658 try
659 {
660 if (noOp)
661 {
662 appendErrorMessage(INFO_ADD_NOOP.get());
663 setResultCode(ResultCode.NO_OPERATION);
664 }
665 else
666 {
667 for (SynchronizationProvider provider :
668 DirectoryServer.getSynchronizationProviders())
669 {
670 try
671 {
672 SynchronizationProviderResult result =
673 provider.doPreOperation(this);
674 if (! result.continueProcessing())
675 {
676 setResultCode(result.getResultCode());
677 appendErrorMessage(result.getErrorMessage());
678 setMatchedDN(result.getMatchedDN());
679 setReferralURLs(result.getReferralURLs());
680 break addProcessing;
681 }
682 }
683 catch (DirectoryException de)
684 {
685 if (debugEnabled())
686 {
687 TRACER.debugCaught(DebugLogLevel.ERROR, de);
688 }
689
690 logError(ERR_ADD_SYNCH_PREOP_FAILED.get(getConnectionID(),
691 getOperationID(), getExceptionMessage(de)));
692 setResponseData(de);
693 break addProcessing;
694 }
695 }
696
697 backend.addEntry(entry, this);
698 }
699
700 if (postReadRequest != null)
701 {
702 addPostReadResponse();
703 }
704
705
706 if (! noOp)
707 {
708 setResultCode(ResultCode.SUCCESS);
709 }
710 }
711 catch (DirectoryException de)
712 {
713 if (debugEnabled())
714 {
715 TRACER.debugCaught(DebugLogLevel.ERROR, de);
716 }
717
718 setResponseData(de);
719 break addProcessing;
720 }
721 }
722 finally
723 {
724 if (entryLock != null)
725 {
726 LockManager.unlock(entryDN, entryLock);
727 }
728
729 if (parentLock != null)
730 {
731 LockManager.unlock(parentDN, parentLock);
732 }
733 }
734 }
735
736 for (SynchronizationProvider provider :
737 DirectoryServer.getSynchronizationProviders())
738 {
739 try
740 {
741 provider.doPostOperation(this);
742 }
743 catch (DirectoryException de)
744 {
745 if (debugEnabled())
746 {
747 TRACER.debugCaught(DebugLogLevel.ERROR, de);
748 }
749
750 logError(ERR_ADD_SYNCH_POSTOP_FAILED.get(getConnectionID(),
751 getOperationID(), getExceptionMessage(de)));
752 setResponseData(de);
753 break;
754 }
755 }
756
757 // Invoke the post-operation or post-synchronization add plugins.
758 if (isSynchronizationOperation())
759 {
760 if (getResultCode() == ResultCode.SUCCESS)
761 {
762 pluginConfigManager.invokePostSynchronizationAddPlugins(this);
763 }
764 }
765 else if (executePostOpPlugins)
766 {
767 // FIXME -- Should this also be done while holding the locks?
768 PluginResult.PostOperation postOpResult =
769 pluginConfigManager.invokePostOperationAddPlugins(this);
770 if(!postOpResult.continueProcessing())
771 {
772 setResultCode(postOpResult.getResultCode());
773 appendErrorMessage(postOpResult.getErrorMessage());
774 setMatchedDN(postOpResult.getMatchedDN());
775 setReferralURLs(postOpResult.getReferralURLs());
776 return;
777 }
778 }
779
780
781 // Notify any change notification listeners that might be registered with
782 // the server.
783 if ((getResultCode() == ResultCode.SUCCESS) && (entry != null))
784 {
785 for (ChangeNotificationListener changeListener :
786 DirectoryServer.getChangeNotificationListeners())
787 {
788 try
789 {
790 changeListener.handleAddOperation(this, entry);
791 }
792 catch (Exception e)
793 {
794 if (debugEnabled())
795 {
796 TRACER.debugCaught(DebugLogLevel.ERROR, e);
797 }
798
799 logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get(
800 getExceptionMessage(e)));
801 }
802 }
803 }
804 }
805
806
807
808 /**
809 * Acquire a read lock on the parent of the entry to add.
810 *
811 * @return The acquired read lock.
812 *
813 * @throws DirectoryException If a problem occurs while attempting to
814 * acquire the lock.
815 */
816 private Lock lockParent(DN parentDN)
817 throws DirectoryException
818 {
819 Lock parentLock = null;
820
821 if (parentDN == null)
822 {
823 // Either this entry is a suffix or doesn't belong in the directory.
824 if (DirectoryServer.isNamingContext(entryDN))
825 {
826 // This is fine. This entry is one of the configured suffixes.
827 parentLock = null;
828 }
829 else if (entryDN.isNullDN())
830 {
831 // This is not fine. The root DSE cannot be added.
832 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
833 ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
834 }
835 else
836 {
837 // The entry doesn't have a parent but isn't a suffix. This is not
838 // allowed.
839 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
840 ERR_ADD_ENTRY_NOT_SUFFIX.get(
841 String.valueOf(entryDN)));
842 }
843 }
844 else
845 {
846 for (int i=0; i < 3; i++)
847 {
848 parentLock = LockManager.lockRead(parentDN);
849 if (parentLock != null)
850 {
851 break;
852 }
853 }
854
855 if (parentLock == null)
856 {
857 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
858 ERR_ADD_CANNOT_LOCK_PARENT.get(
859 String.valueOf(entryDN),
860 String.valueOf(parentDN)));
861 }
862 }
863
864 return parentLock;
865 }
866
867
868
869 /**
870 * Adds any missing RDN attributes to the entry.
871 *
872 * @throws DirectoryException If the entry is missing one or more RDN
873 * attributes and the server is configured to
874 * reject such entries.
875 */
876 private void addRDNAttributesIfNecessary()
877 throws DirectoryException
878 {
879 RDN rdn = entryDN.getRDN();
880 int numAVAs = rdn.getNumValues();
881 for (int i=0; i < numAVAs; i++)
882 {
883 AttributeType t = rdn.getAttributeType(i);
884 AttributeValue v = rdn.getAttributeValue(i);
885 String n = rdn.getAttributeName(i);
886 if (t.isOperational())
887 {
888 List<Attribute> attrList = operationalAttributes.get(t);
889 if (attrList == null)
890 {
891 if (isSynchronizationOperation() ||
892 DirectoryServer.addMissingRDNAttributes())
893 {
894 LinkedHashSet<AttributeValue> valueList =
895 new LinkedHashSet<AttributeValue>(1);
896 valueList.add(v);
897
898 attrList = new ArrayList<Attribute>();
899 attrList.add(new Attribute(t, n, valueList));
900
901 operationalAttributes.put(t, attrList);
902 }
903 else
904 {
905 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
906 ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
907 String.valueOf(entryDN), n));
908 }
909 }
910 else
911 {
912 boolean found = false;
913 for (Attribute a : attrList)
914 {
915 if (a.hasOptions())
916 {
917 continue;
918 }
919 else
920 {
921 if (! a.hasValue(v))
922 {
923 a.getValues().add(v);
924 }
925
926 found = true;
927 break;
928 }
929 }
930
931 if (! found)
932 {
933 if (isSynchronizationOperation() ||
934 DirectoryServer.addMissingRDNAttributes())
935 {
936 LinkedHashSet<AttributeValue> valueList =
937 new LinkedHashSet<AttributeValue>(1);
938 valueList.add(v);
939 attrList.add(new Attribute(t, n, valueList));
940 }
941 else
942 {
943 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
944 ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
945 String.valueOf(entryDN), n));
946 }
947 }
948 }
949 }
950 else
951 {
952 List<Attribute> attrList = userAttributes.get(t);
953 if (attrList == null)
954 {
955 if (isSynchronizationOperation() ||
956 DirectoryServer.addMissingRDNAttributes())
957 {
958 LinkedHashSet<AttributeValue> valueList =
959 new LinkedHashSet<AttributeValue>(1);
960 valueList.add(v);
961
962 attrList = new ArrayList<Attribute>();
963 attrList.add(new Attribute(t, n, valueList));
964
965 userAttributes.put(t, attrList);
966 }
967 else
968 {
969 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
970 ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
971 String.valueOf(entryDN),n));
972 }
973 }
974 else
975 {
976 boolean found = false;
977 for (Attribute a : attrList)
978 {
979 if (a.hasOptions())
980 {
981 continue;
982 }
983 else
984 {
985 if (! a.hasValue(v))
986 {
987 a.getValues().add(v);
988 }
989
990 found = true;
991 break;
992 }
993 }
994
995 if (! found)
996 {
997 if (isSynchronizationOperation() ||
998 DirectoryServer.addMissingRDNAttributes())
999 {
1000 LinkedHashSet<AttributeValue> valueList =
1001 new LinkedHashSet<AttributeValue>(1);
1002 valueList.add(v);
1003 attrList.add(new Attribute(t, n, valueList));
1004 }
1005 else
1006 {
1007 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1008 ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
1009 String.valueOf(entryDN),n));
1010 }
1011 }
1012 }
1013 }
1014 }
1015 }
1016
1017
1018
1019 /**
1020 * Adds the provided objectClass to the entry, along with its superior classes
1021 * if appropriate.
1022 *
1023 * @param objectClass The objectclass to add to the entry.
1024 */
1025 public final void addObjectClassChain(ObjectClass objectClass)
1026 {
1027 Map<ObjectClass, String> objectClasses = getObjectClasses();
1028 if (objectClasses != null){
1029 if (! objectClasses.containsKey(objectClass))
1030 {
1031 objectClasses.put(objectClass, objectClass.getNameOrOID());
1032 }
1033
1034 ObjectClass superiorClass = objectClass.getSuperiorClass();
1035 if ((superiorClass != null) &&
1036 (! objectClasses.containsKey(superiorClass)))
1037 {
1038 addObjectClassChain(superiorClass);
1039 }
1040 }
1041 }
1042
1043
1044
1045 /**
1046 * Performs all password policy processing necessary for the provided add
1047 * operation.
1048 *
1049 * @throws DirectoryException If a problem occurs while performing password
1050 * policy processing for the add operation.
1051 */
1052 public final void handlePasswordPolicy()
1053 throws DirectoryException
1054 {
1055 // FIXME -- We need to check to see if the password policy subentry
1056 // might be specified virtually rather than as a real
1057 // attribute.
1058 PasswordPolicy passwordPolicy = null;
1059 List<Attribute> pwAttrList =
1060 entry.getAttribute(OP_ATTR_PWPOLICY_POLICY_DN);
1061 if ((pwAttrList != null) && (! pwAttrList.isEmpty()))
1062 {
1063 Attribute a = pwAttrList.get(0);
1064 LinkedHashSet<AttributeValue> valueSet = a.getValues();
1065 Iterator<AttributeValue> iterator = valueSet.iterator();
1066 if (iterator.hasNext())
1067 {
1068 DN policyDN;
1069 try
1070 {
1071 policyDN = DN.decode(iterator.next().getValue());
1072 }
1073 catch (DirectoryException de)
1074 {
1075 if (debugEnabled())
1076 {
1077 TRACER.debugCaught(DebugLogLevel.ERROR, de);
1078 }
1079
1080 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1081 ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get(
1082 String.valueOf(entryDN),
1083 de.getMessageObject()));
1084 }
1085
1086 passwordPolicy = DirectoryServer.getPasswordPolicy(policyDN);
1087 if (passwordPolicy == null)
1088 {
1089 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1090 ERR_ADD_NO_SUCH_PWPOLICY.get(
1091 String.valueOf(entryDN),
1092 String.valueOf(policyDN)));
1093 }
1094 }
1095 }
1096
1097 if (passwordPolicy == null)
1098 {
1099 passwordPolicy = DirectoryServer.getDefaultPasswordPolicy();
1100 }
1101
1102 // See if a password was specified.
1103 AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
1104 List<Attribute> attrList = entry.getAttribute(passwordAttribute);
1105 if ((attrList == null) || attrList.isEmpty())
1106 {
1107 // The entry doesn't have a password, so no action is required.
1108 return;
1109 }
1110 else if (attrList.size() > 1)
1111 {
1112 // This must mean there are attribute options, which we won't allow for
1113 // passwords.
1114 Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
1115 passwordAttribute.getNameOrOID());
1116 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1117 }
1118
1119 Attribute passwordAttr = attrList.get(0);
1120 if (passwordAttr.hasOptions())
1121 {
1122 Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
1123 passwordAttribute.getNameOrOID());
1124 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1125 }
1126
1127 LinkedHashSet<AttributeValue> values = passwordAttr.getValues();
1128 if (values.isEmpty())
1129 {
1130 // This will be treated the same as not having a password.
1131 return;
1132 }
1133
1134 if ((! passwordPolicy.allowMultiplePasswordValues()) && (values.size() > 1))
1135 {
1136 // FIXME -- What if they're pre-encoded and might all be the same?
1137 addPWPolicyControl(PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED);
1138
1139 Message message = ERR_PWPOLICY_MULTIPLE_PW_VALUES_NOT_ALLOWED.get(
1140 passwordAttribute.getNameOrOID());
1141 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1142 }
1143
1144 CopyOnWriteArrayList<PasswordStorageScheme> defaultStorageSchemes =
1145 passwordPolicy.getDefaultStorageSchemes();
1146 LinkedHashSet<AttributeValue> newValues =
1147 new LinkedHashSet<AttributeValue>(defaultStorageSchemes.size());
1148 for (AttributeValue v : values)
1149 {
1150 ByteString value = v.getValue();
1151
1152 // See if the password is pre-encoded.
1153 if (passwordPolicy.usesAuthPasswordSyntax())
1154 {
1155 if (AuthPasswordSyntax.isEncoded(value))
1156 {
1157 if (passwordPolicy.allowPreEncodedPasswords())
1158 {
1159 newValues.add(v);
1160 continue;
1161 }
1162 else
1163 {
1164 addPWPolicyControl(
1165 PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
1166
1167 Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
1168 passwordAttribute.getNameOrOID());
1169 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1170 message);
1171 }
1172 }
1173 }
1174 else
1175 {
1176 if (UserPasswordSyntax.isEncoded(value))
1177 {
1178 if (passwordPolicy.allowPreEncodedPasswords())
1179 {
1180 newValues.add(v);
1181 continue;
1182 }
1183 else
1184 {
1185 addPWPolicyControl(
1186 PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
1187
1188 Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
1189 passwordAttribute.getNameOrOID());
1190 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1191 message);
1192 }
1193 }
1194 }
1195
1196
1197 // See if the password passes validation. We should only do this if
1198 // validation should be performed for administrators.
1199 if (! passwordPolicy.skipValidationForAdministrators())
1200 {
1201 // There are never any current passwords for an add operation.
1202 HashSet<ByteString> currentPasswords = new HashSet<ByteString>(0);
1203 MessageBuilder invalidReason = new MessageBuilder();
1204 for (PasswordValidator<?> validator :
1205 passwordPolicy.getPasswordValidators().values())
1206 {
1207 if (! validator.passwordIsAcceptable(value, currentPasswords, this,
1208 entry, invalidReason))
1209 {
1210 addPWPolicyControl(
1211 PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
1212
1213 Message message = ERR_PWPOLICY_VALIDATION_FAILED.
1214 get(passwordAttribute.getNameOrOID(),
1215 String.valueOf(invalidReason));
1216 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1217 message);
1218 }
1219 }
1220 }
1221
1222
1223 // Encode the password.
1224 if (passwordPolicy.usesAuthPasswordSyntax())
1225 {
1226 for (PasswordStorageScheme s : defaultStorageSchemes)
1227 {
1228 ByteString encodedValue = s.encodeAuthPassword(value);
1229 newValues.add(new AttributeValue(passwordAttribute, encodedValue));
1230 }
1231 }
1232 else
1233 {
1234 for (PasswordStorageScheme s : defaultStorageSchemes)
1235 {
1236 ByteString encodedValue = s.encodePasswordWithScheme(value);
1237 newValues.add(new AttributeValue(passwordAttribute, encodedValue));
1238 }
1239 }
1240 }
1241
1242
1243 // Put the new encoded values in the entry.
1244 passwordAttr.setValues(newValues);
1245
1246
1247 // Set the password changed time attribute.
1248 ByteString timeString =
1249 new ASN1OctetString(TimeThread.getGeneralizedTime());
1250 AttributeType changedTimeType =
1251 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
1252 if (changedTimeType == null)
1253 {
1254 changedTimeType = DirectoryServer.getDefaultAttributeType(
1255 OP_ATTR_PWPOLICY_CHANGED_TIME);
1256 }
1257
1258 LinkedHashSet<AttributeValue> changedTimeValues =
1259 new LinkedHashSet<AttributeValue>(1);
1260 changedTimeValues.add(new AttributeValue(changedTimeType, timeString));
1261
1262 ArrayList<Attribute> changedTimeList = new ArrayList<Attribute>(1);
1263 changedTimeList.add(new Attribute(changedTimeType,
1264 OP_ATTR_PWPOLICY_CHANGED_TIME,
1265 changedTimeValues));
1266
1267 entry.putAttribute(changedTimeType, changedTimeList);
1268
1269
1270 // If we should force change on add, then set the appropriate flag.
1271 if (passwordPolicy.forceChangeOnAdd())
1272 {
1273 addPWPolicyControl(PasswordPolicyErrorType.CHANGE_AFTER_RESET);
1274
1275 AttributeType resetType =
1276 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
1277 if (resetType == null)
1278 {
1279 resetType = DirectoryServer.getDefaultAttributeType(
1280 OP_ATTR_PWPOLICY_RESET_REQUIRED);
1281 }
1282
1283 LinkedHashSet<AttributeValue> resetValues = new
1284 LinkedHashSet<AttributeValue>(1);
1285 resetValues.add(BooleanSyntax.createBooleanValue(true));
1286
1287 ArrayList<Attribute> resetList = new ArrayList<Attribute>(1);
1288 resetList.add(new Attribute(resetType, OP_ATTR_PWPOLICY_RESET_REQUIRED,
1289 resetValues));
1290 entry.putAttribute(resetType, resetList);
1291 }
1292 }
1293
1294
1295
1296 /**
1297 * Adds a password policy response control if the corresponding request
1298 * control was included.
1299 *
1300 * @param errorType The error type to use for the response control.
1301 */
1302 private void addPWPolicyControl(PasswordPolicyErrorType errorType)
1303 {
1304 for (Control c : getRequestControls())
1305 {
1306 if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
1307 {
1308 addResponseControl(new PasswordPolicyResponseControl(null, 0,
1309 errorType));
1310 }
1311 }
1312 }
1313
1314
1315
1316 /**
1317 * Verifies that the entry to be added conforms to the server schema.
1318 *
1319 * @param parentEntry The parent of the entry to add.
1320 *
1321 * @throws DirectoryException If the entry violates the server schema
1322 * configuration.
1323 */
1324 private void checkSchema(Entry parentEntry)
1325 throws DirectoryException
1326 {
1327 MessageBuilder invalidReason = new MessageBuilder();
1328 if (! entry.conformsToSchema(parentEntry, true, true, true, invalidReason))
1329 {
1330 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
1331 invalidReason.toMessage());
1332 }
1333 else
1334 {
1335 switch (DirectoryServer.getSyntaxEnforcementPolicy())
1336 {
1337 case REJECT:
1338 invalidReason = new MessageBuilder();
1339 for (List<Attribute> attrList : userAttributes.values())
1340 {
1341 for (Attribute a : attrList)
1342 {
1343 AttributeSyntax syntax = a.getAttributeType().getSyntax();
1344 if (syntax != null)
1345 {
1346 for (AttributeValue v : a.getValues())
1347 {
1348 if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
1349 {
1350 Message message = WARN_ADD_OP_INVALID_SYNTAX.get(
1351 String.valueOf(entryDN),
1352 String.valueOf(v.getStringValue()),
1353 String.valueOf(a.getName()),
1354 String.valueOf(invalidReason));
1355
1356 throw new DirectoryException(
1357 ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1358 message);
1359 }
1360 }
1361 }
1362 }
1363 }
1364
1365 for (List<Attribute> attrList :
1366 operationalAttributes.values())
1367 {
1368 for (Attribute a : attrList)
1369 {
1370 AttributeSyntax syntax = a.getAttributeType().getSyntax();
1371 if (syntax != null)
1372 {
1373 for (AttributeValue v : a.getValues())
1374 {
1375 if (! syntax.valueIsAcceptable(v.getValue(),
1376 invalidReason))
1377 {
1378 Message message = WARN_ADD_OP_INVALID_SYNTAX.
1379 get(String.valueOf(entryDN),
1380 String.valueOf(v.getStringValue()),
1381 String.valueOf(a.getName()),
1382 String.valueOf(invalidReason));
1383
1384 throw new DirectoryException(
1385 ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1386 message);
1387 }
1388 }
1389 }
1390 }
1391 }
1392
1393 break;
1394
1395
1396 case WARN:
1397 invalidReason = new MessageBuilder();
1398 for (List<Attribute> attrList : userAttributes.values())
1399 {
1400 for (Attribute a : attrList)
1401 {
1402 AttributeSyntax syntax = a.getAttributeType().getSyntax();
1403 if (syntax != null)
1404 {
1405 for (AttributeValue v : a.getValues())
1406 {
1407 if (! syntax.valueIsAcceptable(v.getValue(),
1408 invalidReason))
1409 {
1410 logError(WARN_ADD_OP_INVALID_SYNTAX.get(
1411 String.valueOf(entryDN),
1412 String.valueOf(v.getStringValue()),
1413 String.valueOf(a.getName()),
1414 String.valueOf(invalidReason)));
1415 }
1416 }
1417 }
1418 }
1419 }
1420
1421 for (List<Attribute> attrList : operationalAttributes.values())
1422 {
1423 for (Attribute a : attrList)
1424 {
1425 AttributeSyntax syntax = a.getAttributeType().getSyntax();
1426 if (syntax != null)
1427 {
1428 for (AttributeValue v : a.getValues())
1429 {
1430 if (! syntax.valueIsAcceptable(v.getValue(),
1431 invalidReason))
1432 {
1433 logError(WARN_ADD_OP_INVALID_SYNTAX.get(
1434 String.valueOf(entryDN),
1435 String.valueOf(v.getStringValue()),
1436 String.valueOf(a.getName()),
1437 String.valueOf(invalidReason)));
1438 }
1439 }
1440 }
1441 }
1442 }
1443
1444 break;
1445 }
1446 }
1447
1448
1449 // See if the entry contains any attributes or object classes marked
1450 // OBSOLETE. If so, then reject the entry.
1451 for (AttributeType at : userAttributes.keySet())
1452 {
1453 if (at.isObsolete())
1454 {
1455 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1456 WARN_ADD_ATTR_IS_OBSOLETE.get(
1457 String.valueOf(entryDN),
1458 at.getNameOrOID()));
1459 }
1460 }
1461
1462 for (AttributeType at : operationalAttributes.keySet())
1463 {
1464 if (at.isObsolete())
1465 {
1466 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1467 WARN_ADD_ATTR_IS_OBSOLETE.get(
1468 String.valueOf(entryDN),
1469 at.getNameOrOID()));
1470 }
1471 }
1472
1473 for (ObjectClass oc : objectClasses.keySet())
1474 {
1475 if (oc.isObsolete())
1476 {
1477 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1478 WARN_ADD_OC_IS_OBSOLETE.get(
1479 String.valueOf(entryDN),
1480 oc.getNameOrOID()));
1481 }
1482 }
1483 }
1484
1485
1486
1487 /**
1488 * Processes the set of controls contained in the add request.
1489 *
1490 * @param parentDN The DN of the parent of the entry to add.
1491 *
1492 * @throws DirectoryException If there is a problem with any of the
1493 * request controls.
1494 */
1495 private void processControls(DN parentDN)
1496 throws DirectoryException
1497 {
1498 List<Control> requestControls = getRequestControls();
1499 if ((requestControls != null) && (! requestControls.isEmpty()))
1500 {
1501 for (int i=0; i < requestControls.size(); i++)
1502 {
1503 Control c = requestControls.get(i);
1504 String oid = c.getOID();
1505
1506 if (!AccessControlConfigManager.getInstance().
1507 getAccessControlHandler().isAllowed(parentDN, this, c))
1508 {
1509 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
1510 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
1511 }
1512
1513 if (oid.equals(OID_LDAP_ASSERTION))
1514 {
1515 LDAPAssertionRequestControl assertControl;
1516 if (c instanceof LDAPAssertionRequestControl)
1517 {
1518 assertControl = (LDAPAssertionRequestControl) c;
1519 }
1520 else
1521 {
1522 try
1523 {
1524 assertControl = LDAPAssertionRequestControl.decodeControl(c);
1525 requestControls.set(i, assertControl);
1526 }
1527 catch (LDAPException le)
1528 {
1529 if (debugEnabled())
1530 {
1531 TRACER.debugCaught(DebugLogLevel.ERROR, le);
1532 }
1533
1534 throw new DirectoryException(
1535 ResultCode.valueOf(le.getResultCode()),
1536 le.getMessageObject());
1537 }
1538 }
1539
1540 try
1541 {
1542 // FIXME -- We need to determine whether the current user has
1543 // permission to make this determination.
1544 SearchFilter filter = assertControl.getSearchFilter();
1545 if (! filter.matchesEntry(entry))
1546 {
1547 throw new DirectoryException(ResultCode.ASSERTION_FAILED,
1548 ERR_ADD_ASSERTION_FAILED.get(
1549 String.valueOf(entryDN)));
1550 }
1551 }
1552 catch (DirectoryException de)
1553 {
1554 if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
1555 {
1556 throw de;
1557 }
1558
1559 if (debugEnabled())
1560 {
1561 TRACER.debugCaught(DebugLogLevel.ERROR, de);
1562 }
1563
1564 throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1565 ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(
1566 String.valueOf(entryDN),
1567 de.getMessageObject()));
1568 }
1569 }
1570 else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
1571 {
1572 noOp = true;
1573 }
1574 else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
1575 {
1576 if (c instanceof LDAPPostReadRequestControl)
1577 {
1578 postReadRequest = (LDAPPostReadRequestControl) c;
1579 }
1580 else
1581 {
1582 try
1583 {
1584 postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
1585 requestControls.set(i, postReadRequest);
1586 }
1587 catch (LDAPException le)
1588 {
1589 if (debugEnabled())
1590 {
1591 TRACER.debugCaught(DebugLogLevel.ERROR, le);
1592 }
1593
1594 throw new DirectoryException(
1595 ResultCode.valueOf(le.getResultCode()),
1596 le.getMessageObject());
1597 }
1598 }
1599 }
1600 else if (oid.equals(OID_PROXIED_AUTH_V1))
1601 {
1602 // The requester must have the PROXIED_AUTH privilige in order to
1603 // be able to use this control.
1604 if (! getClientConnection().hasPrivilege(Privilege.PROXIED_AUTH,
1605 this))
1606 {
1607 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
1608 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
1609 }
1610
1611
1612 ProxiedAuthV1Control proxyControl;
1613 if (c instanceof ProxiedAuthV1Control)
1614 {
1615 proxyControl = (ProxiedAuthV1Control) c;
1616 }
1617 else
1618 {
1619 try
1620 {
1621 proxyControl = ProxiedAuthV1Control.decodeControl(c);
1622 }
1623 catch (LDAPException le)
1624 {
1625 if (debugEnabled())
1626 {
1627 TRACER.debugCaught(DebugLogLevel.ERROR, le);
1628 }
1629
1630 throw new DirectoryException(
1631 ResultCode.valueOf(le.getResultCode()),
1632 le.getMessageObject());
1633 }
1634 }
1635
1636
1637 Entry authorizationEntry = proxyControl.getAuthorizationEntry();
1638 setAuthorizationEntry(authorizationEntry);
1639 if (authorizationEntry == null)
1640 {
1641 setProxiedAuthorizationDN(DN.nullDN());
1642 }
1643 else
1644 {
1645 setProxiedAuthorizationDN(authorizationEntry.getDN());
1646 }
1647 }
1648 else if (oid.equals(OID_PROXIED_AUTH_V2))
1649 {
1650 // The requester must have the PROXIED_AUTH privilige in order to
1651 // be able to use this control.
1652 if (! getClientConnection().hasPrivilege(Privilege.PROXIED_AUTH,
1653 this))
1654 {
1655 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
1656 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
1657 }
1658
1659
1660 ProxiedAuthV2Control proxyControl;
1661 if (c instanceof ProxiedAuthV2Control)
1662 {
1663 proxyControl = (ProxiedAuthV2Control) c;
1664 }
1665 else
1666 {
1667 try
1668 {
1669 proxyControl = ProxiedAuthV2Control.decodeControl(c);
1670 }
1671 catch (LDAPException le)
1672 {
1673 if (debugEnabled())
1674 {
1675 TRACER.debugCaught(DebugLogLevel.ERROR, le);
1676 }
1677
1678 throw new DirectoryException(
1679 ResultCode.valueOf(le.getResultCode()),
1680 le.getMessageObject());
1681 }
1682 }
1683
1684
1685 Entry authorizationEntry = proxyControl.getAuthorizationEntry();
1686 setAuthorizationEntry(authorizationEntry);
1687 if (authorizationEntry == null)
1688 {
1689 setProxiedAuthorizationDN(DN.nullDN());
1690 }
1691 else
1692 {
1693 setProxiedAuthorizationDN(authorizationEntry.getDN());
1694 }
1695 }
1696 else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
1697 {
1698 // We don't need to do anything here because it's already handled
1699 // in LocalBackendAddOperation.handlePasswordPolicy().
1700 }
1701
1702 // NYI -- Add support for additional controls.
1703 else if (c.isCritical())
1704 {
1705 if ((backend == null) || (! backend.supportsControl(oid)))
1706 {
1707 throw new DirectoryException(
1708 ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
1709 ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get(
1710 String.valueOf(entryDN), oid));
1711 }
1712 }
1713 }
1714 }
1715 }
1716
1717
1718
1719 /**
1720 * Adds the post-read response control to the response.
1721 */
1722 private void addPostReadResponse()
1723 {
1724 Entry addedEntry = entry.duplicate(true);
1725
1726 if (! postReadRequest.allowsAttribute(
1727 DirectoryServer.getObjectClassAttributeType()))
1728 {
1729 addedEntry.removeAttribute(DirectoryServer.getObjectClassAttributeType());
1730 }
1731
1732 if (! postReadRequest.returnAllUserAttributes())
1733 {
1734 Iterator<AttributeType> iterator =
1735 addedEntry.getUserAttributes().keySet().iterator();
1736 while (iterator.hasNext())
1737 {
1738 AttributeType attrType = iterator.next();
1739 if (! postReadRequest.allowsAttribute(attrType))
1740 {
1741 iterator.remove();
1742 }
1743 }
1744 }
1745
1746 if (! postReadRequest.returnAllOperationalAttributes())
1747 {
1748 Iterator<AttributeType> iterator =
1749 addedEntry.getOperationalAttributes().keySet().iterator();
1750 while (iterator.hasNext())
1751 {
1752 AttributeType attrType = iterator.next();
1753 if (! postReadRequest.allowsAttribute(attrType))
1754 {
1755 iterator.remove();
1756 }
1757 }
1758 }
1759
1760 // FIXME -- Check access controls on the entry to see if it should
1761 // be returned or if any attributes need to be stripped
1762 // out..
1763 SearchResultEntry searchEntry = new SearchResultEntry(addedEntry);
1764 LDAPPostReadResponseControl responseControl =
1765 new LDAPPostReadResponseControl(postReadRequest.getOID(),
1766 postReadRequest.isCritical(),
1767 searchEntry);
1768 addResponseControl(responseControl);
1769 }
1770 }
1771