From e627deb0b7849bb8119b9566dd093b92ce9a2dbd Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Wed, 02 Aug 2006 20:31:50 +0000
Subject: [PATCH] Add a new account expiration feature to the password policy.  This will make it possible for accounts to be given an expiration time, after which it will not be possible to authenticate as that user or target that user with the proxied authorization control.

---
 opends/resource/schema/02-config.ldif                                  |    3 
 opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java |    6 
 opends/src/server/org/opends/server/core/PasswordPolicyState.java      |  189 ++++++++++++++++++++++++++++++-------
 opends/src/server/org/opends/server/core/BindOperation.java            |   21 +++
 opends/src/server/org/opends/server/core/SearchOperation.java          |    5 
 opends/src/server/org/opends/server/config/ConfigConstants.java        |   10 ++
 opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java |   12 +
 opends/src/server/org/opends/server/messages/CoreMessages.java         |   13 ++
 8 files changed, 214 insertions(+), 45 deletions(-)

diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 39a4141..7165ca2 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -956,6 +956,9 @@
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.277
   NAME 'ds-cfg-backend-import-pass-size' 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.280
+  NAME 'ds-pwp-account-expiration-time' SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+  SINGLE-VALUE USAGE directoryOperation 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 )
diff --git a/opends/src/server/org/opends/server/config/ConfigConstants.java b/opends/src/server/org/opends/server/config/ConfigConstants.java
index 39b19d4..c815930 100644
--- a/opends/src/server/org/opends/server/config/ConfigConstants.java
+++ b/opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -2932,6 +2932,16 @@
 
 
   /**
+   * The name of the operational attribute that may appear in a user's entry to
+   * indicate when that account will expire (and therefore may no longer be used
+   * to authenticate).
+   */
+  public static final String OP_ATTR_ACCOUNT_EXPIRATION_TIME =
+       NAME_PREFIX_PWP + "account-expiration-time";
+
+
+
+  /**
    * The name of the operational attribute that will appear in an entry to
    * indicate when it was created.
    */
diff --git a/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java b/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
index c794534..a3a0947 100644
--- a/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
+++ b/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
@@ -367,9 +367,11 @@
       // processing.
       PasswordPolicyState pwpState = new PasswordPolicyState(userEntry, false,
                                                              false);
-      if (pwpState.isDisabled() || pwpState.lockedDueToFailures() ||
+      if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
+          pwpState.lockedDueToFailures() ||
           pwpState.lockedDueToIdleInterval() ||
-          pwpState.lockedDueToMaximumResetAge() || pwpState.isExpired())
+          pwpState.lockedDueToMaximumResetAge() ||
+          pwpState.isPasswordExpired())
       {
         int    msgID   = MSGID_PROXYAUTH1_UNUSABLE_ACCOUNT;
         String message = getMessage(msgID, String.valueOf(authzDN));
diff --git a/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java b/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
index 199a3a3..05def8a 100644
--- a/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
+++ b/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
@@ -319,9 +319,11 @@
           // processing.
           PasswordPolicyState pwpState =
                new PasswordPolicyState(userEntry, false, false);
-          if (pwpState.isDisabled() || pwpState.lockedDueToFailures() ||
+          if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
+              pwpState.lockedDueToFailures() ||
               pwpState.lockedDueToIdleInterval() ||
-              pwpState.lockedDueToMaximumResetAge() || pwpState.isExpired())
+              pwpState.lockedDueToMaximumResetAge() ||
+              pwpState.isPasswordExpired())
           {
             int    msgID   = MSGID_PROXYAUTH2_UNUSABLE_ACCOUNT;
             String message = getMessage(msgID, String.valueOf(authzDN));
@@ -376,9 +378,11 @@
         // processing.
         PasswordPolicyState pwpState =
              new PasswordPolicyState(userEntry, false, false);
-        if (pwpState.isDisabled() || pwpState.lockedDueToFailures() ||
+        if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
+            pwpState.lockedDueToFailures() ||
             pwpState.lockedDueToIdleInterval() ||
-            pwpState.lockedDueToMaximumResetAge() || pwpState.isExpired())
+            pwpState.lockedDueToMaximumResetAge() ||
+            pwpState.isPasswordExpired())
         {
           int    msgID   = MSGID_PROXYAUTH2_UNUSABLE_ACCOUNT;
           String message = getMessage(msgID, String.valueOf(authzDN));
diff --git a/opends/src/server/org/opends/server/core/BindOperation.java b/opends/src/server/org/opends/server/core/BindOperation.java
index 82544a9..8a0fb50 100644
--- a/opends/src/server/org/opends/server/core/BindOperation.java
+++ b/opends/src/server/org/opends/server/core/BindOperation.java
@@ -1165,6 +1165,15 @@
               setAuthFailureReason(msgID, message);
               break bindProcessing;
             }
+            else if (pwPolicyState.isAccountExpired())
+            {
+              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
+              String message = getMessage(msgID, String.valueOf(bindDN));
+
+              setResultCode(ResultCode.INVALID_CREDENTIALS);
+              setAuthFailureReason(msgID, message);
+              break bindProcessing;
+            }
             else if (pwPolicyState.lockedDueToFailures())
             {
               int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED;
@@ -1211,7 +1220,7 @@
 
             // Determine whether the password is expired, or whether the user
             // should be warned about an upcoming expiration.
-            if (pwPolicyState.isExpired())
+            if (pwPolicyState.isPasswordExpired())
             {
               if (pwPolicyErrorType == null)
               {
@@ -1547,6 +1556,14 @@
               appendErrorMessage(getMessage(msgID, userDNString));
               break bindProcessing;
             }
+            else if (pwPolicyState.isAccountExpired())
+            {
+              setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+              int msgID = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
+              appendErrorMessage(getMessage(msgID, userDNString));
+              break bindProcessing;
+            }
 
             if (pwPolicyState.requireSecureAuthentication() &&
                 (! clientConnection.isSecure()) &&
@@ -1605,7 +1622,7 @@
                 break bindProcessing;
               }
 
-              if (pwPolicyState.isExpired())
+              if (pwPolicyState.isPasswordExpired())
               {
                 if (pwPolicyErrorType == null)
                 {
diff --git a/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index 44df8ff..a164336 100644
--- a/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -102,11 +102,14 @@
   // successful.
   private boolean useGraceLogin;
 
+  // Indicates whether the user's account is expired.
+  private ConditionResult isAccountExpired;
+
   // Indicates whether the user's account is disabled.
   private ConditionResult isDisabled;
 
   // Indicates whether the user's password is expired.
-  private ConditionResult isExpired;
+  private ConditionResult isPasswordExpired;
 
   // Indicates whether the warning to send to the client would be the first
   // warning for the user.
@@ -213,7 +216,8 @@
     currentTime            = TimeThread.getTime();
     modifications          = new LinkedList<Modification>();
     isDisabled             = ConditionResult.UNDEFINED;
-    isExpired              = ConditionResult.UNDEFINED;
+    isAccountExpired       = ConditionResult.UNDEFINED;
+    isPasswordExpired      = ConditionResult.UNDEFINED;
     isFirstWarning         = ConditionResult.UNDEFINED;
     isIdleLocked           = ConditionResult.UNDEFINED;
     isResetLocked          = ConditionResult.UNDEFINED;
@@ -1004,7 +1008,7 @@
             debugMessage(DebugLogCategory.PASSWORD_POLICY,
                          DebugLogSeverity.INFO, CLASS_NAME, "isDisabled",
                          "User " + userDNString +
-                         " is not administratively disabled.");
+                         " is administratively disabled.");
           }
 
           isDisabled = ConditionResult.TRUE;
@@ -1017,7 +1021,7 @@
             debugMessage(DebugLogCategory.PASSWORD_POLICY,
                          DebugLogSeverity.INFO, CLASS_NAME, "isDisabled",
                          "User " + userDNString +
-                         " is administratively disabled.");
+                         " is not administratively disabled.");
           }
 
           isDisabled = ConditionResult.FALSE;
@@ -1145,6 +1149,120 @@
 
 
   /**
+   * Indicates whether the user's account is currently expired.
+   *
+   * @return  <CODE>true</CODE> if the user's account is expired, or
+   *          <CODE>false</CODE> if not.
+   */
+  public boolean isAccountExpired()
+  {
+    assert debugEnter(CLASS_NAME, "isAccountExpired");
+
+    if ((isAccountExpired == null) ||
+        (isAccountExpired == ConditionResult.UNDEFINED))
+    {
+      AttributeType type =
+           DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
+                                            true);
+      try
+      {
+        long expirationTime = getGeneralizedTime(type);
+        if (expirationTime < 0)
+        {
+          // The user doesn't have an expiration time in their entry, so it
+          // can't be expired.
+          if (debug)
+          {
+            debugMessage(DebugLogCategory.PASSWORD_POLICY,
+                         DebugLogSeverity.INFO, CLASS_NAME, "isAccountExpired",
+                         "The account for user " + userDNString +
+                         " is not expired because there is no expiration " +
+                         "time in the user's entry.");
+          }
+
+          isAccountExpired = ConditionResult.FALSE;
+          return false;
+        }
+        else if (expirationTime > currentTime)
+        {
+          // The user does have an expiration time, but it hasn't arrived yet.
+          if (debug)
+          {
+            debugMessage(DebugLogCategory.PASSWORD_POLICY,
+                         DebugLogSeverity.INFO, CLASS_NAME, "isAccountExpired",
+                         "The account for user " + userDNString +
+                         " is not expired because the expiration time has " +
+                         "not yet arrived.");
+          }
+
+          isAccountExpired = ConditionResult.FALSE;
+          return false;
+        }
+        else
+        {
+          // The user does have an expiration time, and it is in the past.
+          if (debug)
+          {
+            debugMessage(DebugLogCategory.PASSWORD_POLICY,
+                         DebugLogSeverity.INFO, CLASS_NAME, "isAccountExpired",
+                         "The account for user " + userDNString +
+                         " is expired because the expiration time in that " +
+                         "account has passed.");
+          }
+
+          isAccountExpired = ConditionResult.TRUE;
+          return true;
+        }
+      }
+      catch (Exception e)
+      {
+        assert debugException(CLASS_NAME, "isAccountExpired", e);
+
+        if (debug)
+        {
+          debugMessage(DebugLogCategory.PASSWORD_POLICY,
+                       DebugLogSeverity.WARNING, CLASS_NAME, "isAccountExpired",
+                       "User " + userDNString +" is considered to have an " +
+                       "expired account because an error occurred " +
+                       "while attempting to make the determination:  " +
+                       stackTraceToSingleLineString(e) + ".");
+        }
+
+        isAccountExpired = ConditionResult.TRUE;
+        return true;
+      }
+    }
+
+
+    if (isAccountExpired == ConditionResult.FALSE)
+    {
+      if (debug)
+      {
+        debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
+                     CLASS_NAME, "isAccountExpired",
+                     "Returning stored result of false for user " +
+                     userDNString);
+      }
+
+      return false;
+    }
+    else
+    {
+      if (debug)
+      {
+        debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
+                     CLASS_NAME, "isAccountExpired",
+                     "Returning stored result of true for user " +
+                     userDNString);
+      }
+
+      return true;
+    }
+  }
+
+
+
+  /**
    * Retrieves the set of times of failed authentication attempts for the user.
    *
    * @return  The set of times of failed authentication attempts for the user.
@@ -2339,11 +2457,11 @@
 
       if (expirationTime == Long.MAX_VALUE)
       {
-        expirationTime   = -1;
-        shouldWarn       = ConditionResult.FALSE;
-        isFirstWarning   = ConditionResult.FALSE;
-        isExpired        = ConditionResult.FALSE;
-        mayUseGraceLogin = ConditionResult.TRUE;
+        expirationTime    = -1;
+        shouldWarn        = ConditionResult.FALSE;
+        isFirstWarning    = ConditionResult.FALSE;
+        isPasswordExpired = ConditionResult.FALSE;
+        mayUseGraceLogin  = ConditionResult.TRUE;
       }
       else if (checkWarning)
       {
@@ -2357,9 +2475,9 @@
           {
             // The warning time is in the future, so we know the password isn't
             // expired.
-            shouldWarn     = ConditionResult.FALSE;
-            isFirstWarning = ConditionResult.FALSE;
-            isExpired      = ConditionResult.FALSE;
+            shouldWarn        = ConditionResult.FALSE;
+            isFirstWarning    = ConditionResult.FALSE;
+            isPasswordExpired = ConditionResult.FALSE;
           }
           else
           {
@@ -2370,8 +2488,8 @@
             if (expirationTime > currentTime)
             {
               // The password is not expired but we should warn the user.
-              shouldWarn = ConditionResult.TRUE;
-              isExpired  = ConditionResult.FALSE;
+              shouldWarn        = ConditionResult.TRUE;
+              isPasswordExpired = ConditionResult.FALSE;
 
               if (warnedTime < 0)
               {
@@ -2399,32 +2517,32 @@
               // expired if the user has not yet seen a warning.
               if (passwordPolicy.expirePasswordsWithoutWarning())
               {
-                shouldWarn     = ConditionResult.FALSE;
-                isFirstWarning = ConditionResult.FALSE;
-                isExpired      = ConditionResult.TRUE;
+                shouldWarn        = ConditionResult.FALSE;
+                isFirstWarning    = ConditionResult.FALSE;
+                isPasswordExpired = ConditionResult.TRUE;
               }
               else if (warnedTime > 0)
               {
                 expirationTime = warnedTime + (warningInterval*1000);
                 if (expirationTime > currentTime)
                 {
-                  shouldWarn     = ConditionResult.TRUE;
-                  isFirstWarning = ConditionResult.FALSE;
-                  isExpired      = ConditionResult.FALSE;
+                  shouldWarn        = ConditionResult.TRUE;
+                  isFirstWarning    = ConditionResult.FALSE;
+                  isPasswordExpired = ConditionResult.FALSE;
                 }
                 else
                 {
-                  shouldWarn     = ConditionResult.FALSE;
-                  isFirstWarning = ConditionResult.FALSE;
-                  isExpired      = ConditionResult.TRUE;
+                  shouldWarn        = ConditionResult.FALSE;
+                  isFirstWarning    = ConditionResult.FALSE;
+                  isPasswordExpired = ConditionResult.TRUE;
                 }
               }
               else
               {
-                shouldWarn     = ConditionResult.TRUE;
-                isFirstWarning = ConditionResult.TRUE;
-                isExpired      = ConditionResult.FALSE;
-                expirationTime = currentTime + (warningInterval*1000);
+                shouldWarn        = ConditionResult.TRUE;
+                isFirstWarning    = ConditionResult.TRUE;
+                isPasswordExpired = ConditionResult.FALSE;
+                expirationTime    = currentTime + (warningInterval*1000);
               }
             }
           }
@@ -2438,11 +2556,11 @@
 
           if (currentTime > expirationTime)
           {
-            isExpired = ConditionResult.TRUE;
+            isPasswordExpired = ConditionResult.TRUE;
           }
           else
           {
-            isExpired = ConditionResult.FALSE;
+            isPasswordExpired = ConditionResult.FALSE;
           }
         }
       }
@@ -2454,11 +2572,11 @@
 
         if (expirationTime < currentTime)
         {
-          isExpired = ConditionResult.TRUE;
+          isPasswordExpired = ConditionResult.TRUE;
         }
         else
         {
-          isExpired = ConditionResult.FALSE;
+          isPasswordExpired = ConditionResult.FALSE;
         }
       }
     }
@@ -2484,16 +2602,17 @@
    * @return  <CODE>true</CODE> if the user's password is currently expired, or
    *          <CODE>false</CODE> if not.
    */
-  public boolean isExpired()
+  public boolean isPasswordExpired()
   {
-    assert debugEnter(CLASS_NAME, "isExpired");
+    assert debugEnter(CLASS_NAME, "isPasswordExpired");
 
-    if ((isExpired == null) || (isExpired == ConditionResult.UNDEFINED))
+    if ((isPasswordExpired == null) ||
+        (isPasswordExpired == ConditionResult.UNDEFINED))
     {
       getPasswordExpirationTime();
     }
 
-    if (isExpired == ConditionResult.TRUE)
+    if (isPasswordExpired == ConditionResult.TRUE)
     {
       return true;
     }
diff --git a/opends/src/server/org/opends/server/core/SearchOperation.java b/opends/src/server/org/opends/server/core/SearchOperation.java
index b4874fe..ebd251e 100644
--- a/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/opends/src/server/org/opends/server/core/SearchOperation.java
@@ -827,12 +827,13 @@
         PasswordPolicyState pwpState = new PasswordPolicyState(entry, false,
                                                                false);
 
-        boolean isInactive           = pwpState.isDisabled();
+        boolean isInactive           = pwpState.isDisabled() ||
+                                       pwpState.isAccountExpired();
         boolean isLocked             = pwpState.lockedDueToFailures() ||
                                        pwpState.lockedDueToMaximumResetAge() ||
                                        pwpState.lockedDueToIdleInterval();
         boolean isReset              = pwpState.mustChangePassword();
-        boolean isExpired            = pwpState.isExpired();
+        boolean isExpired            = pwpState.isPasswordExpired();
 
         if (isInactive || isLocked || isReset || isExpired)
         {
diff --git a/opends/src/server/org/opends/server/messages/CoreMessages.java b/opends/src/server/org/opends/server/messages/CoreMessages.java
index d7a8b95..cb90115 100644
--- a/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -5568,6 +5568,16 @@
 
 
   /**
+   * The message ID for the message that will be used if a user tries to
+   * authenticate using an expired account.  This takes a single argument, which
+   * is the DN of the user.
+   */
+  public static final int MSGID_BIND_OPERATION_ACCOUNT_EXPIRED =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 531;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined
    * in this class.
    */
@@ -7497,6 +7507,9 @@
     registerMessage(MSGID_BIND_OPERATION_ACCOUNT_DISABLED,
                     "Rejecting a bind request for user %s because the " +
                     "account has been administrative disabled.");
+    registerMessage(MSGID_BIND_OPERATION_ACCOUNT_EXPIRED,
+                    "Rejecting a bind request for user %s because the " +
+                    "account has expired.");
     registerMessage(MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED,
                     "Rejecting a bind request for user %s because the " +
                     "account has been locked due to too many failed " +

--
Gitblit v1.10.0