From ec465f362ea0c7b6e0c0905748bcdfde1b859d0f Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Fri, 09 Feb 2007 21:51:09 +0000
Subject: [PATCH] Add an initial set of privilege support to OpenDS.  The current privileges are currently defined and implemented: * config-read (allow reading the configuration) * config-write (allow updating the configuration) * ldif-import (allow invoking LDIF import tasks) * ldif-export (allow invoking LDIF export tasks) * backend-backup (allow invoking backup tasks) * backend-restore (allow invoking restore tasks) * server-shutdown (allow invoking server shutdown tasks) * server-restart (allow invoking server restart tasks) * server-restart (allow invoking server restart tasks) * password-reset (allow resetting user passwords) * update-schema (allow updating the server schema) * privilege-change (allow changing the set of privileges for a user)

---
 opendj-sdk/opends/src/server/org/opends/server/tasks/ImportTask.java                                   |   22 
 opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java         |   33 
 opendj-sdk/opends/src/server/org/opends/server/tasks/ExportTask.java                                   |   22 
 opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java                          |    2 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java                               |   29 
 opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java                             |   17 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java    | 1166 +++++++++++++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java                             |   16 
 opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java                              |   86 +
 opendj-sdk/opends/resource/config/config.ldif                                                          |   18 
 opendj-sdk/opends/src/server/org/opends/server/types/Privilege.java                                    |  389 +++++++
 opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java                        |   15 
 opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java                       |  116 ++
 opendj-sdk/opends/src/server/org/opends/server/core/RootDNConfigManager.java                           |  312 ++++++
 opendj-sdk/opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java                            |   20 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java |    2 
 opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java                        |   11 
 opendj-sdk/opends/src/server/org/opends/server/tools/LDAPPasswordModify.java                           |   12 
 opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java                               |  238 ++++
 opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java                               |   23 
 opendj-sdk/opends/src/server/org/opends/server/messages/TaskMessages.java                              |   99 +
 opendj-sdk/opends/src/server/org/opends/server/tasks/RestoreTask.java                                  |   22 
 opendj-sdk/opends/src/server/org/opends/server/tasks/ShutdownTask.java                                 |   41 
 opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java                                  |   15 
 opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java                              |   15 
 opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java                            |  140 ++
 opendj-sdk/opends/src/server/org/opends/server/tasks/BackupTask.java                                   |   22 
 opendj-sdk/opends/src/server/org/opends/server/backends/task/Task.java                                 |   38 
 opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java        |    2 
 opendj-sdk/opends/resource/schema/02-config.ldif                                                       |    9 
 opendj-sdk/opends/src/server/org/opends/server/messages/BackendMessages.java                           |   13 
 31 files changed, 2,927 insertions(+), 38 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index 2c730b0..d74958c 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -1028,8 +1028,24 @@
 
 dn: cn=Root DNs,cn=config
 objectClass: top
-objectClass: ds-cfg-branch
+objectClass: ds-cfg-root-dn-base
 cn: Root DNs
+ds-cfg-default-root-privilege-name: bypass-acl
+ds-cfg-default-root-privilege-name: modify-acl
+ds-cfg-default-root-privilege-name: config-read
+ds-cfg-default-root-privilege-name: config-write
+ds-cfg-default-root-privilege-name: ldif-import
+ds-cfg-default-root-privilege-name: ldif-export
+ds-cfg-default-root-privilege-name: backend-backup
+ds-cfg-default-root-privilege-name: backend-restore
+ds-cfg-default-root-privilege-name: server-shutdown
+ds-cfg-default-root-privilege-name: server-restart
+ds-cfg-default-root-privilege-name: disconnect-client
+ds-cfg-default-root-privilege-name: cancel-request
+ds-cfg-default-root-privilege-name: search-unindexed
+ds-cfg-default-root-privilege-name: password-reset
+ds-cfg-default-root-privilege-name: update-schema
+ds-cfg-default-root-privilege-name: privilege-change
 
 dn: cn=Directory Manager,cn=Root DNs,cn=config
 objectClass: top
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index b7605d5..9a1ca7b 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/opends/resource/schema/02-config.ldif
@@ -1042,6 +1042,12 @@
   NAME 'ds-cfg-heartbeat-interval'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
   X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.307
+  NAME 'ds-privilege-name' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  USAGE directoryOperation X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.308
+  NAME 'ds-cfg-default-root-privilege-name'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
   NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
   MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1429,4 +1435,7 @@
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.81 NAME 'ds-cfg-group-implementation'
   SUP top STRUCTURAL MUST ( cn $ ds-cfg-group-implementation-class $
   ds-cfg-group-implementation-enabled ) X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.82 NAME 'ds-cfg-root-dn-base' SUP top
+  STRUCTURAL MUST cn MAY ds-cfg-default-root-privilege-name
+  X-ORIGIN 'OpenDS Directory Server' )
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
index c113d31..48a98ea 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
@@ -32,6 +32,7 @@
 import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -41,6 +42,9 @@
 import org.opends.server.core.PersistentSearch;
 import org.opends.server.core.PluginConfigManager;
 import org.opends.server.core.SearchOperation;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
@@ -48,13 +52,20 @@
 import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.IntermediateResponse;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
 import org.opends.server.util.TimeThread;
 
+import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.loggers.Debug.*;
+import static org.opends.server.loggers.Error.*;
+import static org.opends.server.messages.CoreMessages.*;
 import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.util.StaticUtils.*;
 
 
 
@@ -84,6 +95,9 @@
   // for this client connection.
   private boolean finalized;
 
+  // The set of privileges assigned to this client connection.
+  private HashSet<Privilege> privileges;
+
   // The size limit for use with this client connection.
   private int sizeLimit;
 
@@ -127,6 +141,7 @@
     timeLimit          = DirectoryServer.getTimeLimit();
     lookthroughLimit   = DirectoryServer.getLookthroughLimit();
     finalized          = false;
+    privileges         = new HashSet<Privilege>();
   }
 
 
@@ -846,6 +861,7 @@
     if (authenticationInfo == null)
     {
       this.authenticationInfo = new AuthenticationInfo();
+      privileges = new HashSet<Privilege>();
     }
     else
     {
@@ -869,11 +885,18 @@
           DirectoryServer.getAuthenticatedUsers().put(
                authZEntry.getDN(), this);
         }
+
+        updatePrivileges(authNEntry, authenticationInfo.isRoot());
       }
-      else if (authZEntry != null)
+      else
       {
-        DirectoryServer.getAuthenticatedUsers().put(
-             authZEntry.getDN(), this);
+        if (authZEntry != null)
+        {
+          DirectoryServer.getAuthenticatedUsers().put(
+               authZEntry.getDN(), this);
+        }
+
+        privileges = new HashSet<Privilege>();
       }
     }
   }
@@ -908,11 +931,13 @@
       {
         authenticationInfo =
              authenticationInfo.duplicate(newEntry, authZEntry);
+        updatePrivileges(newEntry, authenticationInfo.isRoot());
       }
       else
       {
         authenticationInfo =
              authenticationInfo.duplicate(newEntry, newEntry);
+        updatePrivileges(newEntry, authenticationInfo.isRoot());
       }
     }
     else if ((authZEntry != null) &&
@@ -943,6 +968,213 @@
 
 
   /**
+   * Indicates whether the authenticated client has the specified
+   * privilege.
+   *
+   * @param  privilege  The privilege for which to make the
+   *                    determination.
+   * @param  operation  The operation being processed which needs to
+   *                    make the privilege determination, or
+   *                    {@code null} if there is no associated
+   *                    operation.
+   *
+   * @return  {@code true} if the authenticated client has the
+   *          specified privilege, or {@code false} if not.
+   */
+  public boolean hasPrivilege(Privilege privilege,
+                              Operation operation)
+  {
+    assert debugEnter(CLASS_NAME, "hasPrivilege",
+                      String.valueOf(privilege),
+                      String.valueOf(operation));
+
+    boolean result = privileges.contains(privilege);
+    if (operation == null)
+    {
+      DN authDN = authenticationInfo.getAuthenticationDN();
+
+      int    msgID   = MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGE;
+      String message = getMessage(msgID, getConnectionID(), -1L,
+                                  String.valueOf(authDN),
+                                  privilege.getName(), result);
+      logError(ErrorLogCategory.ACCESS_CONTROL,
+               ErrorLogSeverity.INFORMATIONAL, message, msgID);
+    }
+    else
+    {
+      DN authDN = authenticationInfo.getAuthenticationDN();
+
+      int    msgID   = MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGE;
+      String message = getMessage(msgID, getConnectionID(),
+                                  operation.getOperationID(),
+                                  String.valueOf(authDN),
+                                  privilege.getName(), result);
+      logError(ErrorLogCategory.ACCESS_CONTROL,
+               ErrorLogSeverity.INFORMATIONAL, message, msgID);
+    }
+
+    return result;
+  }
+
+
+
+  /**
+   * Indicates whether the authenticate client has all of the
+   * specified privileges.
+   *
+   * @param  privileges  The array of privileges for which to make the
+   *                     determination.
+   * @param  operation   The operation being processed which needs to
+   *                     make the privilege determination, or
+   *                     {@code null} if there is no associated
+   *                     operation.
+   *
+   * @return  {@code true} if the authenticated client has all of the
+   *          specified privileges, or {@code false} if not.
+   */
+  public boolean hasAllPrivileges(Privilege[] privileges,
+                                  Operation operation)
+  {
+    assert debugEnter(CLASS_NAME, "hasAllPrivileges",
+                      String.valueOf(privileges),
+                      String.valueOf(operation));
+
+    HashSet<Privilege> privSet = this.privileges;
+    boolean result = true;
+    StringBuilder buffer = new StringBuilder();
+    buffer.append("{");
+
+    for (int i=0; i < privileges.length; i++)
+    {
+      if (i > 0)
+      {
+        buffer.append(",");
+      }
+
+      buffer.append(privileges[i].getName());
+
+      if (! privSet.contains(privileges[i]))
+      {
+        result = false;
+      }
+    }
+
+    buffer.append(" }");
+
+    if (operation == null)
+    {
+      DN authDN = authenticationInfo.getAuthenticationDN();
+
+      int    msgID   = MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGES;
+      String message = getMessage(msgID, getConnectionID(), -1L,
+                                  String.valueOf(authDN),
+                                  buffer.toString(), result);
+      logError(ErrorLogCategory.ACCESS_CONTROL,
+               ErrorLogSeverity.INFORMATIONAL, message, msgID);
+    }
+    else
+    {
+      DN authDN = authenticationInfo.getAuthenticationDN();
+
+      int    msgID   = MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGES;
+      String message = getMessage(msgID, getConnectionID(),
+                                  operation.getOperationID(),
+                                  String.valueOf(authDN),
+                                  buffer.toString(), result);
+      logError(ErrorLogCategory.ACCESS_CONTROL,
+               ErrorLogSeverity.INFORMATIONAL, message, msgID);
+    }
+
+    return result;
+  }
+
+
+
+  /**
+   * Updates the privileges associated with this client connection
+   * object based on the provided entry for the authentication
+   * identity.
+   *
+   * @param  entry   The entry for the authentication identity
+   *                 associated with this client connection.
+   * @param  isRoot  Indicates whether the associated user is a root
+   *                 user and should automatically inherit the root
+   *                 privilege set.
+   */
+  private void updatePrivileges(Entry entry, boolean isRoot)
+  {
+    assert debugEnter(CLASS_NAME, "updatePrivileges",
+                      String.valueOf(entry));
+
+    HashSet<Privilege> newPrivileges = new HashSet<Privilege>();
+    HashSet<Privilege> removePrivileges = new HashSet<Privilege>();
+
+    if (isRoot)
+    {
+      newPrivileges.addAll(DirectoryServer.getRootPrivileges());
+    }
+
+    AttributeType privType =
+         DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME);
+    List<Attribute> attrList = entry.getAttribute(privType);
+    if (attrList != null)
+    {
+      for (Attribute a : attrList)
+      {
+        for (AttributeValue v : a.getValues())
+        {
+          String privName = toLowerCase(v.getStringValue());
+
+          // If the name of the privilege is prefixed with a minus
+          // sign, then we will take away that privilege from the
+          // user.  We'll handle that at the end so that we can make
+          // sure it's not added back later.
+          if (privName.startsWith("-"))
+          {
+            privName = privName.substring(1);
+            Privilege p = Privilege.privilegeForName(privName);
+            if (p == null)
+            {
+              // FIXME -- Generate an administrative alert.
+
+              // We don't know what privilege to remove, so we'll
+              // remove all of them.
+              newPrivileges.clear();
+              privileges = newPrivileges;
+              return;
+            }
+            else
+            {
+              removePrivileges.add(p);
+            }
+          }
+          else
+          {
+            Privilege p = Privilege.privilegeForName(privName);
+            if (p == null)
+            {
+              // FIXME -- Generate an administrative alert.
+            }
+            else
+            {
+              newPrivileges.add(p);
+            }
+          }
+        }
+      }
+    }
+
+    for (Privilege p : removePrivileges)
+    {
+      newPrivileges.remove(p);
+    }
+
+    privileges = newPrivileges;
+  }
+
+
+
+  /**
    * Retrieves an opaque set of information that may be used for
    * processing multi-stage SASL binds.
    *
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
index 571c31f..6a4a349 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -57,6 +57,7 @@
 
 import org.opends.server.api.AlertGenerator;
 import org.opends.server.api.Backend;
+import org.opends.server.api.ClientConnection;
 import org.opends.server.api.ConfigurableComponent;
 import org.opends.server.api.MatchingRule;
 import org.opends.server.config.BooleanConfigAttribute;
@@ -103,6 +104,7 @@
 import org.opends.server.types.NameForm;
 import org.opends.server.types.ObjectClass;
 import org.opends.server.types.ObjectClassType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.RDN;
 import org.opends.server.types.RestoreConfig;
 import org.opends.server.types.ResultCode;
@@ -950,6 +952,20 @@
                       String.valueOf(modifyOperation));
 
 
+    // Make sure that the authenticated user has the necessary UPDATE_SCHEMA
+    // privilege.
+    ClientConnection clientConnection = modifyOperation.getClientConnection();
+    if (! clientConnection.hasPrivilege(Privilege.UPDATE_SCHEMA,
+                                        modifyOperation))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_INSUFFICIENT_PRIVILEGES;
+      String message = getMessage(msgID);
+      throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                   message, msgID);
+    }
+
+
+
     // At present, we only allow the addition of new attribute types,
     // object classes, name forms, DIT content rules, DIT structure rules, and
     // matching rule uses.  We will not support removing or replacing existing
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/task/Task.java b/opendj-sdk/opends/src/server/org/opends/server/backends/task/Task.java
index f147227..e465ee4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/task/Task.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/task/Task.java
@@ -40,6 +40,7 @@
 import java.util.concurrent.locks.Lock;
 
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.Operation;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -114,6 +115,9 @@
   // The time that this task was scheduled to start processing.
   private long scheduledStartTime;
 
+  // The operation used to create this task in the server.
+  private Operation operation;
+
   // The ID of the recurring task with which this task is associated.
   private String recurringTaskID;
 
@@ -473,6 +477,40 @@
 
 
   /**
+   * Retrieves the operation used to create this task in the server.  Note that
+   * this will only be available when the task is first added to the scheduler,
+   * and it should only be accessed from within the {@code initializeTask}
+   * method (and even that method should not depend on it always being
+   * available, since it will not be available if the server is restarted and
+   * the task needs to be reinitialized).
+   *
+   * @return  The operation used to create this task in the server, or
+   *          {@code null} if it is not available.
+   */
+  public final Operation getOperation()
+  {
+    assert debugEnter(CLASS_NAME, "getOperation");
+
+    return operation;
+  }
+
+
+
+  /**
+   * Specifies the operation used to create this task in the server.
+   *
+   * @param  operation  The operation used to create this task in the server.
+   */
+  public final void setOperation(Operation operation)
+  {
+    assert debugEnter(CLASS_NAME, "setOperation", String.valueOf(operation));
+
+    this.operation = operation;
+  }
+
+
+
+  /**
    * Retrieves the unique identifier assigned to this task.
    *
    * @return  The unique identifier assigned to this task.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java
index a486838..df42928 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -608,7 +608,7 @@
     // treat the provided entry like a scheduled task.
     if (parentDN.equals(scheduledTaskParentDN))
     {
-      Task task = taskScheduler.entryToScheduledTask(entry);
+      Task task = taskScheduler.entryToScheduledTask(entry, addOperation);
       taskScheduler.scheduleTask(task, true);
       return;
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java b/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
index e23846c..db0c213 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
@@ -43,6 +43,7 @@
 import org.opends.server.api.AlertGenerator;
 import org.opends.server.api.DirectoryThread;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.Operation;
 import org.opends.server.core.SearchOperation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -1044,7 +1045,7 @@
           {
             try
             {
-              Task task = entryToScheduledTask(entry);
+              Task task = entryToScheduledTask(entry, null);
               if (TaskState.isDone(task.getTaskState()))
               {
                 completedTasks.add(task);
@@ -1792,14 +1793,16 @@
    * Decodes the contents of the provided entry as a scheduled task.  The
    * resulting task will not actually be scheduled for processing.
    *
-   * @param  entry  The entry to decode as a scheduled task.
+   * @param  entry      The entry to decode as a scheduled task.
+   * @param  operation  The operation used to create this task in the server, or
+   *                    {@code null} if the operation is not available.
    *
    * @return  The scheduled task decoded from the provided entry.
    *
    * @throws  DirectoryException  If the provided entry cannot be decoded as a
    *                              scheduled task.
    */
-  public Task entryToScheduledTask(Entry entry)
+  public Task entryToScheduledTask(Entry entry, Operation operation)
          throws DirectoryException
   {
     assert debugEnter(CLASS_NAME, "entryToScheduledTask",
@@ -1916,7 +1919,9 @@
     }
 
 
+    task.setOperation(operation);
     task.initializeTask();
+    task.setOperation(null);
     return task;
   }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
index 2891767..ee1f4cc 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -544,6 +544,15 @@
 
 
   /**
+   * The name of the configuration attribute that specifies the set of
+   * privileges that root users should automatically be granted in the server.
+   */
+  public static final String ATTR_DEFAULT_ROOT_PRIVILEGE_NAME =
+       NAME_PREFIX_CFG + "default-root-privilege-name";
+
+
+
+  /**
    * The name of the configuration attribute that indicates which clients
    * should not be allowed to establish connections.
    */
@@ -3149,6 +3158,14 @@
 
 
   /**
+   * The name of the operational attribute that will appear in a user's entry to
+   * specify the set of privileges assigned to that user.
+   */
+  public static final String OP_ATTR_PRIVILEGE_NAME = "ds-privilege-name";
+
+
+
+  /**
    * The name of the operational attribute that will appear in a user's entry
    * to indicate the time that the password was last changed.
    */
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
index 0f3e85b..6d0bd25 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
@@ -76,6 +76,7 @@
 import org.opends.server.types.LockManager;
 import org.opends.server.types.ObjectClass;
 import org.opends.server.types.OperationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
@@ -1397,6 +1398,20 @@
                           operationalAttributes);
 
 
+        // Check to see if the entry includes a privilege specification.  If so,
+        // then the requester must have the PRIVILEGE_CHANGE privilege.
+        AttributeType privType =
+             DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
+        if (entry.hasAttribute(privType) &&
+            (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)))
+        {
+          int msgID = MSGID_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES;
+          appendErrorMessage(getMessage(msgID));
+          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+          break addProcessing;
+        }
+
+
         // Check to see if the entry contains one or more passwords and if they
         // are valid in accordance with the password policies associated with
         // the user.  Also perform any encoding that might be required by
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java
index a811667..221cc91 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java
@@ -57,6 +57,7 @@
 import org.opends.server.types.Entry;
 import org.opends.server.types.LockManager;
 import org.opends.server.types.OperationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.operation.PostOperationCompareOperation;
@@ -651,6 +652,20 @@
       }
 
 
+      // If the target entry is in the server configuration, then make sure the
+      // requester has the CONFIG_READ privilege.
+      if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) &&
+          (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, this)))
+      {
+        int msgID = MSGID_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES;
+        appendErrorMessage(getMessage(msgID));
+        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+        skipPostOperation = true;
+
+        break compareProcessing;
+      }
+
+
       // Check for and handle a request to cancel this operation.
       if (cancelRequest != null)
       {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
index cf95ab6..831cad5 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -146,6 +146,7 @@
 import org.opends.server.types.ObjectClassType;
 import org.opends.server.types.OperatingSystem;
 import org.opends.server.types.OperationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.Schema;
@@ -473,6 +474,9 @@
   // The special backend used for the Directory Server root DSE.
   private RootDSEBackend rootDSEBackend;
 
+  // The root DN config manager for the server.
+  private RootDNConfigManager rootDNConfigManager;
+
   // The SASL mechanism config manager for the Directory Server.
   private SASLConfigManager saslConfigManager;
 
@@ -982,7 +986,8 @@
 
 
       // Initialize the root DNs.
-      new RootDNConfigManager().initializeRootDNs();
+      rootDNConfigManager = new RootDNConfigManager();
+      rootDNConfigManager.initializeRootDNs();
 
 
       // Initialize the group manager.
@@ -5007,6 +5012,22 @@
 
 
   /**
+   * Retrieves the set of privileges that should automatically be granted to
+   * root users when they authenticate.
+   *
+   * @return  The set of privileges that should automatically be granted to root
+   *          users when they authenticate.
+   */
+  public static Set<Privilege> getRootPrivileges()
+  {
+    assert debugEnter(CLASS_NAME, "getRootPrivileges");
+
+    return directoryServer.rootDNConfigManager.getRootPrivileges();
+  }
+
+
+
+  /**
    * Retrieves the DNs for the root users configured in the Directory Server.
    * Note that this set should only contain the actual DNs for the root users
    * and not any alternate DNs.  Also, the contents of the returned set must not
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
index 999c39c..be01a29 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -81,6 +81,7 @@
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
 import org.opends.server.types.OperationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
@@ -1283,6 +1284,18 @@
                      pwPolicyState.getPasswordAttribute()))
             {
               passwordChanged = true;
+              if (! selfChange)
+              {
+                if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET,
+                                                    this))
+                {
+                  int msgID = MSGID_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES;
+                  appendErrorMessage(getMessage(msgID));
+                  setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+                  break modifyProcessing;
+                }
+              }
+
               break;
             }
           }
@@ -1371,6 +1384,22 @@
           }
 
 
+          // See if the attribute is one which controls the privileges available
+          // for a user.  If it is, then the client must have the
+          // PRIVILEGE_CHANGE privilege.
+          if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
+          {
+            if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE,
+                                                this))
+            {
+              int msgID = MSGID_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES;
+              appendErrorMessage(getMessage(msgID));
+              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+              break modifyProcessing;
+            }
+          }
+
+
           // If the modification is updating the password attribute, then
           // perform any necessary password policy processing.  This processing
           // should be skipped for synchronization operations.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/RootDNConfigManager.java b/opendj-sdk/opends/src/server/org/opends/server/core/RootDNConfigManager.java
index d4c9d95..0affa1e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/RootDNConfigManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/RootDNConfigManager.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.core;
 
@@ -30,22 +30,29 @@
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.opends.server.api.ConfigAddListener;
 import org.opends.server.api.ConfigChangeListener;
 import org.opends.server.api.ConfigDeleteListener;
 import org.opends.server.api.ConfigHandler;
+import org.opends.server.api.ConfigurableComponent;
+import org.opends.server.config.ConfigAttribute;
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.config.DNConfigAttribute;
+import org.opends.server.config.MultiChoiceConfigAttribute;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.DN;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.InitializationException;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 
 import static org.opends.server.config.ConfigConstants.*;
@@ -66,7 +73,8 @@
  * DN for use when binding to the server.
  */
 public class RootDNConfigManager
-       implements ConfigChangeListener, ConfigAddListener, ConfigDeleteListener
+       implements ConfigChangeListener, ConfigAddListener, ConfigDeleteListener,
+                  ConfigurableComponent
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -80,6 +88,13 @@
   // bind DNs for that user.
   private ConcurrentHashMap<DN,List<DN>> bindMappings;
 
+  // The DN of the entry that serves as the base for the root DN
+  // configuration entries.
+  private DN rootDNConfigBaseDN;
+
+  // The set of privileges that will be automatically inherited by root users.
+  private LinkedHashSet<Privilege> rootPrivileges;
+
 
 
   /**
@@ -116,8 +131,8 @@
     ConfigEntry   baseEntry;
     try
     {
-      DN configBase = DN.decode(DN_ROOT_DN_CONFIG_BASE);
-      baseEntry = configHandler.getConfigEntry(configBase);
+      rootDNConfigBaseDN = DN.decode(DN_ROOT_DN_CONFIG_BASE);
+      baseEntry = configHandler.getConfigEntry(rootDNConfigBaseDN);
     }
     catch (Exception e)
     {
@@ -136,11 +151,62 @@
     }
 
 
+    // Get the set of privileges that root users should have by default.
+    rootPrivileges = new LinkedHashSet<Privilege>(
+                              Privilege.getDefaultRootPrivileges());
+
+    int msgID = MSGID_CONFIG_ROOTDN_DESCRIPTION_ROOT_PRIVILEGE;
+    MultiChoiceConfigAttribute rootPrivStub =
+         new MultiChoiceConfigAttribute(ATTR_DEFAULT_ROOT_PRIVILEGE_NAME,
+                                        getMessage(msgID), false, true, false,
+                                        Privilege.getPrivilegeNames());
+    try
+    {
+      MultiChoiceConfigAttribute rootPrivAttr =
+           (MultiChoiceConfigAttribute)
+           baseEntry.getConfigAttribute(rootPrivStub);
+      if (rootPrivAttr != null)
+      {
+        ArrayList<Privilege> privList = new ArrayList<Privilege>();
+        for (String value : rootPrivAttr.activeValues())
+        {
+          String privName = toLowerCase(value);
+          Privilege p = Privilege.privilegeForName(privName);
+          if (p == null)
+          {
+            msgID = MSGID_CONFIG_ROOTDN_UNRECOGNIZED_PRIVILEGE;
+            String message = getMessage(msgID, ATTR_DEFAULT_ROOT_PRIVILEGE_NAME,
+                                        String.valueOf(rootDNConfigBaseDN),
+                                        String.valueOf(value));
+            logError(ErrorLogCategory.CONFIGURATION,
+                     ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+          }
+          else
+          {
+            privList.add(p);
+          }
+        }
+
+        rootPrivileges = new LinkedHashSet<Privilege>(privList);
+      }
+    }
+    catch (Exception e)
+    {
+      assert debugException(CLASS_NAME, "initializeRootDNs", e);
+
+      msgID = MSGID_CONFIG_ROOTDN_ERROR_DETERMINING_ROOT_PRIVILEGES;
+      String message = getMessage(msgID, stackTraceToSingleLineString(e));
+      throw new InitializationException(msgID, message, e);
+    }
+
+
     // Register with the configuration base entry as an add and delete listener.
     // So that we will be notified of attempts to create or remove root DN
-    // entries.
+    // entries.  Also, register with the server as a configurable component so
+    // that we can detect and apply any changes to the root
     baseEntry.registerAddListener(this);
     baseEntry.registerDeleteListener(this);
+    DirectoryServer.registerConfigurableComponent(this);
 
 
     // See if the base entry has any children.  If not, then we don't need to
@@ -206,6 +272,22 @@
 
 
   /**
+   * Retrieves the set of privileges that should automatically be granted to
+   * root users when they authenticate.
+   *
+   * @return  The set of privileges that should automatically be granted to root
+   *          users when they authenticate.
+   */
+  public Set<Privilege> getRootPrivileges()
+  {
+    assert debugEnter(CLASS_NAME, "getRootPrivileges");
+
+    return rootPrivileges;
+  }
+
+
+
+  /**
    * Indicates whether the configuration entry that will result from a proposed
    * modification is acceptable to this change listener.
    *
@@ -638,5 +720,225 @@
 
     return new ConfigChangeResult(resultCode, adminActionRequired);
   }
+
+
+
+  /**
+   * Retrieves the DN of the configuration entry with which this
+   * component is associated.
+   *
+   * @return  The DN of the configuration entry with which this
+   *          component is associated.
+   */
+  public DN getConfigurableComponentEntryDN()
+  {
+    assert debugEnter(CLASS_NAME, "getConfigurableComponentEntryDN");
+
+    return rootDNConfigBaseDN;
+  }
+
+
+
+  /**
+   * Retrieves the set of configuration attributes that are associated
+   * with this configurable component.
+   *
+   * @return  The set of configuration attributes that are associated
+   *          with this configurable component.
+   */
+  public List<ConfigAttribute> getConfigurationAttributes()
+  {
+    assert debugEnter(CLASS_NAME, "getConfigurationAttributes");
+
+    LinkedList<ConfigAttribute> attrList = new LinkedList<ConfigAttribute>();
+
+    LinkedList<String> currentValues = new LinkedList<String>();
+    for (Privilege p : rootPrivileges)
+    {
+      currentValues.add(p.getName());
+    }
+
+    int msgID = MSGID_CONFIG_ROOTDN_DESCRIPTION_ROOT_PRIVILEGE;
+    attrList.add(new MultiChoiceConfigAttribute(
+                          ATTR_DEFAULT_ROOT_PRIVILEGE_NAME, getMessage(msgID),
+                          false, true, false, Privilege.getPrivilegeNames(),
+                          currentValues));
+
+    return attrList;
+  }
+
+
+
+  /**
+   * Indicates whether the provided configuration entry has an
+   * acceptable configuration for this component.  If it does not,
+   * then detailed information about the problem(s) should be added to
+   * the provided list.
+   *
+   * @param  configEntry          The configuration entry for which to
+   *                              make the determination.
+   * @param  unacceptableReasons  A list that can be used to hold
+   *                              messages about why the provided
+   *                              entry does not have an acceptable
+   *                              configuration.
+   *
+   * @return  <CODE>true</CODE> if the provided entry has an
+   *          acceptable configuration for this component, or
+   *          <CODE>false</CODE> if not.
+   */
+  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
+                                            List<String> unacceptableReasons)
+  {
+    assert debugEnter(CLASS_NAME, "hasAcceptableConfiguration",
+                      String.valueOf(configEntry), "List<String>");
+
+
+    int msgID = MSGID_CONFIG_ROOTDN_DESCRIPTION_ROOT_PRIVILEGE;
+    MultiChoiceConfigAttribute rootPrivStub =
+         new MultiChoiceConfigAttribute(ATTR_DEFAULT_ROOT_PRIVILEGE_NAME,
+                                        getMessage(msgID), false, true, false,
+                                        Privilege.getPrivilegeNames());
+    try
+    {
+      MultiChoiceConfigAttribute rootPrivAttr =
+           (MultiChoiceConfigAttribute)
+           configEntry.getConfigAttribute(rootPrivStub);
+      if (rootPrivAttr != null)
+      {
+        for (String value : rootPrivAttr.activeValues())
+        {
+          String privName = toLowerCase(value);
+          Privilege p = Privilege.privilegeForName(privName);
+          if (p == null)
+          {
+            msgID = MSGID_CONFIG_ROOTDN_UNRECOGNIZED_PRIVILEGE;
+            String message = getMessage(msgID, ATTR_DEFAULT_ROOT_PRIVILEGE_NAME,
+                                        String.valueOf(rootDNConfigBaseDN),
+                                        String.valueOf(value));
+            unacceptableReasons.add(message);
+            return false;
+          }
+        }
+      }
+    }
+    catch (Exception e)
+    {
+      assert debugException(CLASS_NAME, "initializeRootDNs", e);
+
+      msgID = MSGID_CONFIG_ROOTDN_ERROR_DETERMINING_ROOT_PRIVILEGES;
+      String message = getMessage(msgID, stackTraceToSingleLineString(e));
+      unacceptableReasons.add(message);
+      return false;
+    }
+
+
+    // If we've gotten here, then everything looks OK.
+    return true;
+  }
+
+
+
+  /**
+   * Makes a best-effort attempt to apply the configuration contained
+   * in the provided entry.  Information about the result of this
+   * processing should be added to the provided message list.
+   * Information should always be added to this list if a
+   * configuration change could not be applied.  If detailed results
+   * are requested, then information about the changes applied
+   * successfully (and optionally about parameters that were not
+   * changed) should also be included.
+   *
+   * @param  configEntry      The entry containing the new
+   *                          configuration to apply for this
+   *                          component.
+   * @param  detailedResults  Indicates whether detailed information
+   *                          about the processing should be added to
+   *                          the list.
+   *
+   * @return  Information about the result of the configuration
+   *          update.
+   */
+  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
+                                                  boolean detailedResults)
+  {
+    assert debugEnter(CLASS_NAME, "applyNewConfiguration",
+                      String.valueOf(configEntry),
+                      String.valueOf(detailedResults));
+
+
+    ResultCode        resultCode          = ResultCode.SUCCESS;
+    ArrayList<String> messages            = new ArrayList<String>();
+    boolean           adminActionRequired = false;
+
+
+    LinkedHashSet<Privilege> newRootPrivileges =
+         new LinkedHashSet<Privilege>(Privilege.getDefaultRootPrivileges());
+
+    int msgID = MSGID_CONFIG_ROOTDN_DESCRIPTION_ROOT_PRIVILEGE;
+    MultiChoiceConfigAttribute rootPrivStub =
+         new MultiChoiceConfigAttribute(ATTR_DEFAULT_ROOT_PRIVILEGE_NAME,
+                                        getMessage(msgID), false, true, false,
+                                        Privilege.getPrivilegeNames());
+    try
+    {
+      MultiChoiceConfigAttribute rootPrivAttr =
+           (MultiChoiceConfigAttribute)
+           configEntry.getConfigAttribute(rootPrivStub);
+      if (rootPrivAttr != null)
+      {
+        ArrayList<Privilege> privList = new ArrayList<Privilege>();
+        for (String value : rootPrivAttr.activeValues())
+        {
+          String privName = toLowerCase(value);
+          Privilege p = Privilege.privilegeForName(privName);
+          if (p == null)
+          {
+            if (resultCode == ResultCode.SUCCESS)
+            {
+              resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
+            }
+
+            msgID = MSGID_CONFIG_ROOTDN_UNRECOGNIZED_PRIVILEGE;
+            messages.add(getMessage(msgID, ATTR_DEFAULT_ROOT_PRIVILEGE_NAME,
+                                    String.valueOf(rootDNConfigBaseDN),
+                                    String.valueOf(value)));
+          }
+          else
+          {
+            privList.add(p);
+          }
+        }
+
+        newRootPrivileges = new LinkedHashSet<Privilege>(privList);
+      }
+    }
+    catch (Exception e)
+    {
+      assert debugException(CLASS_NAME, "initializeRootDNs", e);
+
+      if (resultCode == ResultCode.SUCCESS)
+      {
+        resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
+      }
+
+      msgID = MSGID_CONFIG_ROOTDN_ERROR_DETERMINING_ROOT_PRIVILEGES;
+      messages.add(getMessage(msgID, stackTraceToSingleLineString(e)));
+    }
+
+
+    if (resultCode == ResultCode.SUCCESS)
+    {
+      rootPrivileges = newRootPrivileges;
+
+      if (detailedResults)
+      {
+        msgID = MSGID_CONFIG_ROOTDN_UPDATED_PRIVILEGES;
+        messages.add(getMessage(msgID));
+      }
+    }
+
+
+    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+  }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
index 8ec35c3..0466c65 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.extensions;
 
@@ -57,6 +57,7 @@
 import javax.crypto.Mac;
 
 import org.opends.server.api.AlertGenerator;
+import org.opends.server.api.ClientConnection;
 import org.opends.server.api.ConfigAddListener;
 import org.opends.server.api.ConfigChangeListener;
 import org.opends.server.api.ConfigDeleteListener;
@@ -72,6 +73,7 @@
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.SearchOperation;
 import org.opends.server.tools.LDIFModify;
+import org.opends.server.types.AttributeType;
 import org.opends.server.types.BackupConfig;
 import org.opends.server.types.BackupDirectory;
 import org.opends.server.types.BackupInfo;
@@ -86,6 +88,8 @@
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.LDIFImportConfig;
 import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.Modification;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.RestoreConfig;
 import org.opends.server.types.SearchFilter;
@@ -122,6 +126,18 @@
 
 
 
+  /**
+   * The privilege array containing both the CONFIG_READ and CONFIG_WRITE
+   * privileges.
+   */
+  private static final Privilege[] CONFIG_READ_AND_WRITE =
+  {
+    Privilege.CONFIG_READ,
+    Privilege.CONFIG_WRITE
+  };
+
+
+
   // The mapping that holds all of the configuration entries that have been read
   // from the LDIF file.
   private ConcurrentHashMap<DN,ConfigEntry> configEntries;
@@ -959,6 +975,22 @@
                       String.valueOf(addOperation));
 
 
+    // If there is an add operation, then make sure that the associated user has
+    // both the CONFIG_READ and CONFIG_WRITE privileges.
+    if (addOperation != null)
+    {
+      ClientConnection clientConnection = addOperation.getClientConnection();
+      if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE,
+                                               addOperation)))
+      {
+        int    msgID   = MSGID_CONFIG_FILE_ADD_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
+
+
     // Grab the config lock to ensure that only one config update may be in
     // progress at any given time.
     configLock.lock();
@@ -1095,6 +1127,22 @@
                       String.valueOf(deleteOperation));
 
 
+    // If there is a delete operation, then make sure that the associated user
+    // has both the CONFIG_READ and CONFIG_WRITE privileges.
+    if (deleteOperation != null)
+    {
+      ClientConnection clientConnection = deleteOperation.getClientConnection();
+      if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE,
+                                               deleteOperation)))
+      {
+        int    msgID   = MSGID_CONFIG_FILE_DELETE_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
+
+
     // Grab the config lock to ensure that only one config update may be in
     // progress at any given time.
     configLock.lock();
@@ -1227,6 +1275,44 @@
                       String.valueOf(modifyOperation));
 
 
+    // If there is a modify operation, then make sure that the associated user
+    // has both the CONFIG_READ and CONFIG_WRITE privileges.  Also, if the
+    // operation targets the set of root privileges then make sure the user has
+    // the PRIVILEGE_CHANGE privilege.
+    if (modifyOperation != null)
+    {
+      ClientConnection clientConnection = modifyOperation.getClientConnection();
+      if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE,
+                                               modifyOperation)))
+      {
+        int    msgID   = MSGID_CONFIG_FILE_MODIFY_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+
+      AttributeType privType =
+           DirectoryServer.getAttributeType(ATTR_DEFAULT_ROOT_PRIVILEGE_NAME,
+                                            true);
+      for (Modification m : modifyOperation.getModifications())
+      {
+        if (m.getAttribute().getAttributeType().equals(privType))
+        {
+          if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE,
+                                              modifyOperation))
+          {
+            int msgID = MSGID_CONFIG_FILE_MODIFY_PRIVS_INSUFFICIENT_PRIVILEGES;
+            String message = getMessage(msgID);
+            throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                         message, msgID);
+          }
+
+          break;
+        }
+      }
+    }
+
+
     // Grab the config lock to ensure that only one config update may be in
     // progress at any given time.
     configLock.lock();
@@ -1381,6 +1467,23 @@
                       String.valueOf(entry), String.valueOf(modifyDNOperation));
 
 
+    // If there is a modify DN operation, then make sure that the associated
+    // user has both the CONFIG_READ and CONFIG_WRITE privileges.
+    if (modifyDNOperation != null)
+    {
+      ClientConnection clientConnection =
+           modifyDNOperation.getClientConnection();
+      if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE,
+                                               modifyDNOperation)))
+      {
+        int    msgID   = MSGID_CONFIG_FILE_MODDN_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
+
+
     // Modify DN operations will not be allowed in the configuration, so this
     // will always throw an exception.
     int msgID = MSGID_CONFIG_FILE_MODDN_NOT_ALLOWED;
@@ -1407,6 +1510,17 @@
     assert debugEnter(CLASS_NAME, "search", String.valueOf(searchOperation));
 
 
+    // Make sure that the associated user has the CONFIG_READ privilege.
+    ClientConnection clientConnection = searchOperation.getClientConnection();
+    if (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, searchOperation))
+    {
+      int    msgID   = MSGID_CONFIG_FILE_SEARCH_INSUFFICIENT_PRIVILEGES;
+      String message = getMessage(msgID);
+      throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                   message, msgID);
+    }
+
+
     // First, get the base DN for the search and make sure that it exists.
     DN          baseDN    = searchOperation.getBaseDN();
     ConfigEntry baseEntry = configEntries.get(baseDN);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
index 5eafa77..000f9b9 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -74,6 +74,7 @@
 import org.opends.server.types.LockManager;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 
 import static org.opends.server.config.ConfigConstants.*;
@@ -494,10 +495,34 @@
 
 
       // Determine whether the user is changing his own password or if it's an
-      // administrative reset.
-      boolean selfChange = ((userIdentity == null) ||
-                            (requestorEntry == null) ||
-                            userDN.equals(requestorEntry.getDN()));
+      // administrative reset.  If it's an administrative reset, then the
+      // requester must have the PASSWORD_RESET privilege.
+      boolean selfChange;
+      if (userIdentity == null)
+      {
+        selfChange = true;
+      }
+      else if (requestorEntry == null)
+      {
+        selfChange = (oldPassword != null);
+      }
+      else
+      {
+        selfChange = userDN.equals(requestorEntry.getDN());
+      }
+
+      if (! selfChange)
+      {
+        ClientConnection clientConnection = operation.getClientConnection();
+        if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET,
+                                            operation))
+        {
+          int msgID = MSGID_EXTOP_PASSMOD_INSUFFICIENT_PRIVILEGES;
+          operation.appendErrorMessage(getMessage(msgID));
+          operation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+          return;
+        }
+      }
 
 
       // See if the account is locked.  If so, then reject the request.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/BackendMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/BackendMessages.java
index 0f5e9c9..07671fc 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/BackendMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/BackendMessages.java
@@ -3171,6 +3171,16 @@
 
 
   /**
+   * The message ID for the message that will be used if a user attempts to
+   * modify the server schema without the appropriate privilege.  This does not
+   * take any arguments.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 293;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -3397,6 +3407,9 @@
     registerMessage(MSGID_SCHEMA_DELETE_NOT_SUPPORTED,
                     "Unwilling to remove entry \"%s\" because delete " +
                     "operations are not supported in the schema backend.");
+    registerMessage(MSGID_SCHEMA_MODIFY_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to modify the " +
+                    "Directory Server schema.");
     registerMessage(MSGID_SCHEMA_MODIFY_NOT_SUPPORTED,
                     "Unwilling to update entry \"%s\" because modify " +
                     "operations are not yet supported in the schema " +
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
index af9d07a..7c46619 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
@@ -6389,6 +6389,110 @@
        CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_ERROR | 593;
 
 
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * default root privilege names configuration attribute.  This does not take
+   * any arguments.
+   */
+  public static final int MSGID_CONFIG_ROOTDN_DESCRIPTION_ROOT_PRIVILEGE =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_ERROR | 594;
+
+
+
+  /**
+   * The message ID for the message that will be used if the set of root
+   * privileges contains an unrecognized privilege.  This takes three arguments,
+   * which are the name of the attribute holding the privilege names, the DN of
+   * the configuration entry, and the name of the unrecognized privilege.
+   */
+  public static final int MSGID_CONFIG_ROOTDN_UNRECOGNIZED_PRIVILEGE =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_WARNING | 595;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to determine the set of root privileges.  This takes a single
+   * argument, which is a stack trace of the exception that was caught.
+   */
+  public static final int
+       MSGID_CONFIG_ROOTDN_ERROR_DETERMINING_ROOT_PRIVILEGES =
+            CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_ERROR | 596;
+
+
+
+  /**
+   * The message ID for the message that will be used to indicate that the set
+   * of root privileges has been updated.  This does not take any arguments.
+   */
+  public static final int MSGID_CONFIG_ROOTDN_UPDATED_PRIVILEGES =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_INFORMATIONAL | 597;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform an add operation in the server configuration but the user doesn't
+   * have the necessary privileges to do so.  This does not take any arguments.
+   */
+  public static final int MSGID_CONFIG_FILE_ADD_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_MILD_ERROR | 598;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform a delete operation in the server configuration but the user doesn't
+   * have the necessary privileges to do so.  This does not take any arguments.
+   */
+  public static final int MSGID_CONFIG_FILE_DELETE_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_MILD_ERROR | 599;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform a modify operation in the server configuration but the user doesn't
+   * have the necessary privileges to do so.  This does not take any arguments.
+   */
+  public static final int MSGID_CONFIG_FILE_MODIFY_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_MILD_ERROR | 600;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform a modify DN operation in the server configuration but the user
+   * doesn't have the necessary privileges to do so.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_CONFIG_FILE_MODDN_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_MILD_ERROR | 601;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * perform a search operation in the server configuration but the user doesn't
+   * have the necessary privileges to do so.  This does not take any arguments.
+   */
+  public static final int MSGID_CONFIG_FILE_SEARCH_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CONFIG | SEVERITY_MASK_MILD_ERROR | 602;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * modify the set of default root privileges but the user doesn't have the
+   * necessary privileges to do so.  This does not take any arguments.
+   */
+  public static final int
+       MSGID_CONFIG_FILE_MODIFY_PRIVS_INSUFFICIENT_PRIVILEGES =
+            CATEGORY_MASK_CONFIG | SEVERITY_MASK_MILD_ERROR | 603;
+
+
+
   /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
@@ -6721,6 +6825,24 @@
                     "the server is online.  The server configuration should " +
                     "only be managed using the administration utilities " +
                     "provided with the Directory Server.");
+    registerMessage(MSGID_CONFIG_FILE_ADD_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to perform add " +
+                    "operations in the Directory Server configuration.");
+    registerMessage(MSGID_CONFIG_FILE_DELETE_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to perform delete " +
+                    "operations in the Directory Server configuration.");
+    registerMessage(MSGID_CONFIG_FILE_MODIFY_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to perform modify " +
+                    "operations in the Directory Server configuration.");
+    registerMessage(MSGID_CONFIG_FILE_MODIFY_PRIVS_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to change the set " +
+                    "of default root privileges.");
+    registerMessage(MSGID_CONFIG_FILE_MODDN_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to perform modify " +
+                    "DN operations in the Directory Server configuration.");
+    registerMessage(MSGID_CONFIG_FILE_SEARCH_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to perform search " +
+                    "operations in the Directory Server configuration.");
 
 
     registerMessage(MSGID_CONFIG_LOGGER_CANNOT_GET_BASE,
@@ -9032,6 +9154,24 @@
                     DN_ROOT_DN_CONFIG_BASE + " does not exist in the " +
                     "Directory Server configuration.  This entry must be " +
                     "present for the server to function properly.");
+    registerMessage(MSGID_CONFIG_ROOTDN_DESCRIPTION_ROOT_PRIVILEGE,
+                    "Specifies the set of privileges that should " +
+                    "automatically be assigned to root users when they " +
+                    "authenticate to the server.");
+    registerMessage(MSGID_CONFIG_ROOTDN_UNRECOGNIZED_PRIVILEGE,
+                    "The set of default root privileges contained in " +
+                    "configuration attribute %s of entry %s contains an " +
+                    "unrecognized privilege %s.");
+    registerMessage(MSGID_CONFIG_ROOTDN_ERROR_DETERMINING_ROOT_PRIVILEGES,
+                    "An error occurred while attempting to determine the " +
+                    "set of privileges that root users should be granted by " +
+                    "default:  %s.");
+    registerMessage(MSGID_CONFIG_ROOTDN_UPDATED_PRIVILEGES,
+                    "The set of privileges that will automatically be " +
+                    "assigned to root users has been updated.  This new " +
+                    "privilege set will not apply to any existing " +
+                    "connection already authenticated as a root user, but " +
+                    "will used for any subsequent root user authentications.");
     registerMessage(MSGID_CONFIG_ROOTDN_ENTRY_UNACCEPTABLE,
                     "Configuration entry %s does not contain a valid root DN " +
                     "configuration:  %s.  It will be ignored.");
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
index 295310f..4a6f374 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6161,6 +6161,74 @@
 
 
   /**
+   * The message ID for the message that will be used if a modify request
+   * includes an attempt to reset another user's password by an individual that
+   * does not have the appropriate privileges.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 589;
+
+
+
+  /**
+   * The message ID for the message that will be used if a compare request
+   * targets the server configuration but the requester doesn't have the
+   * appropriate privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 590;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add an entry with one or more privileges but the user doesn't have
+   * sufficient privilege to update privileges.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 591;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * modify the set of privileges contained in an entry but the user doesn't
+   * have sufficient privileges to make that change.  This does not take any
+   * arguments.
+   */
+  public static final int
+       MSGID_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES =
+            CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 592;
+
+
+
+  /**
+   * The message ID for the audit message that will be generated when a client
+   * attempts to perform a privileged operation that requires a single
+   * privilege.  This takes five arguments, which are the connection ID, the
+   * operation ID, the authentication DN, the name of the requested privilege,
+   * and the result of the determination.
+   */
+  public static final int MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGE =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 593;
+
+
+
+  /**
+   * The message ID for the audit message that will be generated when a client
+   * attempts to perform a privileged operation that requires a multiple
+   * privileges.  This takes five arguments, which are the connection ID, the
+   * operation ID, the authentication DN, a formatted list of the names of the
+   * requested privileges, and the result of the determination.
+   */
+  public static final int MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGES =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 594;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined
    * in this class.
    */
@@ -6932,6 +7000,9 @@
                     "attribute %s that is contained in the entry's RDN.  " +
                     "All attributes used in the RDN must also be provided in " +
                     "the attribute list for the entry.");
+    registerMessage(MSGID_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to add entries " +
+                    "that include privileges.");
     registerMessage(MSGID_ADD_NOOP,
                     "The add operation was not actually performed in the " +
                     "Directory Server backend because the LDAP no-op control " +
@@ -7047,6 +7118,9 @@
                     "plugin working on referral %s.");
 
 
+    registerMessage(MSGID_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to access the " +
+                    "server configuration.");
     registerMessage(MSGID_COMPARE_CANNOT_LOCK_ENTRY,
                     "The Directory Server was unable to obtain a read " +
                     "lock on entry %s after multiple attempts.  Processing " +
@@ -7295,6 +7369,9 @@
                     "contained a critical control with OID %s that is not " +
                     "supported by the Directory Server for this type of " +
                     "operation.");
+    registerMessage(MSGID_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to reset user " +
+                    "passwords.");
     registerMessage(MSGID_MODIFY_MUST_CHANGE_PASSWORD,
                     "You must change your password before you will be " +
                     "allowed to perform any other operations.");
@@ -7306,6 +7383,9 @@
                     "Entry %s cannot be modified because the modification " +
                     "attempted to set one or more new values for attribute " +
                     "%s which is marked OBSOLETE in the server schema.");
+    registerMessage(MSGID_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to modify the " +
+                    "set of privileges contained in an entry.");
     registerMessage(MSGID_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS,
                     "Attributes used to hold user passwords are not allowed " +
                     "to have any attribute options.");
@@ -8337,6 +8417,12 @@
                     "Terminating the client connection because its " +
                     "associated authentication or authorization entry %s has " +
                     "been deleted.");
+    registerMessage(MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGE,
+                    "hasPrivilege determination for connID=%d opID=%d " +
+                    "requesterDN=\"%s\" privilege=\"%s\" result=%b");
+    registerMessage(MSGID_CLIENTCONNECTION_AUDIT_HASPRIVILEGES,
+                    "hasPrivilege determination for connID=%d opID=%d " +
+                    "requesterDN=\"%s\" privilegeSet=\"%s\" result=%b");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
index d8fb15c..66e04e5 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -4140,6 +4140,16 @@
 
 
   /**
+   * The message ID for the message that will be used if a password modify
+   * extended operation is requested to reset another user's password by a
+   * client without sufficient privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_EXTOP_PASSMOD_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 392;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -4396,10 +4406,13 @@
     registerMessage(MSGID_EXTOP_PASSMOD_INVALID_OLD_PASSWORD,
                     "The password modify extended operation cannot be " +
                     "processed because the current password provided for the " +
-                    "use is invalid.");
+                    "user is invalid.");
     registerMessage(MSGID_EXTOP_PASSMOD_CANNOT_GET_PW_POLICY,
                     "An error occurred while attempting to get the " +
                     "password policy for user %s:  %s.");
+    registerMessage(MSGID_EXTOP_PASSMOD_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to perform " +
+                    "password reset operations.");
     registerMessage(MSGID_EXTOP_PASSMOD_ACCOUNT_DISABLED,
                     "The user account has been administratively disabled.");
     registerMessage(MSGID_EXTOP_PASSMOD_ACCOUNT_LOCKED,
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/TaskMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/TaskMessages.java
index 5a7ed00..875d984 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/TaskMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/TaskMessages.java
@@ -122,9 +122,9 @@
 
 
   /**
-   * The message ID for the shutdown message that will be used if the server is
-   * unable to obtain a write lock on the server schema.  This takes a single
-   * argument, which is the DN of the schema entry.
+   * The message ID for the message that will be used if the server is unable to
+   * obtain a write lock on the server schema.  This takes a single argument,
+   * which is the DN of the schema entry.
    */
   public static final int MSGID_TASK_ADDSCHEMAFILE_CANNOT_LOCK_SCHEMA =
        CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 9;
@@ -132,6 +132,76 @@
 
 
   /**
+   * The message ID for the message that will be used an attempt is made to
+   * invoke the add schema file task by a user that does not have the required
+   * privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_TASK_ADDSCHEMAFILE_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 10;
+
+
+
+  /**
+   * The message ID for the message that will be used an attempt is made to
+   * invoke the backend backup task by a user that does not have the required
+   * privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_TASK_BACKUP_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 11;
+
+
+
+  /**
+   * The message ID for the message that will be used an attempt is made to
+   * invoke the backend restore task by a user that does not have the required
+   * privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_TASK_RESTORE_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 12;
+
+
+
+  /**
+   * The message ID for the message that will be used an attempt is made to
+   * invoke the LDIF import task by a user that does not have the required
+   * privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_TASK_LDIFIMPORT_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 13;
+
+
+
+  /**
+   * The message ID for the message that will be used an attempt is made to
+   * invoke the LDIF export task by a user that does not have the required
+   * privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_TASK_LDIFEXPORT_INSUFFICIENT_PRIVILEGES =
+       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 14;
+
+
+
+  /**
+   * The message ID for the message that will be used an attempt is made to
+   * invoke the server shutdown task to restart the server by a user that does
+   * not have the required privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_TASK_SHUTDOWN_INSUFFICIENT_RESTART_PRIVILEGES =
+       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 15;
+
+
+
+  /**
+   * The message ID for the message that will be used an attempt is made to
+   * invoke the server shutdown task to shut down the server by a user that does
+   * not have the required privileges.  This does not take any arguments.
+   */
+  public static final int MSGID_TASK_SHUTDOWN_INSUFFICIENT_SHUTDOWN_PRIVILEGES =
+       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 16;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -149,8 +219,17 @@
     registerMessage(MSGID_TASK_SHUTDOWN_CUSTOM_MESSAGE,
                     "The Directory Server shutdown process has been " +
                     "initiated by task %s:  %s");
+    registerMessage(MSGID_TASK_SHUTDOWN_INSUFFICIENT_RESTART_PRIVILEGES,
+                    "You do not have sufficient privileges to initiate a " +
+                    "Directory Server restart.");
+    registerMessage(MSGID_TASK_SHUTDOWN_INSUFFICIENT_SHUTDOWN_PRIVILEGES,
+                    "You do not have sufficient privileges to initiate a " +
+                    "Directory Server shutdown.");
 
 
+    registerMessage(MSGID_TASK_ADDSCHEMAFILE_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to modify the " +
+                    "server schema.");
     registerMessage(MSGID_TASK_ADDSCHEMAFILE_NO_FILENAME,
                     "Unable to add one or more files to the server schema " +
                     "because no schema file names were provided in " +
@@ -170,6 +249,20 @@
                     "Unable to add one or more files to the server schema " +
                     "because the server was unable to obtain a write lock on " +
                     "the schema entry %s after multiple attempts.");
+
+
+    registerMessage(MSGID_TASK_BACKUP_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to initiate a " +
+                    "Directory Server backup.");
+    registerMessage(MSGID_TASK_RESTORE_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to initiate a " +
+                    "Directory Server restore.");
+    registerMessage(MSGID_TASK_LDIFIMPORT_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to initiate an " +
+                    "LDIF import.");
+    registerMessage(MSGID_TASK_LDIFEXPORT_INSUFFICIENT_PRIVILEGES,
+                    "You do not have sufficient privileges to initiate an " +
+                    "LDIF export.");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
index a56704f..9eb75c1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
@@ -201,7 +201,6 @@
 
       LinkedHashMap<AttributeType,List<Attribute>> operationalAttrs =
            new LinkedHashMap<AttributeType,List<Attribute>>();
-      // FIXME -- Add privileges here.
 
 
       DN internalUserDN = DN.decode(fullDNString);
@@ -211,6 +210,7 @@
 
       this.authenticationInfo =
            new AuthenticationInfo(internalUserEntry, true);
+      super.setAuthenticationInfo(authenticationInfo);
     }
     catch (DirectoryException de)
     {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java b/opendj-sdk/opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java
index 9989d36..483b4a8 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java
@@ -33,10 +33,12 @@
 import java.util.TreeSet;
 import java.util.concurrent.locks.Lock;
 
+import org.opends.server.api.ClientConnection;
 import org.opends.server.backends.task.Task;
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.Operation;
 import org.opends.server.core.SchemaConfigManager;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -48,6 +50,7 @@
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.LockManager;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.Schema;
 
@@ -88,6 +91,23 @@
   {
     assert debugEnter(CLASS_NAME, "initializeTask");
 
+
+    // If the client connection is available, then make sure the associated
+    // client has the UPDATE_SCHEMA privilege.
+    Operation operation = getOperation();
+    if (operation != null)
+    {
+      ClientConnection clientConnection = operation.getClientConnection();
+      if (! clientConnection.hasPrivilege(Privilege.UPDATE_SCHEMA, operation))
+      {
+        int    msgID   = MSGID_TASK_ADDSCHEMAFILE_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
+
+
     // Get the attribute that specifies which schema file(s) to add.
     Entry taskEntry = getTaskEntry();
     AttributeType attrType = DirectoryServer.getAttributeType(
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tasks/BackupTask.java b/opendj-sdk/opends/src/server/org/opends/server/tasks/BackupTask.java
index 0aa7639..9d60704 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tasks/BackupTask.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tasks/BackupTask.java
@@ -22,13 +22,14 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tasks;
 
 import static org.opends.server.loggers.Debug.debugEnter;
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.core.DirectoryServer.getAttributeType;
+import static org.opends.server.messages.TaskMessages.*;
 import static org.opends.server.messages.ToolMessages.*;
 import static org.opends.server.messages.MessageHandler.getMessage;
 import static org.opends.server.util.ServerConstants.DATE_FORMAT_UTC_TIME;
@@ -41,7 +42,9 @@
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.LockFileManager;
+import org.opends.server.core.Operation;
 import org.opends.server.api.Backend;
+import org.opends.server.api.ClientConnection;
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.types.Attribute;
@@ -52,6 +55,8 @@
 import org.opends.server.types.Entry;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.ResultCode;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -104,7 +109,20 @@
     assert debugEnter(CLASS_NAME, "initializeTask");
 
 
-    // FIXME -- Do we need any special authorization here?
+    // If the client connection is available, then make sure the associated
+    // client has the BACKEND_BACKUP privilege.
+    Operation operation = getOperation();
+    if (operation != null)
+    {
+      ClientConnection clientConnection = operation.getClientConnection();
+      if (! clientConnection.hasPrivilege(Privilege.BACKEND_BACKUP, operation))
+      {
+        int    msgID   = MSGID_TASK_BACKUP_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
 
 
     Entry taskEntry = getTaskEntry();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tasks/ExportTask.java b/opendj-sdk/opends/src/server/org/opends/server/tasks/ExportTask.java
index d57dcff..73d0e0a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tasks/ExportTask.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tasks/ExportTask.java
@@ -22,13 +22,14 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tasks;
 
 import static org.opends.server.loggers.Debug.debugEnter;
 import static org.opends.server.core.DirectoryServer.getAttributeType;
 import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.messages.TaskMessages.*;
 import static org.opends.server.messages.ToolMessages.*;
 import static org.opends.server.messages.MessageHandler.getMessage;
 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
@@ -37,7 +38,9 @@
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.LockFileManager;
+import org.opends.server.core.Operation;
 import org.opends.server.api.Backend;
+import org.opends.server.api.ClientConnection;
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -48,6 +51,8 @@
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.ExistingFileBehavior;
 import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 
 import java.util.ArrayList;
@@ -90,7 +95,20 @@
     assert debugEnter(CLASS_NAME, "initializeTask");
 
 
-    // FIXME -- Do we need any special authorization here?
+    // If the client connection is available, then make sure the associated
+    // client has the LDIF_EXPORT privilege.
+    Operation operation = getOperation();
+    if (operation != null)
+    {
+      ClientConnection clientConnection = operation.getClientConnection();
+      if (! clientConnection.hasPrivilege(Privilege.LDIF_EXPORT, operation))
+      {
+        int    msgID   = MSGID_TASK_LDIFEXPORT_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
 
 
     Entry taskEntry = getTaskEntry();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tasks/ImportTask.java b/opendj-sdk/opends/src/server/org/opends/server/tasks/ImportTask.java
index d3bfbe5..6321313 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tasks/ImportTask.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tasks/ImportTask.java
@@ -22,10 +22,11 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tasks;
 
+import static org.opends.server.messages.TaskMessages.*;
 import static org.opends.server.messages.ToolMessages.*;
 import static org.opends.server.messages.MessageHandler.getMessage;
 import static org.opends.server.loggers.Debug.*;
@@ -38,7 +39,9 @@
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.LockFileManager;
+import org.opends.server.core.Operation;
 import org.opends.server.api.Backend;
+import org.opends.server.api.ClientConnection;
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -49,6 +52,8 @@
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.ExistingFileBehavior;
 import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 
 import java.util.HashSet;
@@ -95,7 +100,20 @@
     assert debugEnter(CLASS_NAME, "initializeTask");
 
 
-    // FIXME -- Do we need any special authorization here?
+    // If the client connection is available, then make sure the associated
+    // client has the LDIF_IMPORT privilege.
+    Operation operation = getOperation();
+    if (operation != null)
+    {
+      ClientConnection clientConnection = operation.getClientConnection();
+      if (! clientConnection.hasPrivilege(Privilege.LDIF_IMPORT, operation))
+      {
+        int    msgID   = MSGID_TASK_LDIFIMPORT_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
 
 
     Entry taskEntry = getTaskEntry();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tasks/RestoreTask.java b/opendj-sdk/opends/src/server/org/opends/server/tasks/RestoreTask.java
index 563b154..740cf37 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tasks/RestoreTask.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tasks/RestoreTask.java
@@ -22,13 +22,14 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tasks;
 
 import static org.opends.server.loggers.Debug.debugEnter;
 import static org.opends.server.core.DirectoryServer.getAttributeType;
 import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.messages.TaskMessages.*;
 import static org.opends.server.messages.ToolMessages.*;
 import static org.opends.server.messages.MessageHandler.getMessage;
 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
@@ -39,7 +40,9 @@
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.LockFileManager;
+import org.opends.server.core.Operation;
 import org.opends.server.api.Backend;
+import org.opends.server.api.ClientConnection;
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.types.Attribute;
@@ -51,7 +54,9 @@
 import org.opends.server.types.Entry;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.Privilege;
 import org.opends.server.types.RestoreConfig;
+import org.opends.server.types.ResultCode;
 
 import java.util.List;
 import java.io.File;
@@ -84,7 +89,20 @@
     assert debugEnter(CLASS_NAME, "initializeTask");
 
 
-    // FIXME -- Do we need any special authorization here?
+    // If the client connection is available, then make sure the associated
+    // client has the BACKEND_RESTORE privilege.
+    Operation operation = getOperation();
+    if (operation != null)
+    {
+      ClientConnection clientConnection = operation.getClientConnection();
+      if (! clientConnection.hasPrivilege(Privilege.BACKEND_RESTORE, operation))
+      {
+        int    msgID   = MSGID_TASK_RESTORE_INSUFFICIENT_PRIVILEGES;
+        String message = getMessage(msgID);
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                     message, msgID);
+      }
+    }
 
 
     Entry taskEntry = getTaskEntry();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tasks/ShutdownTask.java b/opendj-sdk/opends/src/server/org/opends/server/tasks/ShutdownTask.java
index 827b883..6c7615b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tasks/ShutdownTask.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tasks/ShutdownTask.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tasks;
 
@@ -31,14 +31,18 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 
+import org.opends.server.api.ClientConnection;
 import org.opends.server.backends.task.Task;
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.Operation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
 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 static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.loggers.Debug.*;
@@ -88,9 +92,6 @@
     assert debugEnter(CLASS_NAME, "initializeTask");
 
 
-    // FIXME -- Do we need any special authorization here?
-
-
     // See if the entry contains a shutdown message.  If so, then use it.
     // Otherwise, use a default message.
     Entry taskEntry = getTaskEntry();
@@ -132,6 +133,38 @@
                    valueString.equals("on") || valueString.equals("1"));
       }
     }
+
+
+    // If the client connection is available, then make sure the associated
+    // client has either the SERVER_SHUTDOWN or SERVER_RESTART privilege, based
+    // on the appropriate action.
+    Operation operation = getOperation();
+    if (operation != null)
+    {
+      ClientConnection clientConnection = operation.getClientConnection();
+      if (restart)
+      {
+        if (! clientConnection.hasPrivilege(Privilege.SERVER_RESTART,
+                                            operation))
+        {
+          int    msgID   = MSGID_TASK_SHUTDOWN_INSUFFICIENT_RESTART_PRIVILEGES;
+          String message = getMessage(msgID);
+          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                       message, msgID);
+        }
+      }
+      else
+      {
+        if (! clientConnection.hasPrivilege(Privilege.SERVER_SHUTDOWN,
+                                            operation))
+        {
+          int    msgID   = MSGID_TASK_SHUTDOWN_INSUFFICIENT_SHUTDOWN_PRIVILEGES;
+          String message = getMessage(msgID);
+          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                                       message, msgID);
+        }
+      }
+    }
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPPasswordModify.java b/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPPasswordModify.java
index 444bc29..9792843 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPPasswordModify.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPPasswordModify.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools;
 
@@ -617,6 +617,11 @@
       requestElements.add(new ASN1OctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
                                               currentPW.getValue()));
     }
+    else if (currentPWFile.isPresent())
+    {
+      requestElements.add(new ASN1OctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
+                                              currentPWFile.getValue()));
+    }
     else if (provideDNForAuthzID.isPresent())
     {
       requestElements.add(new ASN1OctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
@@ -628,6 +633,11 @@
       requestElements.add(new ASN1OctetString(TYPE_PASSWORD_MODIFY_NEW_PASSWORD,
                                               newPW.getValue()));
     }
+    else if (newPWFile.isPresent())
+    {
+      requestElements.add(new ASN1OctetString(TYPE_PASSWORD_MODIFY_NEW_PASSWORD,
+                                              newPWFile.getValue()));
+    }
 
     ASN1OctetString requestValue =
          new ASN1OctetString(new ASN1Sequence(requestElements).encode());
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Privilege.java b/opendj-sdk/opends/src/server/org/opends/server/types/Privilege.java
new file mode 100644
index 0000000..f04533d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/Privilege.java
@@ -0,0 +1,389 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types;
+
+
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.opends.server.util.StaticUtils.*;
+
+
+
+/**
+ * This class implements an enumeration that defines the set of
+ * privileges available in the Directory Server.
+ */
+public enum Privilege
+{
+  /**
+   * The privilege that provides the ability to bypass access control
+   * evaluation.
+   */
+  BYPASS_ACL("bypass-acl"),
+
+
+
+  /**
+   * The privilege that provides the ability to modify access control
+   * rules.
+   */
+  MODIFY_ACL("modify-acl"),
+
+
+
+  /**
+   * The privilege that provides the ability to read the server
+   * configuration.
+   */
+  CONFIG_READ("config-read"),
+
+
+
+  /**
+   * The privilege that provides the ability to update the server
+   * configuration.
+   */
+  CONFIG_WRITE("config-write"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform read
+   * operations via JMX.
+   */
+  JMX_READ("jmx-read"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform write
+   * operations via JMX.
+   */
+  JMX_WRITE("jmx-write"),
+
+
+
+  /**
+   * The privilege that provides the ability to subscribe to JMX
+   * notifications.
+   */
+  JMX_NOTIFY("jmx-notify"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform LDIF import
+   * operations.
+   */
+  LDIF_IMPORT("ldif-import"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform LDIF export
+   * operations.
+   */
+  LDIF_EXPORT("ldif-export"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform backend backup
+   * operations.
+   */
+  BACKEND_BACKUP("backend-backup"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform backend
+   * restore operations.
+   */
+  BACKEND_RESTORE("backend-restore"),
+
+
+
+  /**
+   * The privilege that provides the ability to request a server
+   * shutdown.
+   */
+  SERVER_SHUTDOWN("server-shutdown"),
+
+
+
+  /**
+   * The privilege that provides the ability to request a server
+   * restart.
+   */
+  SERVER_RESTART("server-restart"),
+
+
+
+  /**
+   * The privilege that provides the ability to perform proxied
+   * authorization or request an alternate authorization identity.
+   */
+  PROXIED_AUTH("proxied-auth"),
+
+
+
+  /**
+   * The privilege that provides the ability to terminate arbitrary
+   * client connections.
+   */
+  DISCONNECT_CLIENT("disconnect-client"),
+
+
+
+  /**
+   * The privilege that provides the ability to cancel arbitrary
+   * client requests.
+   */
+  CANCEL_REQUEST("cancel-request"),
+
+
+
+  /**
+   * The privilege that provides the ability to request unindexed
+   * searches.
+   */
+  SEARCH_UNINDEXED("search-unindexed"),
+
+
+
+  /**
+   * The privilege that provides the ability to reset user passwords.
+   */
+  PASSWORD_RESET("password-reset"),
+
+
+
+  /**
+   * The privilege that provides the ability to participate in a
+   * data synchronization environment.
+   */
+  DATA_SYNC("data-sync"),
+
+
+
+  /**
+   * The privilege that provides the ability to update the server
+   * schema.
+   */
+  UPDATE_SCHEMA("update-schema"),
+
+
+
+  /**
+   * The privilege that provides the ability to change the set of
+   * privileges for a user, or to change the set of privileges
+   * automatically assigned to a root user.
+   */
+  PRIVILEGE_CHANGE("privilege-change");
+
+
+
+  /**
+   * A map that will be used to hold a mapping between privilege names
+   * and enum values.
+   */
+  private static final HashMap<String,Privilege> PRIV_MAP =
+       new HashMap<String,Privilege>();
+
+
+
+  /**
+   * The set of privileges that will be automatically assigned to root
+   * users if the root privilege set is not specified in the
+   * configuration.
+   */
+  private static final HashSet<Privilege> DEFAULT_ROOT_PRIV_SET =
+       new HashSet<Privilege>();
+
+
+
+  /**
+   * The names of the available privileges defined in this class.
+   */
+  private static final HashSet<String> PRIV_NAMES =
+       new HashSet<String>();
+
+
+
+  // The human-readable name for this privilege.
+  private final String privilegeName;
+
+
+
+  static
+  {
+    PRIV_MAP.put("bypass-acl", BYPASS_ACL);
+    PRIV_MAP.put("modify-acl", MODIFY_ACL);
+    PRIV_MAP.put("config-read", CONFIG_READ);
+    PRIV_MAP.put("config-write", CONFIG_WRITE);
+    PRIV_MAP.put("jmx-read", JMX_READ);
+    PRIV_MAP.put("jmx-write", JMX_WRITE);
+    PRIV_MAP.put("jmx-notify", JMX_NOTIFY);
+    PRIV_MAP.put("ldif-import", LDIF_IMPORT);
+    PRIV_MAP.put("ldif-export", LDIF_EXPORT);
+    PRIV_MAP.put("backend-backup", BACKEND_BACKUP);
+    PRIV_MAP.put("backend-restore", BACKEND_RESTORE);
+    PRIV_MAP.put("server-shutdown", SERVER_SHUTDOWN);
+    PRIV_MAP.put("server-restart", SERVER_RESTART);
+    PRIV_MAP.put("proxied-auth", PROXIED_AUTH);
+    PRIV_MAP.put("disconnect-client", DISCONNECT_CLIENT);
+    PRIV_MAP.put("cancel-request", CANCEL_REQUEST);
+    PRIV_MAP.put("search-unindexed", SEARCH_UNINDEXED);
+    PRIV_MAP.put("password-reset", PASSWORD_RESET);
+    PRIV_MAP.put("data-sync", DATA_SYNC);
+    PRIV_MAP.put("update-schema", UPDATE_SCHEMA);
+    PRIV_MAP.put("privilege-change", PRIVILEGE_CHANGE);
+
+    PRIV_NAMES.add("bypass-acl");
+    PRIV_NAMES.add("modify-acl");
+    PRIV_NAMES.add("config-read");
+    PRIV_NAMES.add("config-write");
+    PRIV_NAMES.add("jmx-read");
+    PRIV_NAMES.add("jmx-write");
+    PRIV_NAMES.add("jmx-notify");
+    PRIV_NAMES.add("ldif-import");
+    PRIV_NAMES.add("ldif-export");
+    PRIV_NAMES.add("backend-backup");
+    PRIV_NAMES.add("backend-restore");
+    PRIV_NAMES.add("server-shutdown");
+    PRIV_NAMES.add("server-restart");
+    PRIV_NAMES.add("proxied-auth");
+    PRIV_NAMES.add("disconnect-client");
+    PRIV_NAMES.add("cancel-request");
+    PRIV_NAMES.add("search-unindexed");
+    PRIV_NAMES.add("password-reset");
+    PRIV_NAMES.add("data-sync");
+    PRIV_NAMES.add("update-schema");
+    PRIV_NAMES.add("privilege-change");
+
+    DEFAULT_ROOT_PRIV_SET.add(BYPASS_ACL);
+    DEFAULT_ROOT_PRIV_SET.add(MODIFY_ACL);
+    DEFAULT_ROOT_PRIV_SET.add(CONFIG_READ);
+    DEFAULT_ROOT_PRIV_SET.add(CONFIG_WRITE);
+    DEFAULT_ROOT_PRIV_SET.add(LDIF_IMPORT);
+    DEFAULT_ROOT_PRIV_SET.add(LDIF_EXPORT);
+    DEFAULT_ROOT_PRIV_SET.add(BACKEND_BACKUP);
+    DEFAULT_ROOT_PRIV_SET.add(BACKEND_RESTORE);
+    DEFAULT_ROOT_PRIV_SET.add(SERVER_SHUTDOWN);
+    DEFAULT_ROOT_PRIV_SET.add(SERVER_RESTART);
+    DEFAULT_ROOT_PRIV_SET.add(DISCONNECT_CLIENT);
+    DEFAULT_ROOT_PRIV_SET.add(CANCEL_REQUEST);
+    DEFAULT_ROOT_PRIV_SET.add(SEARCH_UNINDEXED);
+    DEFAULT_ROOT_PRIV_SET.add(PASSWORD_RESET);
+    DEFAULT_ROOT_PRIV_SET.add(UPDATE_SCHEMA);
+    DEFAULT_ROOT_PRIV_SET.add(PRIVILEGE_CHANGE);
+  }
+
+
+
+  /**
+   * Creates a new privilege with the provided name.
+   *
+   * @param  privilegeName  The human-readable name for this policy.
+   */
+  private Privilege(String privilegeName)
+  {
+    this.privilegeName = privilegeName;
+  }
+
+
+
+  /**
+   * Retrieves the name for this privilege.
+   *
+   * @return  The name for this privilege.
+   */
+  public String getName()
+  {
+    return privilegeName;
+  }
+
+
+
+  /**
+   * Retrieves the privilege with the specified name.
+   *
+   * @param  lowerPrivName  The name of the privilege to retrieve,
+   *                        formatted in all lowercase characters.
+   *
+   * @return  The requested privilege, or {@code null} if the provided
+   *          value is not the name of a valid privilege.
+   */
+  public static Privilege privilegeForName(String lowerPrivName)
+  {
+    return PRIV_MAP.get(lowerPrivName);
+  }
+
+
+
+  /**
+   * Retrieves the human-readable name for this privilege.
+   *
+   * @return  The human-readable name for this privilege.
+   */
+  public String toString()
+  {
+    return privilegeName;
+  }
+
+
+
+  /**
+   * Retrieves the set of available privilege names.
+   *
+   * @return  The set of available privilege names.
+   */
+  public static Set<String> getPrivilegeNames()
+  {
+    return PRIV_NAMES;
+  }
+
+
+
+  /**
+   * Retrieves the set of privileges that should be automatically
+   * granted to root users if the root privilege set is not specified
+   * in the configuration.
+   *
+   * @return  The set of privileges that should be automatically
+   *          granted to root users if the root privilege set is not
+   *          specified in the configuration.
+   */
+  public static Set<Privilege> getDefaultRootPrivileges()
+  {
+    return DEFAULT_ROOT_PRIV_SET;
+  }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
index d715517..9e13bd7 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
@@ -1778,7 +1778,7 @@
     TestCaseUtils.initializeTestBackend(true);
 
     InternalClientConnection conn =
-         new InternalClientConnection(new AuthenticationInfo());
+         InternalClientConnection.getRootConnection();
 
     String attr = "ds-cfg-bind-with-dn-requires-password";
     ArrayList<Modification> mods = new ArrayList<Modification>();
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
new file mode 100644
index 0000000..2fbf660
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -0,0 +1,1166 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types;
+
+
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.backends.task.Task;
+import org.opends.server.backends.task.TaskBackend;
+import org.opends.server.backends.task.TaskState;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.CompareOperation;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.SchemaConfigManager;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.tools.LDAPModify;
+import org.opends.server.tools.LDAPPasswordModify;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AuthenticationInfo;
+import org.opends.server.types.DN;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.RDN;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchScope;
+
+import static org.testng.Assert.*;
+
+
+
+/**
+ * This class provides a set of test cases for the Directory Server privilege
+ * subsystem.
+ *
+ * FIXME -- It will likely be necessary to also have access control rules in
+ *          place to allow operations as necessary once that functionality has
+ *          integrated into the server.
+ */
+public class PrivilegeTestCase
+       extends TypesTestCase
+{
+  /**
+   * The DN of the user that is associated with the internal root connection.
+   */
+  private static final String INTERNAL_ROOT_DN =
+       "cn=Internal Client,cn=Root DNs,cn=config";
+
+
+
+  // An array of boolean values that indicates whether config read operations
+  // should be successful for users in the corresponding slots of the
+  // connections array.
+  private boolean[] successful;
+
+  // The set of client connections that should be used when performing
+  // operations.
+  private InternalClientConnection[] connections;
+
+
+
+  /**
+   * Make sure that the server is running and that an appropriate set of
+   * structures are in place.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @BeforeClass()
+  public void setUp()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntries(
+      "dn: cn=Unprivileged Root,cn=Root DNs,cn=config",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "objectClass: ds-cfg-root-dn",
+      "cn: Unprivileged Root",
+      "givenName: Unprivileged",
+      "sn: Root",
+      "userPassword: password",
+      "ds-privilege-name: -config-read",
+      "ds-privilege-name: -config-write",
+      "ds-privilege-name: -password-reset",
+      "ds-privilege-name: -update-schema",
+      "ds-privilege-name: -ldif-import",
+      "ds-privilege-name: -ldif-export",
+      "ds-privilege-name: -backend-backup",
+      "ds-privilege-name: -backend-restore",
+      "",
+      "dn: cn=Privileged User,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "cn: Privileged User",
+      "givenName: Privileged",
+      "sn: User",
+      "userPassword: password",
+      "ds-privilege-name: config-read",
+      "ds-privilege-name: config-write",
+      "ds-privilege-name: password-reset",
+      "ds-privilege-name: update-schema",
+      "ds-privilege-name: ldif-import",
+      "ds-privilege-name: ldif-export",
+      "ds-privilege-name: backend-backup",
+      "ds-privilege-name: backend-restore",
+      "",
+      "dn: cn=Unprivileged User,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "cn: Unprivileged User",
+      "givenName: Unprivileged",
+      "sn: User",
+      "userPassword: password",
+      "",
+      "dn: cn=PWReset Target,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "cn: PWReset Target",
+      "givenName: PWReset",
+      "sn: Target",
+      "userPassword: password");
+
+// FIXME -- It will likely be necessary to also have access control rules in
+//          place to allow operations as necessary once that functionality has
+//          integrated into the server.
+
+
+    // Build the array of connections we will use to perform the tests.
+    ArrayList<InternalClientConnection> connList =
+         new ArrayList<InternalClientConnection>();
+    ArrayList<Boolean> successList = new ArrayList<Boolean>();
+
+    connList.add(new InternalClientConnection(new AuthenticationInfo()));
+    successList.add(false);
+
+    connList.add(InternalClientConnection.getRootConnection());
+    successList.add(true);
+
+    String userDN = "cn=Directory Manager,cn=Root DNs,cn=config";
+    Entry userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+    AuthenticationInfo authInfo = new AuthenticationInfo(userEntry, true);
+    connList.add(new InternalClientConnection(authInfo));
+    successList.add(true);
+
+    userDN    = "cn=Unprivileged Root,cn=Root DNs,cn=config";
+    userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+    authInfo  = new AuthenticationInfo(userEntry, true);
+    connList.add(new InternalClientConnection(authInfo));
+    successList.add(false);
+
+    userDN    = "cn=Unprivileged User,o=test";
+    userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+    authInfo  = new AuthenticationInfo(userEntry, false);
+    connList.add(new InternalClientConnection(authInfo));
+    successList.add(false);
+
+    userDN    = "cn=Privileged User,o=test";
+    userEntry = DirectoryServer.getEntry(DN.decode(userDN));
+    authInfo  = new AuthenticationInfo(userEntry, false);
+    connList.add(new InternalClientConnection(authInfo));
+    successList.add(true);
+
+
+    connections = new InternalClientConnection[connList.size()];
+    successful  = new boolean[connections.length];
+    for (int i=0; i < connections.length; i++)
+    {
+      connections[i] = connList.get(i);
+      successful[i]  = successList.get(i);
+    }
+  }
+
+
+
+  /**
+   * Cleans up anything that might be left around after running the tests in
+   * this class.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @AfterClass()
+  public void cleanUp()
+         throws Exception
+  {
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    DeleteOperation deleteOperation =
+         conn.processDelete(
+              DN.decode("cn=Unprivileged Root,cn=Root DNs,cn=config"));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Retrieves a set of data that can be used for performing the tests.  The
+   * arguments generated for each method will be:
+   * <OL>
+   *   <LI>A client connection to use to perform the operation</LI>
+   *   <LI>A flag indicating whether or not the operation should succeed</LI>
+   * </OL>
+   *
+   * @return  A set of data that can be used for performing the tests.
+   */
+  @DataProvider(name = "testdata")
+  public Object[][] getTestData()
+  {
+    Object[][] returnArray = new Object[connections.length][2];
+    for (int i=0; i < connections.length; i++)
+    {
+      returnArray[i][0] = connections[i];
+      returnArray[i][1] = successful[i];
+    }
+
+    return returnArray;
+  }
+
+
+
+  /**
+   * Tests to ensure that search operations in the server configuration properly
+   * respect the CONFIG_READ privilege.
+   *
+   * @param  conn          The client connection to use to perform the search
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the CONFIG_READ privilege and therefore the
+   *                       search should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testConfigReadSearch(InternalClientConnection conn,
+                                   boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.CONFIG_READ, null), hasPrivilege);
+
+    InternalSearchOperation searchOperation =
+         conn.processSearch(DN.decode("cn=config"), SearchScope.BASE_OBJECT,
+              SearchFilter.createFilterFromString("(objectClass=*)"));
+    if (hasPrivilege)
+    {
+      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(searchOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that compare operations in the server configuration
+   * properly respect the CONFIG_READ privilege.
+   *
+   * @param  conn          The client connection to use to perform the compare
+   *                       operation.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the CONFIG_READ privilege and therefore the
+   *                       compare should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testConfigReadCompare(InternalClientConnection conn,
+                                    boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.CONFIG_READ, null), hasPrivilege);
+
+    CompareOperation compareOperation =
+         conn.processCompare(DN.decode("cn=config"),
+                             DirectoryServer.getAttributeType("cn"),
+                             ByteStringFactory.create("config"));
+    if (hasPrivilege)
+    {
+      assertEquals(compareOperation.getResultCode(), ResultCode.COMPARE_TRUE);
+    }
+    else
+    {
+      assertEquals(compareOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that add and delete operations in the server configuration
+   * properly respect the CONFIG_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 CONFIG_WRITE privilege and therefore the
+   *                       operations should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testConfigWriteAddAndDelete(InternalClientConnection conn,
+                                          boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.CONFIG_WRITE, null), hasPrivilege);
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: cn=Test Root,cn=Root DNs,cn=config",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "objectClass: ds-cfg-root-dn",
+      "cn: Test Root",
+      "givenName: Test",
+      "sn: Root",
+      "userPassword: password");
+
+    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=Telex Number,cn=Syntaxes,cn=config"));
+      assertEquals(deleteOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that modify operations in the server configuration
+   * properly respect the CONFIG_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 CONFIG_WRITE privilege and therefore the
+   *                       modify should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testConfigWriteModify(InternalClientConnection conn,
+                                    boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.CONFIG_WRITE, null), hasPrivilege);
+
+    ArrayList<Modification> mods = new ArrayList<Modification>();
+
+    mods.add(new Modification(ModificationType.REPLACE,
+                              new Attribute("ds-cfg-size-limit", "2000")));
+
+    ModifyOperation modifyOperation =
+         conn.processModify(DN.decode("cn=config"), mods);
+    if (hasPrivilege)
+    {
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+      mods.clear();
+      mods.add(new Modification(ModificationType.REPLACE,
+                              new Attribute("ds-cfg-size-limit", "1000")));
+
+      modifyOperation = conn.processModify(DN.decode("cn=config"), mods);
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(modifyOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that modify DN operations in the server configuration
+   * properly respect the CONFIG_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 CONFIG_WRITE privilege and therefore the
+   *                       modify DN should succeed (or at least get past the
+   *                       privilege check, only to fail because we don't
+   *                       support modify DN in the server configuration).
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testConfigWriteModifyDN(InternalClientConnection conn,
+                                      boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.CONFIG_WRITE, null), hasPrivilege);
+
+    ModifyDNOperation modifyDNOperation =
+         conn.processModifyDN(DN.decode("cn=Work Queue,cn=config"),
+                              RDN.decode("cn=New RDN for Work Queue"), true,
+                              null);
+    if (hasPrivilege)
+    {
+      // We don't support modify DN operations in the server configuration, but
+      // at least we need to make sure we're getting past the privilege check.
+      assertEquals(modifyDNOperation.getResultCode(),
+                   ResultCode.UNWILLING_TO_PERFORM);
+    }
+    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.
+   *
+   * @param  conn          The client connection to use to perform the password
+   *                       reset.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the PASSWORD_RESET privilege and therefore
+   *                       the password reset should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testPasswordResetModify(InternalClientConnection conn,
+                                      boolean hasPrivilege)
+         throws Exception
+  {
+    // We've got to do this as an external operation rather than internal, so
+    // get the bind DN and password to use from the client connection.
+
+    String userDN;
+    String userPassword;
+    Entry  authNEntry = conn.getAuthenticationInfo().getAuthenticationEntry();
+    if (authNEntry == null)
+    {
+      userDN       = "";
+      userPassword = "";
+    }
+    else if (authNEntry.getDN().toString().equalsIgnoreCase(INTERNAL_ROOT_DN))
+    {
+      return;
+    }
+    else
+    {
+      userDN       = authNEntry.getDN().toString();
+      userPassword = "password";
+    }
+
+    assertEquals(conn.hasPrivilege(Privilege.PASSWORD_RESET, null),
+                 hasPrivilege);
+
+    String path = TestCaseUtils.createTempFile(
+      "dn: cn=PWReset Target,o=test",
+      "changetype: modify",
+      "replace: userPassword",
+      "userPassword: newpassword",
+      "",
+      "dn: cn=PWReset Target,o=test",
+      "changetype: modify",
+      "replace: userPassword",
+      "userPassword: password");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", userDN,
+      "-w", userPassword,
+      "-f", path
+    };
+
+    int resultCode = LDAPModify.mainModify(args, false, null, null);
+    if (hasPrivilege)
+    {
+      assertEquals(resultCode, 0);
+    }
+    else
+    {
+      assertEquals(resultCode, 50);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that password modify extended operations which attempt to
+   * reset a user's password properly respect the PASSWORD_RESET privilege.
+   *
+   * @param  conn          The client connection to use to perform the password
+   *                       reset.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the PASSWORD_RESET privilege and therefore
+   *                       the password reset should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testPasswordResetExtOp(InternalClientConnection conn,
+                                     boolean hasPrivilege)
+         throws Exception
+  {
+    // We've got to do this as an external operation rather than internal, so
+    // get the bind DN and password to use from the client connection.
+
+    String userDN;
+    String userPassword;
+    Entry  authNEntry = conn.getAuthenticationInfo().getAuthenticationEntry();
+    if (authNEntry == null)
+    {
+      userDN       = "";
+      userPassword = "";
+    }
+    else if (authNEntry.getDN().toString().equalsIgnoreCase(INTERNAL_ROOT_DN))
+    {
+      return;
+    }
+    else
+    {
+      userDN       = authNEntry.getDN().toString();
+      userPassword = "password";
+    }
+
+    assertEquals(conn.hasPrivilege(Privilege.PASSWORD_RESET, null),
+                 hasPrivilege);
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", userDN,
+      "-w", userPassword,
+      "-a", "dn:cn=PWReset Target,o=test",
+      "-n", "newpassword"
+    };
+
+    int resultCode =
+             LDAPPasswordModify.mainPasswordModify(args, false, null, null);
+    if (hasPrivilege)
+    {
+      assertEquals(resultCode, 0);
+
+      args = new String[]
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", userDN,
+        "-w", userPassword,
+        "-a", "dn:cn=PWReset Target,o=test",
+        "-n", "password"
+      };
+      assertEquals(LDAPPasswordModify.mainPasswordModify(args, false, null,
+                                                         null), 0);
+    }
+    else
+    {
+      assertEquals(resultCode, 50);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that attempts to update the schema with a modify operation
+   * will properly respect the UPDATE_SCHEMA privilege.
+   *
+   * @param  conn          The client connection to use to perform the schema
+   *                       update.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the UPDATE_SCHEMA privilege and therefore
+   *                       the schema update should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testUpdateSchemaModify(InternalClientConnection conn,
+                               boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.UPDATE_SCHEMA, null),
+                 hasPrivilege);
+
+    String attrDefinition =
+         "( testupdateschemaat-oid NAME 'testUpdateSchemaAT' " +
+         "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+         "X-ORIGIN 'PrivilegeTestCase' )";
+
+    ArrayList<Modification> mods = new ArrayList<Modification>();
+
+    mods.add(new Modification(ModificationType.ADD,
+                              new Attribute("attributetypes", attrDefinition)));
+
+    ModifyOperation modifyOperation =
+         conn.processModify(DN.decode("cn=schema"), mods);
+    if (hasPrivilege)
+    {
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+      mods.clear();
+      mods.add(new Modification(ModificationType.DELETE,
+                        new Attribute("attributetypes", attrDefinition)));
+
+      modifyOperation = conn.processModify(DN.decode("cn=schema"), mods);
+      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+    }
+    else
+    {
+      assertEquals(modifyOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that attempts to update the schema with an add schema file
+   * task will properly respect the UPDATE_SCHEMA privilege.
+   *
+   * @param  conn          The client connection to use to perform the schema
+   *                       update.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the UPDATE_SCHEMA privilege and therefore
+   *                       the schema update should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testUpdateSchemaAddSchemaFile(InternalClientConnection conn,
+                                            boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.UPDATE_SCHEMA, null),
+                 hasPrivilege);
+
+
+    String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath();
+
+    String identifier;
+    Entry authNEntry = conn.getAuthenticationInfo().getAuthenticationEntry();
+    if (authNEntry == null)
+    {
+      identifier = "null";
+    }
+    else
+    {
+      identifier = authNEntry.getDN().toString();
+      identifier = identifier.replace(',', '-');
+      identifier = identifier.replace(' ', '-');
+      identifier = identifier.replace('=', '-');
+    }
+
+    String[] fileLines =
+    {
+      "dn: cn=schema",
+      "objectClass: top",
+      "objectClass: ldapSubentry",
+      "objectClass: subschema",
+      "attributeTypes: ( " + identifier.toLowerCase() + "-oid " +
+           "NAME '" + identifier + "' )"
+    };
+
+    File validFile = new File(schemaDirectory, "05-" + identifier + ".ldif");
+    BufferedWriter writer = new BufferedWriter(new FileWriter(validFile));
+    for (String line : fileLines)
+    {
+      writer.write(line);
+      writer.newLine();
+    }
+    writer.close();
+
+    Entry taskEntry = TestCaseUtils.makeEntry(
+      "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+      "objectClass: top",
+      "objectClass: ds-task",
+      "objectClass: ds-task-add-schema-file",
+      "ds-task-class-name: org.opends.server.tasks.AddSchemaFileTask",
+      "ds-task-schema-file-name: 05-" + identifier + ".ldif");
+
+    AddOperation addOperation =
+         conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+                         taskEntry.getUserAttributes(),
+                         taskEntry.getOperationalAttributes());
+
+    if (hasPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+      Task task = getCompletedTask(taskEntry.getDN());
+      assertNotNull(task);
+      assertTrue(TaskState.isSuccessful(task.getTaskState()));
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that attempts to backup the Directory Server backends
+   * will properly respect the BACKEND_BACKUP privilege.
+   *
+   * @param  conn          The client connection to use to perform the backup.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the BACKEND_BACKUP privilege and therefore
+   *                       the backup should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata", groups = { "slow" })
+  public void testBackupBackend(InternalClientConnection conn,
+                                boolean hasPrivilege)
+         throws Exception
+  {
+    // We have to sleep here because the backup ID that gets generated will be
+    // based on a timestamp and we don't want two in the same second.
+    Thread.sleep(1100);
+
+    assertEquals(conn.hasPrivilege(Privilege.BACKEND_BACKUP, null),
+                 hasPrivilege);
+
+    Entry taskEntry = TestCaseUtils.makeEntry(
+      "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+      "objectclass: top",
+      "objectclass: ds-task",
+      "objectclass: ds-task-backup",
+      "ds-task-class-name: org.opends.server.tasks.BackupTask",
+      "ds-backup-directory-path: bak",
+      "ds-task-backup-all: TRUE");
+
+    AddOperation addOperation =
+         conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+                         taskEntry.getUserAttributes(),
+                         taskEntry.getOperationalAttributes());
+
+    if (hasPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+      Task task = getCompletedTask(taskEntry.getDN());
+      assertNotNull(task);
+      assertTrue(TaskState.isSuccessful(task.getTaskState()));
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that attempts to restore the Directory Server backends
+   * will properly respect the BACKEND_RESTORE privilege.
+   *
+   * @param  conn          The client connection to use to perform the restore.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the BACKEND_RESTORE privilege and therefore
+   *                       the restore should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(enabled = false, dataProvider = "testdata", groups = { "slow" },
+        dependsOnMethods = { "testBackupBackend" })
+  public void testRestoreBackend(InternalClientConnection conn,
+                                 boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.BACKEND_RESTORE, null),
+                 hasPrivilege);
+
+    Entry taskEntry = TestCaseUtils.makeEntry(
+      "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+      "objectclass: top",
+      "objectclass: ds-task",
+      "objectclass: ds-task-restore",
+      "ds-task-class-name: org.opends.server.tasks.RestoreTask",
+      "ds-backup-directory-path: bak" + File.separator + "userRoot");
+
+    AddOperation addOperation =
+         conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+                         taskEntry.getUserAttributes(),
+                         taskEntry.getOperationalAttributes());
+
+    if (hasPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+      Task task = getCompletedTask(taskEntry.getDN());
+      assertNotNull(task);
+      assertTrue(TaskState.isSuccessful(task.getTaskState()));
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that attempts to export the contents of a Directory Server
+   * backend will properly respect the LDIF_EXPORT privilege.
+   *
+   * @param  conn          The client connection to use to perform the export.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the LDIF_EXPORT privilege and therefore
+   *                       the export should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata", groups = { "slow" })
+  public void testLDIFExport(InternalClientConnection conn,
+                             boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.LDIF_EXPORT, null), hasPrivilege);
+
+    File   tempFile     = File.createTempFile("export-", ".ldif");
+    String tempFilePath = tempFile.getAbsolutePath();
+    tempFile.delete();
+
+    Entry taskEntry = TestCaseUtils.makeEntry(
+      "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+      "objectclass: top",
+      "objectclass: ds-task",
+      "objectclass: ds-task-export",
+      "ds-task-class-name: org.opends.server.tasks.ExportTask",
+      "ds-task-export-backend-id: userRoot",
+      "ds-task-export-ldif-file: " + tempFilePath);
+
+    AddOperation addOperation =
+         conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+                         taskEntry.getUserAttributes(),
+                         taskEntry.getOperationalAttributes());
+
+    if (hasPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+      Task task = getCompletedTask(taskEntry.getDN());
+      assertNotNull(task);
+      assertTrue(TaskState.isSuccessful(task.getTaskState()));
+
+      tempFile.delete();
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that attempts to import into a Directory Server backend
+   * will properly respect the LDIF_IMPORT privilege.
+   *
+   * @param  conn          The client connection to use to perform the import.
+   * @param  hasPrivilege  Indicates whether the authenticated user is expected
+   *                       to have the LDIF_IMPORT privilege and therefore
+   *                       the import should succeed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata", groups = { "slow" })
+  public void testLDIFImport(InternalClientConnection conn,
+                             boolean hasPrivilege)
+         throws Exception
+  {
+    assertEquals(conn.hasPrivilege(Privilege.LDIF_IMPORT, null), hasPrivilege);
+
+    String path = TestCaseUtils.createTempFile(
+      "dn: dc=example,dc=com",
+      "objectClass: top",
+      "objectClass: domain",
+      "dc: example");
+
+    Entry taskEntry = TestCaseUtils.makeEntry(
+      "dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
+      "objectclass: top",
+      "objectclass: ds-task",
+      "objectclass: ds-task-import",
+      "ds-task-class-name: org.opends.server.tasks.ImportTask",
+      "ds-task-import-backend-id: userRoot",
+      "ds-task-import-ldif-file: " + path);
+
+    AddOperation addOperation =
+         conn.processAdd(taskEntry.getDN(), taskEntry.getObjectClasses(),
+                         taskEntry.getUserAttributes(),
+                         taskEntry.getOperationalAttributes());
+
+    if (hasPrivilege)
+    {
+      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+      Task task = getCompletedTask(taskEntry.getDN());
+      assertNotNull(task);
+      assertTrue(TaskState.isSuccessful(task.getTaskState()));
+    }
+    else
+    {
+      assertEquals(addOperation.getResultCode(),
+                   ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+    }
+  }
+
+
+
+  /**
+   * Tests the ability to update the set of privileges for a user on the fly
+   * and have them take effect immediately.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testUpdateUserPrivileges()
+         throws Exception
+  {
+    InternalClientConnection rootConnection =
+         InternalClientConnection.getRootConnection();
+
+    TestCaseUtils.addEntry(
+      "dn: cn=Test User,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "cn: Test User",
+      "givenName: Test",
+      "sn: User",
+      "userPassword: password");
+
+    Entry testEntry =
+               DirectoryServer.getEntry(DN.decode("cn=Test User,o=test"));
+    AuthenticationInfo authInfo = new AuthenticationInfo(testEntry, false);
+    InternalClientConnection testConnection =
+         new InternalClientConnection(authInfo);
+
+
+    // Make sure the user starts out without any privileges.
+    for (Privilege p : Privilege.values())
+    {
+      assertFalse(testConnection.hasPrivilege(p, null));
+    }
+
+
+    // Modify the user entry to add the CONFIG_READ privilege and verify that
+    // the client connection reflects that.
+    ArrayList<Modification> mods = new ArrayList<Modification>();
+    mods.add(new Modification(ModificationType.ADD,
+                      new Attribute("ds-privilege-name", "config-read")));
+    ModifyOperation modifyOperation =
+         rootConnection.processModify(DN.decode("cn=Test User,o=test"), mods);
+    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+    assertTrue(testConnection.hasPrivilege(Privilege.CONFIG_READ, null));
+
+
+    // Take the privilege away from the user and verify that it is recognized
+    // immediately.
+    mods.clear();
+    mods.add(new Modification(ModificationType.DELETE,
+                      new Attribute("ds-privilege-name", "config-read")));
+    modifyOperation =
+         rootConnection.processModify(DN.decode("cn=Test User,o=test"), mods);
+    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+    assertFalse(testConnection.hasPrivilege(Privilege.CONFIG_READ, null));
+
+
+    DeleteOperation deleteOperation =
+         rootConnection.processDelete(DN.decode("cn=Test User,o=test"));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests the ability to update the set of root privileges and have them take
+   * effect immediately for new root connections.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testUpdateRootPrivileges()
+         throws Exception
+  {
+    // Make sure that a root connection doesn't  have the proxied auth
+    // privilege.
+    DN unprivRootDN = DN.decode("cn=Unprivileged Root,cn=Root DNs,cn=config");
+    Entry unprivRootEntry = DirectoryServer.getEntry(unprivRootDN);
+    AuthenticationInfo authInfo = new AuthenticationInfo(unprivRootEntry, true);
+    InternalClientConnection unprivRootConn =
+         new InternalClientConnection(authInfo);
+    assertFalse(unprivRootConn.hasPrivilege(Privilege.PROXIED_AUTH, null));
+
+
+    // Update the set of root privileges to include proxied auth.
+    InternalClientConnection internalRootConn =
+         InternalClientConnection.getRootConnection();
+
+    ArrayList<Modification> mods = new ArrayList<Modification>();
+    mods.add(new Modification(ModificationType.ADD,
+                      new Attribute("ds-cfg-default-root-privilege-name",
+                                    "proxied-auth")));
+    ModifyOperation modifyOperation =
+         internalRootConn.processModify(DN.decode("cn=Root DNs,cn=config"),
+                                        mods);
+    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+
+    // Get a new root connection and verify that it now has proxied auth.
+    unprivRootEntry = DirectoryServer.getEntry(unprivRootDN);
+    authInfo = new AuthenticationInfo(unprivRootEntry, true);
+    unprivRootConn = new InternalClientConnection(authInfo);
+    assertTrue(unprivRootConn.hasPrivilege(Privilege.PROXIED_AUTH, null));
+
+
+    // Update the set of root privileges to revoke proxied auth.
+    mods.clear();
+    mods.add(new Modification(ModificationType.DELETE,
+                      new Attribute("ds-cfg-default-root-privilege-name",
+                                    "proxied-auth")));
+    modifyOperation =
+         internalRootConn.processModify(DN.decode("cn=Root DNs,cn=config"),
+                                        mods);
+    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+
+
+    // Get a new root connection and verify that it no longer has proxied auth.
+    unprivRootEntry = DirectoryServer.getEntry(unprivRootDN);
+    authInfo = new AuthenticationInfo(unprivRootEntry, true);
+    unprivRootConn = new InternalClientConnection(authInfo);
+    assertFalse(unprivRootConn.hasPrivilege(Privilege.PROXIED_AUTH, null));
+  }
+
+
+
+  /**
+   * Retrieves the specified task from the server, waiting for it to finish all
+   * the running its going to do before returning.
+   *
+   * @param  taskEntryDN  The DN of the entry for the task to retrieve.
+   *
+   * @return  The requested task entry.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  private Task getCompletedTask(DN taskEntryDN)
+          throws Exception
+  {
+    TaskBackend taskBackend =
+         (TaskBackend) DirectoryServer.getBackend(DN.decode("cn=tasks"));
+    Task task = taskBackend.getScheduledTask(taskEntryDN);
+    if (task == null)
+    {
+      long stopWaitingTime = System.currentTimeMillis() + 10000L;
+      while ((task == null) && (System.currentTimeMillis() < stopWaitingTime))
+      {
+        Thread.sleep(10);
+        task = taskBackend.getScheduledTask(taskEntryDN);
+      }
+    }
+
+    if (task == null)
+    {
+      throw new AssertionError("There is no such task " +
+                               taskEntryDN.toString());
+    }
+
+    if (! TaskState.isDone(task.getTaskState()))
+    {
+      long stopWaitingTime = System.currentTimeMillis() + 20000L;
+      while ((! TaskState.isDone(task.getTaskState())) &&
+             (System.currentTimeMillis() < stopWaitingTime))
+      {
+        Thread.sleep(10);
+      }
+    }
+
+    if (! TaskState.isDone(task.getTaskState()))
+    {
+      throw new AssertionError("Task " + taskEntryDN.toString() +
+                               " did not complete in a timely manner.");
+    }
+
+    return task;
+  }
+}
+

--
Gitblit v1.10.0