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