From 7dbb2ffb7f4587baf363193f7e25aa11bfc775c4 Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Fri, 06 Apr 2007 00:01:33 +0000
Subject: [PATCH] Add ACI support for LDAP modify DN operation (export and import rights). Also add support for self-write (selfwrite) right.

---
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/AciTests.java |  177 +++++++++++++++++++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java        |   13 +
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java                          |   47 +++++
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java               |    7 
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java                              |    6 
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java                       |  219 ++++++++++++++++++++---
 opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java                                      |   10 +
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java        |   18 +
 8 files changed, 448 insertions(+), 49 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
index 8a27eb5..5308889 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
@@ -183,7 +183,7 @@
     public static final int ACI_SEARCH = 0x0020;
 
     /**
-     * ACI_SELF is used for the SELFWRITE right. Currently not implemented.
+     * ACI_SELF is used for the SELFWRITE right.
      */
     public static final int ACI_SELF = 0x0040;
 
@@ -200,13 +200,13 @@
 
     /**
      * ACI_IMPORT is used to set the container rights for a LDAP
-     * modify dn operation. Currently not used.
+     * modify dn operation.
      */
     public static final int ACI_IMPORT = 0x0100;
 
     /**
      * ACI_EXPORT is used to set the container rights for a LDAP
-     * modify dn operation. Currently not used.
+     * modify dn operation.
      */
     public static final int ACI_EXPORT = 0x0200;
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
index f46529d..8544649 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -44,8 +44,10 @@
 import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
 import org.opends.server.protocols.internal.InternalSearchOperation;
 import org.opends.server.protocols.internal.InternalClientConnection;
+import static org.opends.server.schema.SchemaConstants.*;
 
 import java.util.*;
+import java.util.concurrent.locks.Lock;
 
 /**
  * The AciHandler class performs the main processing for the
@@ -216,9 +218,9 @@
         List<Modification> modifications=container.getModifications();
         for(Modification m : modifications) {
             Attribute modAttr=m.getAttribute();
-            AttributeType modType=modAttr.getAttributeType();
+            AttributeType modAttrType=modAttr.getAttributeType();
 
-            if(modType.equals(aciType)) {
+            if(modAttrType.equals(aciType)) {
               /*
                * Check that the operation has modify privileges if
                * it contains an "aci" attribute type.
@@ -236,20 +238,22 @@
                 return false;
               }
             }
-
-            switch(m.getModificationType()) {
-              case DELETE:
-              case REPLACE:
-              case INCREMENT:
-              {
-                /*
+            //This access check handles the case where all attributes of this
+            //type are being replaced or deleted. If only a subset is being
+            //deleted than this access check is skipped.
+            ModificationType modType=m.getModificationType();
+            if((modType == ModificationType.DELETE &&
+                modAttr.getValues().isEmpty()) ||
+               (modType == ModificationType.REPLACE ||
+                modType == ModificationType.INCREMENT)) {
+                                /*
                  * Check if we have rights to delete all values of
                  * an attribute type in the resource entry.
                  */
-                if(resourceEntry.hasAttribute(modType)) {
-                  container.setCurrentAttributeType(modType);
+                if(resourceEntry.hasAttribute(modAttrType)) {
+                  container.setCurrentAttributeType(modAttrType);
                   List<Attribute> attrList =
-                       resourceEntry.getAttribute(modType,modAttr.getOptions());
+                   resourceEntry.getAttribute(modAttrType,modAttr.getOptions());
                   for (Attribute a : attrList) {
                     for (AttributeValue v : a.getValues()) {
                       container.setCurrentAttributeValue(v);
@@ -260,11 +264,11 @@
                     }
                   }
                 }
-              }
-            }
+             }
+
             if(modAttr.hasValue()) {
                for(AttributeValue v : modAttr.getValues()) {
-                   container.setCurrentAttributeType(modType);
+                   container.setCurrentAttributeType(modAttrType);
                    switch (m.getModificationType())
                    {
                      case ADD:
@@ -283,7 +287,7 @@
                      case INCREMENT:
                        Entry modifiedEntry = operation.getModifiedEntry();
                        List<Attribute> modifiedAttrs =
-                            modifiedEntry.getAttribute(modType,
+                            modifiedEntry.getAttribute(modAttrType,
                                                        modAttr.getOptions());
                        if (modifiedAttrs != null)
                        {
@@ -305,12 +309,12 @@
                    If so, check the syntax of that attribute value. Fail the
                    the operation if the syntax check fails.
                    */
-                   if(modType.equals(aciType)  ||
-                      modType.equals(globalAciType)) {
+                   if(modAttrType.equals(aciType)  ||
+                      modAttrType.equals(globalAciType)) {
                        try {
                            //A global ACI needs a NULL DN, not the DN of the
                            //modification.
-                           if(modType.equals(globalAciType))
+                           if(modAttrType.equals(globalAciType))
                                dn=DN.nullDN();
                            Aci.decode(v.getValue(),dn);
                        } catch (AciException ex) {
@@ -420,6 +424,25 @@
         if(container.hasRights(ACI_WRITE_ADD) ||
            container.hasRights(ACI_WRITE_DELETE))
                 container.setRights(container.getRights() | ACI_WRITE);
+        //Check if the ACI_SELF right needs to be set (selfwrite right).
+        //Only done if the right is ACI_WRITE,  an attribute value is set and
+        //that attribute value is a DN.
+        if((container.getCurrentAttributeValue() != null) &&
+           (container.hasRights(ACI_WRITE)) &&
+           (isAttributeDN(container.getCurrentAttributeType())))  {
+          try {
+            String DNString =
+                        container.getCurrentAttributeValue().getStringValue();
+            DN tmpDN = DN.decode(DNString);
+            //Have a valid DN, compare to clientDN to see if the ACI_SELF
+            //right should be set.
+            if(tmpDN.equals(container.getClientDN())) {
+              container.setRights(container.getRights() | ACI_SELF);
+            }
+          } catch (DirectoryException ex) {
+             return false;
+          }
+        }
         /*
          * First get all allowed candidate ACIs.
          */
@@ -435,6 +458,17 @@
         return(testApplicableLists(container));
     }
 
+  /**
+   * Check if the specified attribute type is a DN by checking if its syntax
+   * OID is equal to the DN syntax OID.
+   * @param attribute The attribute type to check.
+   * @return True if the attribute type syntax OID is equal to a DN syntax OID.
+   */
+  private boolean isAttributeDN(AttributeType attribute) {
+    return (attribute.getSyntaxOID().equals(SYNTAX_DN_OID));
+  }
+
+
     /**
      * Performs an access check against all of the attributes of an entry.
      * The attributes that fail access are removed from the entry. This method
@@ -781,15 +815,148 @@
       return returnEntry;
   }
 
-  //Planned to be implemented methods
+  /**
+   * Perform all needed RDN checks for the modifyDN operation. These checks
+   * are:
+   *
+   *  - Verify WRITE access to the entry.
+   *  - Verfiy WRITE_ADD access on each RDN component of the new RDN. The
+   *    WRITE_ADD access is used because this access could be restricted by
+   *    the targattrfilters keyword.
+   *  - If the deleteOLDRDN flag is set, verify WRITE_DELETE access on the
+   *    old RDN. The WRITE_DELETE access is used because this access could be
+   *    restricted by the targattrfilters keyword.
+   *
+   * @param operation   The ModifyDN operation class containing information to
+   * check access on.
+   * @return True if access is allowed.
+   */
+  private boolean aciCheckRDNs(ModifyDNOperation operation) {
+      boolean ret;
+      AciLDAPOperationContainer operationContainer =
+              new AciLDAPOperationContainer(operation, (ACI_WRITE),
+                      operation.getOriginalEntry());
+      ret=accessAllowed(operationContainer);
+      if(ret)
+          ret=checkRDN(ACI_WRITE_ADD,operation.getNewRDN(),operationContainer);
+      if(ret && operation.deleteOldRDN()) {
+          RDN oldRDN=operation.getOriginalEntry().getDN().getRDN();
+          ret =
+            checkRDN(ACI_WRITE_DELETE, oldRDN, operationContainer);
+      }
+      return ret;
+  }
+
 
   /**
+   * Check access on each attribute-value pair component of the specified RDN.
+   * There may be more than one attribute-value pair if the RDN is multi-valued.
+   *
+   * @param right  The access right to check for.
+   * @param rdn  The RDN to examine the attribute-value pairs of.
+   * @param container The container containing the information needed to
+   * evaluate the specified RDN.
+   * @return  True if access is allowed for all attribute-value pairs.
+   */
+  private boolean checkRDN(int right, RDN rdn, AciContainer container) {
+        boolean ret=false;
+        int numAVAs = rdn.getNumValues();
+        container.setRights(right);
+        for (int i = 0; i < numAVAs; i++){
+            AttributeType type=rdn.getAttributeType(i);
+            AttributeValue value=rdn.getAttributeValue(i);
+            container.setCurrentAttributeType(type);
+            container.setCurrentAttributeValue(value);
+            if(!(ret=accessAllowed(container)))
+                break;
+        }
+        return ret;
+  }
+
+  /**
+   * Check access on the new superior entry if it exists. If the entry does not
+   * exist or the DN cannot be locked then false is returned.
+   *
+   * @param superiorDN The DN of the new superior entry.
+   * @param op The modifyDN operation to check access on.
+   * @return True if access is granted to the new superior entry.
+   * @throws DirectoryException  If a problem occurs while trying to
+   *                             retrieve the new superior entry.
+   */
+  private boolean aciCheckSuperiorEntry(DN superiorDN, ModifyDNOperation op)
+  throws DirectoryException {
+    boolean ret=false;
+    Lock entryLock = null;
+    for (int i=0; i < 3; i++)  {
+      entryLock = LockManager.lockRead(superiorDN);
+      if (entryLock != null)
+        break;
+    }
+    if (entryLock == null) {
+      int    msgID   = MSGID_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER;
+      String message = getMessage(msgID, String.valueOf(superiorDN));
+       logError(ErrorLogCategory.ACCESS_CONTROL, ErrorLogSeverity.INFORMATIONAL,
+                message, msgID);
+      return false;
+    }
+    try {
+      Entry superiorEntry=DirectoryServer.getEntry(superiorDN);
+      if(superiorEntry!= null) {
+        AciLDAPOperationContainer operationContainer =
+                new AciLDAPOperationContainer(op, (ACI_IMPORT),
+                        superiorEntry);
+        ret=accessAllowed(operationContainer);
+      }
+    }  finally {
+          LockManager.unlock(superiorDN, entryLock);
+    }
+    return ret;
+  }
+
+  /**
+   * Checks access on a modifyDN operation.
+   *
+   * @param operation The modifyDN operation to check access on.
+   * @return True if access is allowed.
+   *
+   */
+  public boolean isAllowed(ModifyDNOperation operation) {
+      boolean ret=true;
+      DN newSuperiorDN;
+      if(!skipAccessCheck(operation)) {
+          //If this is a modifyDN move to a new superior, then check if the
+          //superior DN has import accesss.
+          if((newSuperiorDN=operation.getNewSuperior()) != null) {
+             try {
+               ret=aciCheckSuperiorEntry(newSuperiorDN, operation);
+             } catch (DirectoryException ex) {
+               ret=false;
+             }
+          }
+          //Perform the RDN access checks.
+          if(ret)
+              ret=aciCheckRDNs(operation);
+          //If this is a modifyDN move to a new superior, then check if the
+          //original entry DN has export access.
+          if(ret && (newSuperiorDN != null)) {
+              AciLDAPOperationContainer operationContainer =
+                      new AciLDAPOperationContainer(operation, (ACI_EXPORT),
+                              operation.getOriginalEntry());
+                 ret=accessAllowed(operationContainer);
+          }
+      }
+      return ret;
+  }
+
+  //Not planned to be implemented methods.
+
+   /**
    * {@inheritDoc}
    */
   @Override
   public boolean maySend(SearchOperation operation,
       SearchResultReference reference) {
-    //TODO: Planned to be implemented.
+    //TODO: Deferred.
     return true;
   }
 
@@ -797,16 +964,6 @@
    * {@inheritDoc}
    */
   @Override
-  public boolean isAllowed(ModifyDNOperation modifyDNOperation) {
-      // TODO: Planned to be implemented.
-      return true;
-  }
-
-  //Not planned to be implemented methods.
-  /**
-   * {@inheritDoc}
-   */
-  @Override
   public boolean isAllowed(BindOperation bindOperation) {
       //Not planned to be implemented.
       return true;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
index 30b9560..cf1898d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
@@ -29,13 +29,10 @@
 
 import java.util.List;
 
-import org.opends.server.core.AddOperation;
-import org.opends.server.core.CompareOperation;
-import org.opends.server.core.DeleteOperation;
-import org.opends.server.core.ModifyOperation;
-import org.opends.server.core.SearchOperation;
+import org.opends.server.core.*;
 import org.opends.server.types.Modification;
 import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.Entry;
 
 /**
  * The AciLDAPOperationContainer is an AciContainer
@@ -93,6 +90,17 @@
     }
 
     /**
+     * Constructor interface for the modify DN operation.
+     * @param operation  The modify DN operation.
+     * @param rights  The rights of the modify DN operation.
+     * @param entry  The entry to evalauted for this modify DN.
+     */
+    public AciLDAPOperationContainer(ModifyDNOperation operation,  int rights,
+                                     Entry entry) {
+        super(operation, rights,  entry);
+    }
+
+    /**
      * Constructor interface for the LDAP search operation.
      * @param operation The search operation.
      * @param rights The rights of a search operation.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
index 399ca85e..7c6d913 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
@@ -343,4 +343,51 @@
     // Replace the ACI list with the copy.
     aciList = aciCopy;
   }
+
+  /**
+   * Rename all ACIs under the specified old DN to the new DN. A simple
+   * interation over the entire list is performed.
+   * @param oldDN The DN of the original entry that was moved.
+   * @param newDN The DN of the new entry.
+   */
+  public synchronized void renameAci(DN oldDN, DN newDN ) {
+    LinkedHashMap<DN, List<Aci>> newCopyList =
+            new LinkedHashMap<DN, List<Aci>>();
+    int oldRDNCount=oldDN.getNumComponents();
+    int newRDNCount=newDN.getNumComponents();
+    for (Map.Entry<DN,List<Aci>> hashEntry : aciList.entrySet()) {
+      if(hashEntry.getKey().isDescendantOf(oldDN)) {
+        int keyRDNCount=hashEntry.getKey().getNumComponents();
+        int keepRDNCount=keyRDNCount - oldRDNCount;
+        RDN[] newRDNs = new RDN[keepRDNCount + newRDNCount];
+        for (int i=0; i < keepRDNCount; i++)
+          newRDNs[i] = hashEntry.getKey().getRDN(i);
+        for (int i=keepRDNCount, j=0; j < newRDNCount; i++,j++)
+          newRDNs[i] = newDN.getRDN(j);
+        DN relocateDN=new DN(newRDNs);
+        List<Aci> acis = new LinkedList<Aci>();
+        for(Aci aci : hashEntry.getValue()) {
+          try {
+             Aci newAci =
+               Aci.decode(ByteStringFactory.create(aci.toString()), relocateDN);
+             acis.add(newAci);
+          } catch (AciException ex) {
+            //This should never happen since only a copy of the
+            //ACI with a new DN is being made. Log a message if it does and
+            //keep going.
+            int    msgID  = MSGID_ACI_ADD_LIST_FAILED_DECODE;
+            String message = getMessage(msgID,
+                    ex.getMessage());
+            logError(ErrorLogCategory.ACCESS_CONTROL,
+                    ErrorLogSeverity.INFORMATIONAL,
+                    message, msgID);
+          }
+        }
+        newCopyList.put(relocateDN, acis);
+      }  else
+        newCopyList.put(hashEntry.getKey(), hashEntry.getValue());
+    }
+    // Replace the ACI list with the copy.
+    aciList = newCopyList;
+  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
index 5c3e268..9841ec6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
@@ -150,7 +150,8 @@
     }
 
     /**
-     * Not implemented.
+     * A modify DN operation has succeeded. Adjust the ACIs by moving ACIs
+     * under the old entry DN to the new entry DN.
      * @param modifyDNOperation  The LDAP modify DN operation.
      * @param oldEntry  The old entry.
      * @param newEntry The new entry.
@@ -159,9 +160,7 @@
             PostResponseModifyDNOperation modifyDNOperation,
             Entry oldEntry, Entry newEntry)
     {
-        /*
-         * TODO Not yet implemented.
-         */
+        aciList.renameAci(oldEntry.getDN(), newEntry.getDN());
     }
 
     /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java
index 23c43e9..0103519 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/AciMessages.java
@@ -722,6 +722,13 @@
     public static final int MSGID_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN =
          CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 71;
 
+   /**
+    * The message ID for the message that will be used if the server is unable to
+    * obtain a lock on a ModifyDN new superior entry.  This takes a
+    * single argument, which is the DN of the new superior entry.
+    */
+   public static final int MSGID_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER =
+        CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 72;
 
     /**
      * Associates a set of generic messages with the message IDs defined in
@@ -1137,5 +1144,8 @@
         registerMessage(MSGID_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN,
           "The pattern DN %s is not valid because it contains a wildcard in " +
                "an attribute type in a multi-valued RDN");
+
+      registerMessage(MSGID_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER,
+          "Unable to obtain a lock on the ModifyDN new superior entry %s.");
     }
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/AciTests.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/AciTests.java
index a849e1b..7948766 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/AciTests.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/AciTests.java
@@ -155,10 +155,18 @@
   private static final String OU_GROUP_1_DN = "cn=group1," + OU_GROUPS_DN;
   private static final String OU_GROUP_2_DN = "cn=group2," + OU_GROUPS_DN;
   //End group entries.
-  private static final String MANAGER_DN = "cn=the managers,dc=example,dc=com";
+  //Used by modrdn new superior
+  private static final String MANAGER_NEW_DN =
+                                        "cn=new managers," + OU_BASE_DN;
+  private static final String MGR_NEW_DN_URL = "ldap:///" + MANAGER_NEW_DN;
+  private static final String MANAGER_DN = "cn=the managers," + OU_BASE_DN;
+  private static final String MGR_DN_URL = "ldap:///" + MANAGER_DN;
   //These entries are going to be used to test userattr parent stuff.
   private static final String SALES_DN = "cn=sales dept," + MANAGER_DN;
+  private static final String SALES_NEW_DN = "cn=sales dept," + MANAGER_NEW_DN;
   private static final String SALES_USER_1 = "cn=sales1 person," + SALES_DN;
+  private static final String SALES_USER_NEW_1 =
+                                           "cn=sales1 person," + SALES_NEW_DN;
   private static final String SALES_USER_2 = "cn=sales2 person," + SALES_DN;
   private static final String SALES_USER_3 = "cn=sales3 person," + SALES_DN;
   private static final String LEVEL_1_USER_URL =
@@ -190,6 +198,7 @@
     OU_LEAF_DN,
     OU_INNER_DN,
     MANAGER_DN,
+    MANAGER_NEW_DN,
     OU_GROUPS_DN,
     OU_BASE_DN,
     ADMIN_DN,
@@ -297,6 +306,27 @@
   private static final String ALLOW_ALL_TO_COMPARE =
              buildAciValue("name", "allow compare", "targetattr", "*", "target", "ldap:///cn=*," + OU_LEAF_DN, "allow(compare)", BIND_RULE_USERDN_ALL);
 
+  private static final String ALLOW_ALL_TO_IMPORT_MGR_NEW =
+             buildAciValue("name", "allow import mgr new tree", "target", MGR_NEW_DN_URL, "allow(import)", BIND_RULE_USERDN_ALL);
+
+  private static final String ALLOW_ALL_TO_IMPORT_MGR=
+             buildAciValue("name", "allow import mgr tree", "target", MGR_DN_URL, "allow(import)", BIND_RULE_USERDN_ALL);
+
+  private static final String ALLOW_ALL_TO_EXPORT_MGR_NEW =
+             buildAciValue("name", "allow export mgr new tree", "target", MGR_NEW_DN_URL, "allow(export)", BIND_RULE_USERDN_ALL);
+
+  private static final String ALLOW_ALL_TO_EXPORT_MGR=
+             buildAciValue("name", "allow export mgr tree", "target", MGR_DN_URL, "allow(export)", BIND_RULE_USERDN_ALL);
+
+  private static final String ALLOW_ALL_TO_WRITE_RDN_ATTRS=
+             buildAciValue("name", "allow write to RDN attrs", "targetattr", "uid || cn || sn", "allow(write)", BIND_RULE_USERDN_ALL);
+
+  private static final String ALLOW_ALL_TO_MOVED_ENTRY =
+             buildAciValue("name", "allow all to moved", "targetattr", "*", "allow(search,read)", BIND_RULE_USERDN_ALL);
+
+  private static final String ALLOW_ALL_TO_SELFWRITE =
+             buildAciValue("name", "allow selfwrite", "targetattr", "member", "allow(selfwrite)", BIND_RULE_USERDN_ALL);
+
   private static final String ALLOW_ALL_TO_ADMIN =
           buildAciValue("name", "allow all to admin", "targetattr", "*", "allow(all)", BIND_RULE_USERDN_ADMIN);
 
@@ -944,6 +974,11 @@
             makeUserLdif(MANAGER_DN, "the", "managers", "pa$$word",
                          ADMIN_DN, OU_GROUP_2_DN );
 
+   private static final String MANAGER_NEW__SEARCH_TESTS =
+           makeUserLdif(MANAGER_NEW_DN, "new", "managers", "pa$$word",
+                        ADMIN_DN, OU_GROUP_2_DN );
+
+
     private static final String SALES__SEARCH_TESTS =
             makeUserLdif(SALES_DN, "sales", "dept", "pa$$word",
                         LEVEL_2_USER_DN, LEVEL_1_USER_URL);
@@ -951,6 +986,7 @@
   //LDIF entries used to test group stuff.
   private static final String GROUP_LDIF__SEARCH_TESTS =
                                              makeOuLdif(OU_GROUPS_DN, "groups");
+
   private static final
   String GROUP_1_LDIF__SEARCH_TESTS = makeGroupLdif(OU_GROUP_1_DN,
                                                     LEVEL_1_USER_DN,
@@ -1009,6 +1045,31 @@
  String COMPARE_ACI =  makeAddAciLdif(OU_LEAF_DN,
                                        ALLOW_ALL_TO_COMPARE);
 
+  //ACI used to test selfwrite
+  private static final
+  String SELFWRITE_ACI =  makeAddAciLdif(OU_GROUP_1_DN,
+                                        ALLOW_ALL_TO_SELFWRITE);
+
+  //ACIs used for modDN tests (export, import)
+
+ private static final  String ACI_IMPORT_MGR_NEW =
+                   makeAddAciLdif(OU_BASE_DN, ALLOW_ALL_TO_IMPORT_MGR_NEW);
+
+ private static final  String ACI_IMPORT_MGR =
+                   makeAddAciLdif(OU_BASE_DN, ALLOW_ALL_TO_IMPORT_MGR);
+
+ private static final  String ACI_EXPORT_MGR_NEW =
+                   makeAddAciLdif(OU_BASE_DN, ALLOW_ALL_TO_EXPORT_MGR_NEW);
+
+ private static final  String ACI_EXPORT_MGR =
+                   makeAddAciLdif(OU_BASE_DN, ALLOW_ALL_TO_EXPORT_MGR);
+
+  private static final String ACI_WRITE_RDN_ATTRS =
+                   makeAddAciLdif(OU_BASE_DN, ALLOW_ALL_TO_WRITE_RDN_ATTRS);
+
+   private static final String ACI_MOVED_ENTRY =
+                   makeAddAciLdif(SALES_USER_1, ALLOW_ALL_TO_MOVED_ENTRY);
+
 //ACI used in testing the groupdn/roledn bind rule keywords.
 
  private static final
@@ -1046,6 +1107,7 @@
             USER_LDIF__SEARCH_TESTS +
             BASE_OU_LDIF__SEARCH_TESTS  +
             MANAGER__SEARCH_TESTS +
+            MANAGER_NEW__SEARCH_TESTS +
             SALES__SEARCH_TESTS +
             SALES_USER_1__SEARCH_TESTS +
             SALES_USER_2__SEARCH_TESTS +
@@ -1578,7 +1640,7 @@
 
  /**
   * Test LDAP compare.
-  * @throws Throwable If the search returned is not valid for the ACI.
+  * @throws Throwable If the compare is not valid for the ACI.
  */
  @Test()
   public void testCompare() throws Throwable {
@@ -1597,7 +1659,82 @@
       }
   }
 
- /**
+  /**
+   * Test modify DN. Add a set of ACIs to allow exports, imports and write
+   * rights. Also add an aci low in the DIT to test the ACI list after a move
+   * has been made. Move the subtree, search with base at new DN, move the
+   * tree back and re-search with base at orig DN.
+   * @throws Throwable
+   */
+  @Test()
+  public void testModDN() throws Throwable {
+    SingleSearchParams userParamOrig = new SingleSearchParams(LEVEL_1_USER_DN,
+                                      "pa$$word", SALES_USER_1,
+                                      OBJECTCLASS_STAR, SCOPE_BASE,
+                                      null, null, null);
+    SingleSearchParams userParamNew = new SingleSearchParams(LEVEL_1_USER_DN,
+                                      "pa$$word", SALES_USER_NEW_1,
+                                      OBJECTCLASS_STAR, SCOPE_BASE,
+                                      null, null, null);
+
+
+     try {
+        addEntries(BASIC_LDIF__GROUP_SEARCH_TESTS, DIR_MGR_DN, DIR_MGR_PW);
+        modEntries(ACI_IMPORT_MGR, DIR_MGR_DN, DIR_MGR_PW);
+        modEntries(ACI_IMPORT_MGR_NEW, DIR_MGR_DN, DIR_MGR_PW);
+        modEntries(ACI_EXPORT_MGR, DIR_MGR_DN, DIR_MGR_PW);
+        modEntries(ACI_EXPORT_MGR_NEW, DIR_MGR_DN, DIR_MGR_PW);
+        modEntries(ACI_WRITE_RDN_ATTRS, DIR_MGR_DN, DIR_MGR_PW);
+        modEntries(ACI_MOVED_ENTRY, DIR_MGR_DN, DIR_MGR_PW);
+        String modrdnLdif =
+                makeModDN(SALES_DN, "cn=sales dept", "0", MANAGER_NEW_DN);
+        modEntries(modrdnLdif, LEVEL_1_USER_DN, "pa$$word");
+        String userNewResults = ldapSearch(userParamNew.getLdapSearchArgs());
+        Assert.assertFalse(userNewResults.equals(""));
+        String modrdnLdif1 =
+                makeModDN(SALES_NEW_DN, "cn=sales dept", "0", MANAGER_DN);
+        modEntries(modrdnLdif1, LEVEL_1_USER_DN, "pa$$word");
+        String userOrigResults = ldapSearch(userParamOrig.getLdapSearchArgs());
+        Assert.assertFalse(userOrigResults.equals(""));
+   } catch (Throwable e)  {
+       throw e;
+   }
+  }
+  /**
+   * Test selfwrite right. Attempt to bind as level3 user and remove level1
+   * user from a group, should fail.
+   * @throws Throwable If the delete succeeds.
+   */
+  @Test()
+  public void testNonSelfWrite() throws Throwable {
+          try {
+            addEntries(BASIC_LDIF__GROUP_SEARCH_TESTS, DIR_MGR_DN, DIR_MGR_PW);
+            modEntries(SELFWRITE_ACI, DIR_MGR_DN, DIR_MGR_PW);
+            deleteAttrFromEntry(OU_GROUP_1_DN, "member",LEVEL_1_USER_DN,
+                                LEVEL_3_USER_DN, "pa$$word",  false);
+          } catch(Throwable e) {
+                throw e;
+          }
+  }
+
+  /**
+   * Test selfwrite right. Attempt to bind as level1 user and remove itself
+   * from a group, should succeed.
+   * @throws Throwable If the delete fails.
+   */
+  @Test()
+  public void testSelfWrite() throws Throwable {
+          try {
+            addEntries(BASIC_LDIF__GROUP_SEARCH_TESTS, DIR_MGR_DN, DIR_MGR_PW);
+            modEntries(SELFWRITE_ACI, DIR_MGR_DN, DIR_MGR_PW);
+            deleteAttrFromEntry(OU_GROUP_1_DN, "member",LEVEL_1_USER_DN,
+                                LEVEL_1_USER_DN, "pa$$word",  true);
+          } catch(Throwable e) {
+                throw e;
+          }
+  }
+
+  /**
   * Test group and role bind rule ACI keywords. Both groupdn and roledn keywords
   * funnel through the same code so the results should be the same.
   * @throws Throwable
@@ -1919,15 +2056,43 @@
         deleteEntries(ALL_TEST_ENTRY_DNS_BOTTOM_UP);
     }
 
-    private void deleteAttrFromEntry(String dn, String attr, boolean errorOk) throws Exception {
+  private void deleteAttrFromEntry(String dn, String attr, String val,
+                                   String bindDN, String pwd,
+                                   boolean errorOk) throws Exception {
         StringBuilder ldif = new StringBuilder();
         ldif.append(TestCaseUtils.makeLdif(
                 "dn: "  + dn,
                 "changetype: modify",
-                "delete: " + attr));
-        modEntries(ldif.toString(), DIR_MGR_DN, DIR_MGR_PW, errorOk, false);
+                "delete: " + attr,
+                attr + ":" + val));
+        modEntries(ldif.toString(), bindDN, pwd, errorOk, false);
     }
 
+
+  private static String makeModDN(String dn, String newRDN, String deleteOldRDN,
+                                  String newSuperior ) throws Exception {
+    StringBuilder ldif = new StringBuilder();
+    ldif.append("dn: " + dn).append(EOL);
+    ldif.append("changetype: modrdn").append(EOL);
+    ldif.append("newrdn: " + newRDN).append(EOL);
+    ldif.append("deleteoldrdn: " + deleteOldRDN).append(EOL);
+    if(newSuperior != null)
+       ldif.append("newsuperior: " + newSuperior).append(EOL);
+    ldif.append(EOL);
+    return ldif.toString();
+  }
+
+
+
+      private void deleteAttrFromEntry(String dn, String attr, boolean errorOk) throws Exception {
+          StringBuilder ldif = new StringBuilder();
+          ldif.append(TestCaseUtils.makeLdif(
+                  "dn: "  + dn,
+                  "changetype: modify",
+                  "delete: " + attr));
+          modEntries(ldif.toString(), DIR_MGR_DN, DIR_MGR_PW, errorOk, false);
+      }
+
     private void deleteEntries(String[] entries) throws Exception {
         // TODO: make this actually do a search first!
         StringBuilder ldif = new StringBuilder();
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
index 8873dc4..e205b42 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
@@ -147,6 +147,7 @@
          "sn: User",
          "cn: Proxy User",
          "userPassword: password",
+         "ds-privilege-name: bypass-acl",
          "ds-privilege-name: proxied-auth");
 
     Entry proxyUserEntry =
@@ -1116,7 +1117,19 @@
     ASN1Reader r = new ASN1Reader(s);
     ASN1Writer w = new ASN1Writer(s);
     r.setIOTimeout(6000);
+    BindRequestProtocolOp bindRequest =
+              new BindRequestProtocolOp(
+                      new ASN1OctetString("cn=Directory Manager"),
+                      3, new ASN1OctetString("password"));
+    LDAPMessage bindMessage = new LDAPMessage(1, bindRequest);
+    w.writeElement(bindMessage.encode());
 
+    bindMessage = LDAPMessage.decode(r.readElement().decodeAsSequence());
+    BindResponseProtocolOp bindResponse = bindMessage.getBindResponseProtocolOp();
+    assertEquals(bindResponse.getResultCode(), LDAPResultCode.SUCCESS);
+
+    assertTrue(DirectoryServer.getWorkQueue().waitUntilIdle(10000)); 
+    InvocationCounterPlugin.resetAllCounters();
     ModifyDNRequestProtocolOp modifyRequest =
         new ModifyDNRequestProtocolOp(
             new ASN1OctetString(entry.getDN().toString()),

--
Gitblit v1.10.0