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

neil_a_wilson
09.51.2007 ed39262fa647434d4a0e31f07754a263ce2b16e3
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)

The following privileges are also defined but not yet implemented:
* bypass-acl (allow bypassing access control evaluation)
* modify-acl (allow updating access control definitions)
* jmx-read (allow reading information over JMX)
* jmx-write (allow updating information over JMX)
* jmx-notify (allow subscribing to JMX notifications)
* proxied-auth (allow the use of proxied authorization and SASL authzid)
* disconnect-request (allow terminating arbitrary client connections)
* cancel-request (allow canceling arbitrary client connections)
* search-unindexed (allow requesting unindexed searches)
* data-sync (allow participating in a data synchronization environment)

Root users automatically inherit a subset of these privileges by default, and
users can also be explicitly granted or forbidden the use of specified
privileges.

OpenDS Issue Numbers: 468, 472, 474, 475, 477, 1213
2 files added
29 files modified
2965 ■■■■■ changed files
opends/resource/config/config.ldif 18 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ClientConnection.java 238 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java 16 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/Task.java 38 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskBackend.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskScheduler.java 11 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigConstants.java 17 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperation.java 15 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CompareOperation.java 15 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 23 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperation.java 29 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/RootDNConfigManager.java 312 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java 116 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java 33 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/BackendMessages.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ConfigMessages.java 140 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 86 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 15 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/TaskMessages.java 99 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java 20 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/BackupTask.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/ExportTask.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/ImportTask.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/RestoreTask.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/ShutdownTask.java 41 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/LDAPPasswordModify.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Privilege.java 389 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java 1166 ●●●●● patch | view | raw | blame | history
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
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' )
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.
   *
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
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.
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;
    }
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;
  }
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.
   */
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
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)
      {
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
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.
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);
  }
}
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);
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.
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 " +
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.");
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");
  }
}
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,
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.");
  }
}
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)
    {
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(
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();
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();
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();
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();
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);
        }
      }
    }
  }
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());
opends/src/server/org/opends/server/types/Privilege.java
New file
@@ -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;
  }
}
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>();
opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
New file
@@ -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;
  }
}