mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
18.21.2010 9376e1bcaf90a83599c4102222b919dfd6526a91
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.
17 files modified
290 ■■■■■ changed files
opends/resource/config/config.ldif 2 ●●● patch | view | raw | blame | history
opends/resource/schema/00-core.ldif 4 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml 6 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/RootDNConfiguration.xml 7 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/GlobalCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/admin/messages/RootDNCfgDefn.properties 1 ●●●● patch | view | raw | blame | history
opends/src/ads/org/opends/admin/ads/ADSContext.java 1 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/core.properties 2 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CoreConfigManager.java 3 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/RootPrivilegeChangeListener.java 3 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SubentryManager.java 55 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Entry.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Privilege.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/SubEntry.java 17 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryManagerTestCase.java 6 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java 3 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java 160 ●●●●● patch | view | raw | blame | history
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
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 )
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">
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">
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.
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
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;
  }
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
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;
        }
      }
    }
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;
      }
    }
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
        {
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;
                }
              }
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);
  }
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()
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",
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",
      "",
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.
   *