From 9376e1bcaf90a83599c4102222b919dfd6526a91 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Fri, 17 Sep 2010 22:21:02 +0000
Subject: [PATCH] More fixes to the sub-entry security model: add new subentry-write privilege; rename inheritFromBaseDN to inheritFromBaseRDN and restrict it to the root entry of the subentry scope; restrict DNs derived from inheritFromDNAttribute to the root entry of the subentry scope; remove band-aid subentry write access global ACI.

---
 opends/src/messages/messages/core.properties                                                        |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java |    3 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java       |    6 
 opends/src/server/org/opends/server/types/Entry.java                                                |    6 +
 opends/src/server/org/opends/server/core/RootPrivilegeChangeListener.java                           |    3 
 opends/src/server/org/opends/server/types/Privilege.java                                            |   13 ++
 opends/src/server/org/opends/server/core/SubentryManager.java                                       |   55 +++++++++++
 opends/resource/config/config.ldif                                                                  |    2 
 opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml                           |    6 +
 opends/src/server/org/opends/server/types/SubEntry.java                                             |   17 ++-
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java            |  160 +++++++++++++++++++++++++++++++
 opends/src/admin/defn/org/opends/server/admin/std/RootDNConfiguration.xml                           |    7 +
 opends/src/ads/org/opends/admin/ads/ADSContext.java                                                 |    1 
 opends/resource/schema/00-core.ldif                                                                 |    4 
 opends/src/admin/messages/GlobalCfgDefn.properties                                                  |    1 
 opends/src/admin/messages/RootDNCfgDefn.properties                                                  |    1 
 opends/src/server/org/opends/server/core/CoreConfigManager.java                                     |    3 
 17 files changed, 275 insertions(+), 15 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index e820ba1..e7c1fd2 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -82,7 +82,6 @@
 ds-cfg-global-aci: (targetattr="createTimestamp||creatorsName||modifiersName||modifyTimestamp||entryDN||entryUUID||subschemaSubentry")(version 3.0; acl "User-Visible Operational Attributes"; allow (read,search,compare) userdn="ldap:///anyone";)
 ds-cfg-global-aci: (target="ldap:///dc=replicationchanges")(targetattr="*")(version 3.0; acl "Replication backend access"; deny (all) userdn="ldap:///anyone";)
 ds-cfg-global-aci: (target="ldap:///cn=changelog")(targetattr="*")(version 3.0; acl "External changelog access"; deny (all) userdn="ldap:///anyone";)
-ds-cfg-global-aci: (targetfilter="(|(objectclass=subentry)(objectclass=ldapsubentry))")(version 3.0; acl "Subentry write access"; deny (add,write,delete) userdn="ldap:///anyone";)
 cn: Access Control Handler
 ds-cfg-java-class: org.opends.server.authorization.dseecompat.AciHandler
 ds-cfg-enabled: true
@@ -1880,6 +1879,7 @@
 ds-cfg-default-root-privilege-name: update-schema
 ds-cfg-default-root-privilege-name: privilege-change
 ds-cfg-default-root-privilege-name: unindexed-search
+ds-cfg-default-root-privilege-name: subentry-write
 
 dn: cn=Directory Manager,cn=Root DNs,cn=config
 objectClass: top
diff --git a/opends/resource/schema/00-core.ldif b/opends/resource/schema/00-core.ldif
index 0ab7dc0..342bb0c 100644
--- a/opends/resource/schema/00-core.ldif
+++ b/opends/resource/schema/00-core.ldif
@@ -402,7 +402,7 @@
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.621 NAME 'inheritFromDNAttribute'
   EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
   SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
-attributeTypes: ( 1.3.6.1.4.1.26027.1.1.622 NAME 'inheritFromBaseDN'
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.622 NAME 'inheritFromBaseRDN'
   EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
   SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.623 NAME 'inheritFromRDNType'
@@ -689,7 +689,7 @@
   'inheritedFromRDNCollectiveAttributeSubentry'
   DESC 'Inherited from RDN Collective Attributes Subentry class'
   SUP inheritedCollectiveAttributeSubentry STRUCTURAL
-  MUST ( inheritFromRDNAttribute $ inheritFromRDNType $ inheritFromBaseDN )
+  MUST ( inheritFromRDNAttribute $ inheritFromRDNType $ inheritFromBaseRDN )
   X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.33 NAME 'groupOfURLs'
   DESC 'Sun-defined objectclass' SUP top STRUCTURAL MUST ( cn )
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
index 20d24d7..c5d7c3c 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
+++ b/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
@@ -670,6 +670,12 @@
             that cannot be optimized using server indexes.
           </adm:synopsis>
         </adm:value>
+        <adm:value name="subentry-write">
+          <adm:synopsis>
+            Allows the associated user to perform LDAP subentry write
+            operations.
+          </adm:synopsis>
+        </adm:value>
       </adm:enumeration>
     </adm:syntax>
     <adm:profile name="ldap">
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/RootDNConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/RootDNConfiguration.xml
index e493841..42ea75c 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/RootDNConfiguration.xml
+++ b/opends/src/admin/defn/org/opends/server/admin/std/RootDNConfiguration.xml
@@ -76,6 +76,7 @@
         <adm:value>update-schema</adm:value>
         <adm:value>privilege-change</adm:value>
         <adm:value>unindexed-search</adm:value>
+        <adm:value>subentry-write</adm:value>
       </adm:defined>
     </adm:default-behavior>
     <adm:syntax>
@@ -210,6 +211,12 @@
             that cannot be optimized using server indexes.
           </adm:synopsis>
         </adm:value>
+        <adm:value name="subentry-write">
+          <adm:synopsis>
+            Allows the associated user to perform LDAP subentry write
+            operations.
+          </adm:synopsis>
+        </adm:value>
       </adm:enumeration>
     </adm:syntax>
     <adm:profile name="ldap">
diff --git a/opends/src/admin/messages/GlobalCfgDefn.properties b/opends/src/admin/messages/GlobalCfgDefn.properties
index 6e81ff5..2dd3d75 100644
--- a/opends/src/admin/messages/GlobalCfgDefn.properties
+++ b/opends/src/admin/messages/GlobalCfgDefn.properties
@@ -35,6 +35,7 @@
 property.disabled-privilege.syntax.enumeration.value.server-lockdown.synopsis=Allows the user to place and bring the server of lockdown mode.
 property.disabled-privilege.syntax.enumeration.value.server-restart.synopsis=Allows the user to request that the server perform an in-core restart.
 property.disabled-privilege.syntax.enumeration.value.server-shutdown.synopsis=Allows the user to request that the server shut down.
+property.disabled-privilege.syntax.enumeration.value.subentry-write.synopsis=Allows the associated user to perform LDAP subentry write operations.
 property.disabled-privilege.syntax.enumeration.value.unindexed-search.synopsis=Allows the user to request that the server process a search that cannot be optimized using server indexes.
 property.disabled-privilege.syntax.enumeration.value.update-schema.synopsis=Allows the user to make changes to the server schema.
 property.entry-cache-preload.synopsis=Indicates whether or not to preload the entry cache on startup.
diff --git a/opends/src/admin/messages/RootDNCfgDefn.properties b/opends/src/admin/messages/RootDNCfgDefn.properties
index ad0cb6f..097758f 100644
--- a/opends/src/admin/messages/RootDNCfgDefn.properties
+++ b/opends/src/admin/messages/RootDNCfgDefn.properties
@@ -23,6 +23,7 @@
 property.default-root-privilege-name.syntax.enumeration.value.server-lockdown.synopsis=Allows the user to place and bring the server of lockdown mode.
 property.default-root-privilege-name.syntax.enumeration.value.server-restart.synopsis=Allows the user to request that the server perform an in-core restart.
 property.default-root-privilege-name.syntax.enumeration.value.server-shutdown.synopsis=Allows the user to request that the server shut down.
+property.default-root-privilege-name.syntax.enumeration.value.subentry-write.synopsis=Allows the associated user to perform LDAP subentry write operations.
 property.default-root-privilege-name.syntax.enumeration.value.unindexed-search.synopsis=Allows the user to request that the server process a search that cannot be optimized using server indexes.
 property.default-root-privilege-name.syntax.enumeration.value.update-schema.synopsis=Allows the user to make changes to the server schema.
 relation.root-dn-user.user-friendly-name=Root DN User
diff --git a/opends/src/ads/org/opends/admin/ads/ADSContext.java b/opends/src/ads/org/opends/admin/ads/ADSContext.java
index 1f3634f..fd93d9f 100644
--- a/opends/src/ads/org/opends/admin/ads/ADSContext.java
+++ b/opends/src/ads/org/opends/admin/ads/ADSContext.java
@@ -1695,6 +1695,7 @@
     privilege.add("update-schema");
     privilege.add("privilege-change");
     privilege.add("unindexed-search");
+    privilege.add("subentry-write");
     return privilege;
   }
 
diff --git a/opends/src/messages/messages/core.properties b/opends/src/messages/messages/core.properties
index 55ed59a..e8df13c 100644
--- a/opends/src/messages/messages/core.properties
+++ b/opends/src/messages/messages/core.properties
@@ -1846,3 +1846,5 @@
  operations each %d ms
 SEVERE_ERR_MAX_OPS_PER_INTERVAL_738=The value "%d" is not a valid value for \
 the maximum number of operations per interval (must be positive)
+MILD_ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES_739=This operation involves \
+ LDAP subentries which you do not have sufficient privileges to administer
diff --git a/opends/src/server/org/opends/server/core/CoreConfigManager.java b/opends/src/server/org/opends/server/core/CoreConfigManager.java
index 81b1ecf..51b9b65 100644
--- a/opends/src/server/org/opends/server/core/CoreConfigManager.java
+++ b/opends/src/server/org/opends/server/core/CoreConfigManager.java
@@ -326,6 +326,9 @@
           case UPDATE_SCHEMA:
             disabledPrivileges.add(Privilege.UPDATE_SCHEMA);
             break;
+          case SUBENTRY_WRITE:
+            disabledPrivileges.add(Privilege.SUBENTRY_WRITE);
+            break;
         }
       }
     }
diff --git a/opends/src/server/org/opends/server/core/RootPrivilegeChangeListener.java b/opends/src/server/org/opends/server/core/RootPrivilegeChangeListener.java
index 6a51e37..8387cb7 100644
--- a/opends/src/server/org/opends/server/core/RootPrivilegeChangeListener.java
+++ b/opends/src/server/org/opends/server/core/RootPrivilegeChangeListener.java
@@ -188,6 +188,9 @@
         case UNINDEXED_SEARCH:
           privSet.add(Privilege.UNINDEXED_SEARCH);
           break;
+        case SUBENTRY_WRITE:
+          privSet.add(Privilege.SUBENTRY_WRITE);
+          break;
       }
     }
 
diff --git a/opends/src/server/org/opends/server/core/SubentryManager.java b/opends/src/server/org/opends/server/core/SubentryManager.java
index adfa86d..b94f46a 100644
--- a/opends/src/server/org/opends/server/core/SubentryManager.java
+++ b/opends/src/server/org/opends/server/core/SubentryManager.java
@@ -28,6 +28,7 @@
 
 
 
+import org.opends.server.api.ClientConnection;
 import org.opends.server.api.SubtreeSpecification;
 import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -52,6 +53,8 @@
 import org.opends.server.types.DN;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Entry;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchScope;
 import org.opends.server.types.SearchFilter;
@@ -944,6 +947,15 @@
 
     if (entry.isSubentry() || entry.isLDAPSubentry())
     {
+      ClientConnection conn = addOperation.getClientConnection();
+      if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
+           conn.getOperationInProgress(
+             addOperation.getMessageID())))
+      {
+        return PluginResult.PreOperation.stopProcessing(
+                ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
+      }
       for (SubentryChangeListener changeListener :
               changeListeners)
       {
@@ -975,12 +987,29 @@
           PreOperationDeleteOperation deleteOperation)
   {
     Entry entry = deleteOperation.getEntryToDelete();
+    boolean hasSubentryWritePrivilege = false;
 
     lock.readLock().lock();
     try
     {
       for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getDN()))
       {
+        if (!hasSubentryWritePrivilege)
+        {
+          ClientConnection conn = deleteOperation.getClientConnection();
+          if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
+               conn.getOperationInProgress(
+                 deleteOperation.getMessageID())))
+          {
+            return PluginResult.PreOperation.stopProcessing(
+                    ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                    ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
+          }
+          else
+          {
+            hasSubentryWritePrivilege = true;
+          }
+        }
         for (SubentryChangeListener changeListener :
                 changeListeners)
         {
@@ -1023,6 +1052,15 @@
     if ((newEntry.isSubentry() || newEntry.isLDAPSubentry()) ||
         (oldEntry.isSubentry() || oldEntry.isLDAPSubentry()))
     {
+      ClientConnection conn = modifyOperation.getClientConnection();
+      if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
+           conn.getOperationInProgress(
+             modifyOperation.getMessageID())))
+      {
+        return PluginResult.PreOperation.stopProcessing(
+                ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
+      }
       for (SubentryChangeListener changeListener :
               changeListeners)
       {
@@ -1058,6 +1096,7 @@
     Entry newEntry = modifyDNOperation.getUpdatedEntry();
     String oldDNString = oldEntry.getDN().toNormalizedString();
     String newDNString = newEntry.getDN().toNormalizedString();
+    boolean hasSubentryWritePrivilege = false;
 
     lock.readLock().lock();
     try
@@ -1066,6 +1105,22 @@
               dit2SubEntry.getSubtree(oldEntry.getDN());
       for (SubEntry subentry : setToDelete)
       {
+        if (!hasSubentryWritePrivilege)
+        {
+          ClientConnection conn = modifyDNOperation.getClientConnection();
+          if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
+               conn.getOperationInProgress(
+                 modifyDNOperation.getMessageID())))
+          {
+            return PluginResult.PreOperation.stopProcessing(
+                    ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                    ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
+          }
+          else
+          {
+            hasSubentryWritePrivilege = true;
+          }
+        }
         oldEntry = subentry.getEntry();
         try
         {
diff --git a/opends/src/server/org/opends/server/types/Entry.java b/opends/src/server/org/opends/server/types/Entry.java
index fd37d33..26016d5 100644
--- a/opends/src/server/org/opends/server/types/Entry.java
+++ b/opends/src/server/org/opends/server/types/Entry.java
@@ -3707,6 +3707,12 @@
                 {
                   inheritFromDN = DN.decode(
                           value.getNormalizedValue());
+                  // Respect subentry root scope.
+                  if (!inheritFromDN.isDescendantOf(
+                       subEntry.getDN().getParent()))
+                  {
+                    inheritFromDN = null;
+                  }
                   break;
                 }
               }
diff --git a/opends/src/server/org/opends/server/types/Privilege.java b/opends/src/server/org/opends/server/types/Privilege.java
index 9386ec5..7608b98 100644
--- a/opends/src/server/org/opends/server/types/Privilege.java
+++ b/opends/src/server/org/opends/server/types/Privilege.java
@@ -226,7 +226,15 @@
    * The privilege that provides the ability to perform an unindexed
    * search in the JE backend.
    */
-  UNINDEXED_SEARCH("unindexed-search");
+  UNINDEXED_SEARCH("unindexed-search"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform write
+   * operations on LDAP subentries.
+   */
+  SUBENTRY_WRITE("subentry-write");
 
 
 
@@ -287,6 +295,7 @@
     PRIV_MAP.put("update-schema", UPDATE_SCHEMA);
     PRIV_MAP.put("privilege-change", PRIVILEGE_CHANGE);
     PRIV_MAP.put("unindexed-search", UNINDEXED_SEARCH);
+    PRIV_MAP.put("subentry-write", SUBENTRY_WRITE);
 
     PRIV_NAMES.add("bypass-acl");
     PRIV_NAMES.add("bypass-lockdown");
@@ -311,6 +320,7 @@
     PRIV_NAMES.add("update-schema");
     PRIV_NAMES.add("privilege-change");
     PRIV_NAMES.add("unindexed-search");
+    PRIV_NAMES.add("subentry-write");
 
     DEFAULT_ROOT_PRIV_SET.add(BYPASS_ACL);
     DEFAULT_ROOT_PRIV_SET.add(BYPASS_LOCKDOWN);
@@ -330,6 +340,7 @@
     DEFAULT_ROOT_PRIV_SET.add(UPDATE_SCHEMA);
     DEFAULT_ROOT_PRIV_SET.add(PRIVILEGE_CHANGE);
     DEFAULT_ROOT_PRIV_SET.add(UNINDEXED_SEARCH);
+    DEFAULT_ROOT_PRIV_SET.add(SUBENTRY_WRITE);
   }
 
 
diff --git a/opends/src/server/org/opends/server/types/SubEntry.java b/opends/src/server/org/opends/server/types/SubEntry.java
index 4e529b4..3c6a344 100644
--- a/opends/src/server/org/opends/server/types/SubEntry.java
+++ b/opends/src/server/org/opends/server/types/SubEntry.java
@@ -131,11 +131,11 @@
           "inheritfromrdntype";
 
   /**
-   * The name of the "inheritFromBaseDN" attribute type,
+   * The name of the "inheritFromBaseRDN" attribute type,
    * formatted in all lowercase characters.
    */
   public static final String ATTR_INHERIT_COLLECTIVE_FROM_BASE =
-          "inheritfrombasedn";
+          "inheritfrombaserdn";
 
   /**
    * The name of the "inheritAttribute" attribute type,
@@ -390,6 +390,11 @@
             {
               this.inheritFromBaseDN =
                       DN.decode(value.getNormalizedValue());
+              // Has to have a parent since subentry itself
+              // cannot be a suffix entry within the server.
+              this.inheritFromBaseDN =
+                      getDN().getParent().concat(
+                        inheritFromBaseDN);
               break;
             }
           }
@@ -442,7 +447,7 @@
    * Retrieves the distinguished name for this subentry.
    * @return  The distinguished name for this subentry.
    */
-  public DN getDN()
+  public final DN getDN()
   {
     return this.entry.getDN();
   }
@@ -452,7 +457,7 @@
    * for this subentry.
    * @return entry object for this subentry.
    */
-  public Entry getEntry()
+  public final Entry getEntry()
   {
     return this.entry;
   }
@@ -562,9 +567,9 @@
   }
 
   /**
-   * Getter to retrieve inheritFromBaseDN DN
+   * Getter to retrieve inheritFromBaseRDN DN
    * for inherited collective attribute subentry.
-   * @return DN of inheritFromBaseDN or,
+   * @return DN of inheritFromBaseRDN or,
    *         <code>null</code> if there is none.
    */
   public DN getInheritFromBaseDN()
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java
index 5cbe963..d75ee68 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java
@@ -64,8 +64,8 @@
 public class SubentryManagerTestCase extends CoreTestCase
 {
   private static final String SUFFIX = "dc=example,dc=com";
-  private static final String BASE =
-          "ou=Test SubEntry Manager," + SUFFIX;
+  private static final String BASE_RDN = "ou=Test SubEntry Manager";
+  private static final String BASE = BASE_RDN + "," + SUFFIX;
 
   private Entry testEntry;
   private Entry ldapSubentry;
@@ -192,7 +192,7 @@
          "objectclass: subentry",
          "objectClass: inheritedCollectiveAttributeSubentry",
          "objectClass: inheritedFromRDNCollectiveAttributeSubentry",
-         "inheritFromBaseDN: " + BASE,
+         "inheritFromBaseRDN: " + BASE_RDN,
          "inheritFromRDNAttribute: title",
          "inheritFromRDNType: cn",
          "inheritAttribute: telephoneNumber",
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
index 059d2bc..7f3bf87 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2008 Sun Microsystems, Inc.
+ *      Copyright 2008-2010 Sun Microsystems, Inc.
  */
 package org.opends.server.protocols.jmx;
 
@@ -180,6 +180,7 @@
       "ds-privilege-name: unindexed-search",
       "ds-privilege-name: jmx-read",
       "ds-privilege-name: jmx-write",
+      "ds-privilege-name: subentry-write",
       "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
            "cn=Password Policies,cn=config",
       "",
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
index 32b9aba..0921a0c 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2007-2009 Sun Microsystems, Inc.
+ *      Copyright 2007-2010 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
@@ -141,6 +141,7 @@
       "ds-privilege-name: -backend-backup",
       "ds-privilege-name: -backend-restore",
       "ds-privilege-name: -unindexed-search",
+      "ds-privilege-name: -subentry-write",
       "",
       "dn: cn=Proxy Root,cn=Root DNs,cn=config",
       "objectClass: top",
@@ -176,6 +177,7 @@
       "ds-privilege-name: proxied-auth",
       "ds-privilege-name: bypass-acl",
       "ds-privilege-name: unindexed-search",
+      "ds-privilege-name: subentry-write",
       "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
            "cn=Password Policies,cn=config",
       "",
@@ -193,6 +195,15 @@
       "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
            "cn=Password Policies,cn=config",
       "",
+      "dn: cn=Subentry Target,o=test",
+      "objectClass: top",
+      "objectClass: subentry",
+      "objectClass: collectiveAttributeSubentry",
+      "objectClass: extensibleObject",
+      "cn: Subentry Target",
+      "l;collective: Test",
+      "subtreeSpecification: {}",
+      "",
       "dn: cn=PWReset Target,o=test",
       "objectClass: top",
       "objectClass: person",
@@ -633,6 +644,153 @@
 
 
   /**
+   * Tests to ensure that add and delete operations
+   * properly respect the SUBENTRY_WRITE privilege.
+   *
+   * @param  conn          The client connection to use to perform the
+   *                       operations.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the SUBENTRY_WRITE privilege and therefore
+   *                       the operations should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testSubentryWriteAddAndDelete(InternalClientConnection conn,
+                                          boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.SUBENTRY_WRITE, null),
+            hasPrivilege);
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: cn=Test Subentry,o=test",
+      "objectClass: top",
+      "objectClass: subentry",
+      "objectClass: collectiveAttributeSubentry",
+      "objectClass: extensibleObject",
+      "cn: Test Subentry",
+      "l;collective: Test",
+      "subtreeSpecification: {}");
+
+    AddOperation addOperation =
+         conn.processAdd(entry.getDN(), entry.getObjectClasses(),
+                         entry.getUserAttributes(),
+                         entry.getOperationalAttributes());
+    if (hasPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+      DeleteOperation deleteOperation = conn.processDelete(entry.getDN());
+      assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+      DeleteOperation deleteOperation =
+           conn.processDelete(
+                DN.decode("cn=Subentry Target,o=test"));
+      assertEquals(deleteOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that modify operations properly respect
+   * the SUBENTRY_WRITE privilege.
+   *
+   * @param  conn          The client connection to use to perform the modify
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the SUBENTRY_WRITE privilege and therefore
+   *                       the modify should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testSubentryWriteModify(InternalClientConnection conn,
+                                    boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.SUBENTRY_WRITE, null),
+            hasPrivilege);
+
+    ArrayList<Modification> mods = new ArrayList<Modification>();
+
+    mods.add(new Modification(ModificationType.REPLACE,
+                              Attributes.create("subtreeSpecification",
+                              "{base \"ou=doesnotexist\"}")));
+
+    ModifyOperation modifyOperation =
+         conn.processModify(DN.decode("cn=Subentry Target,o=test"), mods);
+    if (hasPrivilege)
+    {
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+      mods.clear();
+      mods.add(new Modification(ModificationType.REPLACE,
+          Attributes.create("subtreeSpecification", "{}")));
+
+      modifyOperation = conn.processModify(
+              DN.decode("cn=Subentry Target,o=test"), mods);
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(modifyOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that modify DN operations
+   * properly respect the SUBENTRY_WRITE privilege.
+   *
+   * @param  conn          The client connection to use to perform the modify DN
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the SUBENTRY_WRITE privilege and therefore
+   *                       the modify DN should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testSubentryWriteModifyDN(InternalClientConnection conn,
+                                      boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.SUBENTRY_WRITE, null),
+            hasPrivilege);
+
+    ModifyDNOperation modifyDNOperation =
+         conn.processModifyDN(DN.decode("cn=Subentry Target,o=test"),
+                              RDN.decode("cn=New Subentry Target"),
+                              true, null);
+    if (hasPrivilege)
+    {
+      assertEquals(modifyDNOperation.getResultCode(),
+                   ResultCode.SUCCESS);
+      modifyDNOperation =
+         conn.processModifyDN(DN.decode("cn=New Subentry Target,o=test"),
+                              RDN.decode("cn=Subentry Target"),
+                              true, null);
+    }
+    else
+    {
+      assertEquals(modifyDNOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
    * Tests to ensure that modify operations which attempt to reset a user's
    * password properly respect the PASSWORD_RESET privilege.
    *

--
Gitblit v1.10.0