From 785fcca7ef16dd93aaa3ca22e17a812ab6ac250a Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Wed, 11 Jul 2007 22:25:46 +0000
Subject: [PATCH] Update the password policy configuration to support a new attribute, ds-cfg-state-update-failure-policy.  This attribute makes it possible to control how the server should handle failures that may occur when attempting to update password policy state information during a bind operation.  This attribute allows the following values:

---
 opends/resource/schema/02-config.ldif                                                             |    9 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java |   76 +++++++-
 opends/src/server/org/opends/server/core/PasswordPolicyState.java                                 |   23 ++
 opends/src/admin/defn/org/opends/server/admin/std/PasswordPolicyConfiguration.xml                 |   53 +++++
 opends/src/server/org/opends/server/core/PasswordPolicy.java                                      |   24 ++
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java       |  286 +++++++++++++++++++++++++++++++
 opends/resource/config/config.ldif                                                                |    2 
 opends/src/server/org/opends/server/messages/CoreMessages.java                                    |   17 +
 8 files changed, 471 insertions(+), 19 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 1efaf92..7d1a76c 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -1072,6 +1072,7 @@
 ds-cfg-require-secure-authentication: false
 ds-cfg-require-secure-password-changes: false
 ds-cfg-skip-validation-for-administrators: false
+ds-cfg-state-update-failure-policy: reactive
 
 dn: cn=Root Password Policy,cn=Password Policies,cn=config
 objectClass: top
@@ -1099,6 +1100,7 @@
 ds-cfg-require-secure-authentication: false
 ds-cfg-require-secure-password-changes: false
 ds-cfg-skip-validation-for-administrators: false
+ds-cfg-state-update-failure-policy: ignore
 
 dn: cn=Password Storage Schemes,cn=config
 objectClass: top
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 201b264..d3f874a 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -1475,6 +1475,9 @@
   NAME 'ds-cfg-strip-syntax-minimum-upper-bound'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
   X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.443
+  NAME 'ds-cfg-state-update-failure-policy'
+  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 )
@@ -1602,7 +1605,7 @@
   X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.23 NAME 'ds-cfg-access-logger'
   SUP ds-cfg-logger
-  STRUCTURAL MAY ( ds-cfg-suppress-internal-operations $ 
+  STRUCTURAL MAY ( ds-cfg-suppress-internal-operations $
   ds-cfg-suppress-synchronization-operations )
   X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.24 NAME 'ds-cfg-error-logger'
@@ -1803,8 +1806,8 @@
   ds-cfg-password-validator-dn $ ds-cfg-previous-last-login-time-format $
   ds-cfg-require-change-by-time $ ds-cfg-require-secure-authentication $
   ds-cfg-require-secure-password-changes $
-  ds-cfg-skip-validation-for-administrators )
-  X-ORIGIN 'OpenDS Directory Server' )
+  ds-cfg-skip-validation-for-administrators $
+  ds-cfg-state-update-failure-policy ) X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.63 NAME
   'ds-cfg-jmx-connection-handler' SUP ds-cfg-connection-handler
   STRUCTURAL MUST ( ds-cfg-listen-port $ ds-cfg-ssl-cert-nickname $
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/PasswordPolicyConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/PasswordPolicyConfiguration.xml
index 70a2c3d..933f525 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/PasswordPolicyConfiguration.xml
+++ b/opends/src/admin/defn/org/opends/server/admin/std/PasswordPolicyConfiguration.xml
@@ -944,4 +944,57 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+
+  <adm:property name="state-update-failure-policy" mandatory="false"
+  multi-valued="false">
+    <adm:synopsis>
+      Specifies how the server should deal with the inability to update password
+      policy state information during an authentication attempt.  In particular,
+      it may be used to control whether an otherwise successful bind operation
+      should fail if a failure occurs while attempting to update password policy
+      state information (e.g., to clear a record of previous authentication
+      failures or to update the last login time), or even whether to reject a
+      bind request if it is known aheaed of time that it will not be possible to
+      update the authentication failure times in the event of an unsuccessful
+      bind attempt (e.g., if the backend writability mode is disabled).
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>reactive</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:enumeration>
+        <adm:value name="ignore">
+          <adm:synopsis>
+            If a bind attempt would otherwise be successful, then do not reject
+            it if a problem occurs while attempting to update the password
+            policy state information for the user.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="reactive">
+          <adm:synopsis>
+            Even if a bind attempt would otherwise be successful, reject it if a
+            problem occurs while attempting to update the password policy state
+            information for the user.
+          </adm:synopsis>
+        </adm:value>
+        <adm:value name="proactive">
+          <adm:synopsis>
+            Proactively reject any bind attempt if it is known ahead of time
+            that it would not be possible to update the user's password policy
+            state information.
+          </adm:synopsis>
+        </adm:value>
+      </adm:enumeration>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.443</ldap:oid>
+        <ldap:name>ds-cfg-state-update-failure-policy</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
 </adm:managed-object>
+
diff --git a/opends/src/server/org/opends/server/core/PasswordPolicy.java b/opends/src/server/org/opends/server/core/PasswordPolicy.java
index 14fea7d..6e5eb90 100644
--- a/opends/src/server/org/opends/server/core/PasswordPolicy.java
+++ b/opends/src/server/org/opends/server/core/PasswordPolicy.java
@@ -39,6 +39,7 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 
+import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
 import org.opends.server.admin.std.server.PasswordPolicyCfg;
 import org.opends.server.admin.std.server.PasswordValidatorCfg;
 import org.opends.server.api.AccountStatusNotificationHandler;
@@ -215,6 +216,11 @@
   private CopyOnWriteArrayList<String> previousLastLoginTimeFormats =
        new CopyOnWriteArrayList<String>();
 
+  // The state update failure policy.
+  private PasswordPolicyCfgDefn.StateUpdateFailurePolicy
+       stateUpdateFailurePolicy =
+            PasswordPolicyCfgDefn.StateUpdateFailurePolicy.REACTIVE;
+
 
 
   /**
@@ -801,6 +807,11 @@
     // Get the idle lockout duration.
     this.idleLockoutInterval = (int) configuration.getIdleLockoutInterval();
 
+
+    // Get the state update failure policy.
+    this.stateUpdateFailurePolicy = configuration.getStateUpdateFailurePolicy();
+
+
     /*
      *  Holistic validation.
      */
@@ -1448,6 +1459,19 @@
 
 
   /**
+   * Retrieves the state update failure policy for this password policy.
+   *
+   * @return  The state update failure policy for this password policy.
+   */
+  public PasswordPolicyCfgDefn.StateUpdateFailurePolicy
+              getStateUpdateFailurePolicy()
+  {
+    return stateUpdateFailurePolicy;
+  }
+
+
+
+  /**
    * Retrieves a string representation of this password policy.
    *
    * @return  A string representation of this password policy.
diff --git a/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index 6c916bf..bd5be3f 100644
--- a/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -39,11 +39,13 @@
 import java.util.List;
 import java.util.Set;
 
+import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
 import org.opends.server.admin.std.server.PasswordValidatorCfg;
 import org.opends.server.api.AccountStatusNotificationHandler;
 import org.opends.server.api.PasswordGenerator;
 import org.opends.server.api.PasswordStorageScheme;
 import org.opends.server.api.PasswordValidator;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.ldap.LDAPAttribute;
@@ -57,9 +59,12 @@
 import org.opends.server.types.AttributeValue;
 import org.opends.server.types.ByteString;
 import org.opends.server.types.ConditionResult;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DirectoryException;
 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.Modification;
 import org.opends.server.types.ModificationType;
 import org.opends.server.types.Operation;
@@ -68,9 +73,8 @@
 import org.opends.server.util.TimeThread;
 
 import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.types.DebugLogLevel;
 import static org.opends.server.messages.CoreMessages.*;
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.util.StaticUtils.*;
@@ -4082,7 +4086,20 @@
       String message = getMessage(msgID, userDNString,
                             String.valueOf(internalModify.getErrorMessage()));
 
-      throw new DirectoryException(resultCode, message, msgID);
+      // If this is a root user, or if the password policy says that we should
+      // ignore these problems, then log a warning message.  Otherwise, cause
+      // the bind to fail.
+      if ((DirectoryServer.isRootDN(userEntry.getDN()) ||
+          (passwordPolicy.getStateUpdateFailurePolicy() ==
+           PasswordPolicyCfgDefn.StateUpdateFailurePolicy.IGNORE)))
+      {
+        logError(ErrorLogCategory.PASSWORD_POLICY,
+                 ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+      }
+      else
+      {
+        throw new DirectoryException(resultCode, message, msgID);
+      }
     }
   }
 }
diff --git a/opends/src/server/org/opends/server/messages/CoreMessages.java b/opends/src/server/org/opends/server/messages/CoreMessages.java
index 54350d9..470fb15 100644
--- a/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6257,6 +6257,18 @@
 
 
   /**
+   * The message ID for the message that will be used if a bind attempt is
+   * rejected because either the entire server or the user's backend has a
+   * writability mode of "disabled" and the server would not be able to update
+   * the authentication failure count or the last login time.  This takes a
+   * single argument, which is the target user DN.
+   */
+  public static final int MSGID_BIND_OPERATION_WRITABILITY_DISABLED =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 628;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined
    * in this class.
    */
@@ -8329,6 +8341,11 @@
     registerMessage(MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND,
                     "Rejecting a simple bind request for user %s because the " +
                     "password policy requires secure authentication");
+    registerMessage(MSGID_BIND_OPERATION_WRITABILITY_DISABLED,
+                    "Rejecting a bind request for user %s because either " +
+                    "the entire server or the user's backend has a " +
+                    "writability mode of 'disabled' and password policy " +
+                    "state updates would not be allowed");
     registerMessage(MSGID_BIND_OPERATION_ACCOUNT_DISABLED,
                     "Rejecting a bind request for user %s because the " +
                     "account has been administrative disabled");
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
index 5208153..e7919f2 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -45,6 +45,7 @@
 import java.util.Map;
 import java.util.concurrent.locks.Lock;
 
+import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
 import org.opends.server.api.AttributeSyntax;
 import org.opends.server.api.Backend;
 import org.opends.server.api.ChangeNotificationListener;
@@ -108,13 +109,14 @@
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
 import org.opends.server.types.ObjectClass;
+import org.opends.server.types.Operation;
 import org.opends.server.types.Privilege;
 import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SynchronizationProviderResult;
-import org.opends.server.types.Operation;
+import org.opends.server.types.WritabilityMode;
 import org.opends.server.util.Validator;
 import org.opends.server.workflowelement.LeafWorkflowElement;
 
@@ -3313,8 +3315,8 @@
             // Check to see if the user has a password.  If not, then fail.
             // FIXME -- We need to have a way to enable/disable debugging.
             pwPolicyState = new PasswordPolicyState(userEntry, false, false);
-            AttributeType pwType
-                 = pwPolicyState.getPolicy().getPasswordAttribute();
+            PasswordPolicy policy = pwPolicyState.getPolicy();
+            AttributeType  pwType = policy.getPasswordAttribute();
 
             List<Attribute> pwAttr = userEntry.getAttribute(pwType);
             if ((pwAttr == null) || (pwAttr.isEmpty()))
@@ -3328,9 +3330,35 @@
             }
 
 
+            // If the password policy is configured to track authentication
+            // failures or keep the last login time and the associated backend
+            // is disabled, then we may need to reject the bind immediately.
+            if ((policy.getStateUpdateFailurePolicy() ==
+                 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
+                ((policy.getLockoutFailureCount() > 0) ||
+                 ((policy.getLastLoginTimeAttribute() != null) &&
+                  (policy.getLastLoginTimeFormat() != null))) &&
+                ((DirectoryServer.getWritabilityMode() ==
+                  WritabilityMode.DISABLED) ||
+                 (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
+            {
+              // This policy isn't applicable to root users, so if it's a root
+              // user then ignore it.
+              if (! DirectoryServer.isRootDN(bindDN))
+              {
+                int    msgID   = MSGID_BIND_OPERATION_WRITABILITY_DISABLED;
+                String message = getMessage(msgID, String.valueOf(bindDN));
+
+                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+                localOp.setAuthFailureReason(msgID, message);
+                break bindProcessing;
+              }
+            }
+
+
             // Check to see if the authentication must be done in a secure
             // manner.  If so, then the client connection must be secure.
-            if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
+            if (policy.requireSecureAuthentication() &&
                 (! clientConnection.isSecure()))
             {
               int    msgID   = MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND;
@@ -3429,8 +3457,7 @@
                 pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
               }
 
-              int maxGraceLogins
-                   = pwPolicyState.getPolicy().getGraceLoginCount();
+              int maxGraceLogins = policy.getGraceLoginCount();
               if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
               {
                 List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
@@ -3731,7 +3758,7 @@
               localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
               localOp.setAuthFailureReason(msgID, message);
 
-              if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
+              if (policy.getLockoutFailureCount() > 0)
               {
                 pwPolicyState.updateAuthFailureTimes();
                 if (pwPolicyState.lockedDueToFailures())
@@ -3895,7 +3922,33 @@
           // regardless of whether the authentication was successful.
           if (pwPolicyState != null)
           {
-            if (pwPolicyState.isDisabled())
+            PasswordPolicy policy = pwPolicyState.getPolicy();
+
+            // If the password policy is configured to track authentication
+            // failures or keep the last login time and the associated backend
+            // is disabled, then we may need to reject the bind immediately.
+            if ((policy.getStateUpdateFailurePolicy() ==
+                 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
+                ((policy.getLockoutFailureCount() > 0) ||
+                 ((policy.getLastLoginTimeAttribute() != null) &&
+                  (policy.getLastLoginTimeFormat() != null))) &&
+                ((DirectoryServer.getWritabilityMode() ==
+                  WritabilityMode.DISABLED) ||
+                 (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
+            {
+              // This policy isn't applicable to root users, so if it's a root
+              // user then ignore it.
+              if (! DirectoryServer.isRootDN(bindDN))
+              {
+                int    msgID   = MSGID_BIND_OPERATION_WRITABILITY_DISABLED;
+                String message = getMessage(msgID, userDNString);
+
+                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+                localOp.setAuthFailureReason(msgID, message);
+                break bindProcessing;
+              }
+            }
+            else if (pwPolicyState.isDisabled())
             {
               localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
 
@@ -3919,7 +3972,7 @@
               break bindProcessing;
             }
 
-            if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
+            if (policy.requireSecureAuthentication() &&
                 (! clientConnection.isSecure()) &&
                 (! saslHandler.isSecure(saslMechanism)))
             {
@@ -3996,8 +4049,7 @@
                   pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
                 }
 
-                int maxGraceLogins
-                     = pwPolicyState.getPolicy().getGraceLoginCount();
+                int maxGraceLogins = policy.getGraceLoginCount();
                 if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
                 {
                   List<Long> graceLoginTimes =
@@ -5472,7 +5524,7 @@
 
 
         // If the operation is not a synchronization operation,
-        // Invoke the pre-operation modify plugins.
+        // Invoke the pre-operation add plugins.
         if (!localOp.isSynchronizationOperation())
         {
           PreOperationPluginResult preOpResult =
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
index b9a23ba..39ac48e 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
@@ -51,6 +51,7 @@
 import org.opends.server.protocols.ldap.BindResponseProtocolOp;
 import org.opends.server.protocols.ldap.LDAPMessage;
 import org.opends.server.protocols.ldap.LDAPResultCode;
+import org.opends.server.tools.LDAPSearch;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.AuthenticationType;
@@ -1904,7 +1905,7 @@
    * cause the connection to no longer be associated with the previous identity.
    * This helps provide coverage for issue #1392.
    *
-   * @throws  Exception
+   * @throws  Exception  If an unexpected problem occurs.
    */
   @Test()
   public void testRebindClearsAuthInfo()
@@ -1962,5 +1963,288 @@
 
     s.close();
   }
+
+
+
+  /**
+   * Tests to ensure that the "ignore" password policy state update policy
+   * works as expected.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testIgnoreStateUpdateFailurePolicy()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    TestCaseUtils.applyModifications(
+      "dn: uid=test.user,o=test",
+      "changetype: add",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "",
+      "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+      "changetype: modify",
+      "replace: ds-cfg-last-login-time-attribute",
+      "ds-cfg-last-login-time-attribute: ds-pwp-last-login-time",
+      "-",
+      "replace: ds-cfg-last-login-time-format",
+      "ds-cfg-last-login-time-format: yyyyMMdd",
+      "-",
+      "replace: ds-cfg-state-update-failure-policy",
+      "ds-cfg-state-update-failure-policy: ignore",
+      "",
+      "dn: cn=config",
+      "changetype: modify",
+      "replace: ds-cfg-writability-mode",
+      "ds-cfg-writability-mode: disabled"
+    );
+
+    try
+    {
+      String[] args =
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", "uid=test.user,o=test",
+        "-w", "password",
+        "-b", "",
+        "-s", "base",
+        "(objectClass=*)"
+      };
+
+      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
+                   0);
+
+      args = new String[]
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", "cn=Directory Manager",
+        "-w", "password",
+        "-b", "",
+        "-s", "base",
+        "(objectClass=*)"
+      };
+
+      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
+                   0);
+    }
+    finally
+    {
+      TestCaseUtils.applyModifications(
+        "dn: cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-writability-mode",
+        "ds-cfg-writability-mode: enabled",
+        "",
+        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-last-login-time-attribute",
+        "-",
+        "replace: ds-cfg-last-login-time-format",
+        "-",
+        "replace: ds-cfg-state-update-failure-policy",
+        "ds-cfg-state-update-failure-policy: reactive"
+      );
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that the "reactive" password policy state update policy
+   * works as expected.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testReactiveStateUpdateFailurePolicy()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    TestCaseUtils.applyModifications(
+      "dn: uid=test.user,o=test",
+      "changetype: add",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "",
+      "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+      "changetype: modify",
+      "replace: ds-cfg-last-login-time-attribute",
+      "ds-cfg-last-login-time-attribute: ds-pwp-last-login-time",
+      "-",
+      "replace: ds-cfg-last-login-time-format",
+      "ds-cfg-last-login-time-format: yyyyMMdd",
+      "-",
+      "replace: ds-cfg-state-update-failure-policy",
+      "ds-cfg-state-update-failure-policy: reactive",
+      "",
+      "dn: cn=config",
+      "changetype: modify",
+      "replace: ds-cfg-writability-mode",
+      "ds-cfg-writability-mode: disabled"
+    );
+
+    try
+    {
+      String[] args =
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", "uid=test.user,o=test",
+        "-w", "password",
+        "-b", "",
+        "-s", "base",
+        "(objectClass=*)"
+      };
+
+      int rc = LDAPSearch.mainSearch(args, false, System.out, System.err);
+      assertFalse(rc == 0);
+      assertFalse(rc == LDAPResultCode.INVALID_CREDENTIALS);
+
+      args = new String[]
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", "cn=Directory Manager",
+        "-w", "password",
+        "-b", "",
+        "-s", "base",
+        "(objectClass=*)"
+      };
+
+      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
+                   0);
+    }
+    finally
+    {
+      TestCaseUtils.applyModifications(
+        "dn: cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-writability-mode",
+        "ds-cfg-writability-mode: enabled",
+        "",
+        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-last-login-time-attribute",
+        "-",
+        "replace: ds-cfg-last-login-time-format",
+        "-",
+        "replace: ds-cfg-state-update-failure-policy",
+        "ds-cfg-state-update-failure-policy: reactive"
+      );
+    }
+  }
+
+
+
+  /**
+   * Tests to ensure that the "proactive" password policy state update policy
+   * works as expected.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testProactiveStateUpdateFailurePolicy()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    TestCaseUtils.applyModifications(
+      "dn: uid=test.user,o=test",
+      "changetype: add",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password",
+      "",
+      "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+      "changetype: modify",
+      "replace: ds-cfg-last-login-time-attribute",
+      "ds-cfg-last-login-time-attribute: ds-pwp-last-login-time",
+      "-",
+      "replace: ds-cfg-last-login-time-format",
+      "ds-cfg-last-login-time-format: yyyyMMdd",
+      "-",
+      "replace: ds-cfg-state-update-failure-policy",
+      "ds-cfg-state-update-failure-policy: proactive",
+      "",
+      "dn: cn=config",
+      "changetype: modify",
+      "replace: ds-cfg-writability-mode",
+      "ds-cfg-writability-mode: disabled"
+    );
+
+    try
+    {
+      String[] args =
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", "uid=test.user,o=test",
+        "-w", "password",
+        "-b", "",
+        "-s", "base",
+        "(objectClass=*)"
+      };
+
+      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
+                   LDAPResultCode.INVALID_CREDENTIALS);
+
+      args = new String[]
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", "cn=Directory Manager",
+        "-w", "password",
+        "-b", "",
+        "-s", "base",
+        "(objectClass=*)"
+      };
+
+      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
+                   0);
+    }
+    finally
+    {
+      TestCaseUtils.applyModifications(
+        "dn: cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-writability-mode",
+        "ds-cfg-writability-mode: enabled",
+        "",
+        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-last-login-time-attribute",
+        "-",
+        "replace: ds-cfg-last-login-time-format",
+        "-",
+        "replace: ds-cfg-state-update-failure-policy",
+        "ds-cfg-state-update-failure-policy: reactive"
+      );
+    }
+  }
 }
 

--
Gitblit v1.10.0