[dev] [patch] Password strength tests

Jason M. Felice jfelice at cronosys.com
Thu Dec 2 09:01:19 PST 2004


Attached is what I did for password strength tests.  Let me know how
much, if any, to commit.  The rest I'll keep as a patch until a later
date.

-- 
 Jason M. Felice
 Cronosys, LLC <http://www.cronosys.com/>
 216.221.4600 x302
-------------- next part --------------
This patch:

* Moves support for verifying passwords based on character class policies from
  the "passwd" module into the Auth package.
* Allows configuration of a global password policy in Horde's config.
* Checks passwords in user administration against the global password policy
  and prevents user add or update if password fails tests.

Index: horde/admin/user.php
===================================================================
--- horde.orig/admin/user.php	2004-06-17 14:16:25.000000000 -0400
+++ horde/admin/user.php	2004-12-02 11:58:14.000000000 -0500
@@ -55,37 +55,54 @@
 case 'add':
     $addForm->validate($vars);
 
-    if ($addForm->isValid() && $vars->get('formname') == 'adduser') {
-        $addForm->getInfo($vars, $info);
+    while (true) {
+        if (!$addForm->isValid() || $vars->get('formname') != 'adduser') {
+            break;
+        }
 
+        $addForm->getInfo($vars, $info);
         if (empty($info['user_name'])) {
-            $notification->push(_("You must specify the username to add."), 'horde.error');
+            $notification->push(_("You must specify the username to add."),
+                                'horde.error');
+            break;
+        }
 
-        } else {
-            $credentials = array('password' => $info['password']);
-            if (isset($info['extra'])) {
-                foreach ($info['extra'] as $field => $value) {
-                    $credentials[$field] = $value;
-                }
+        if (!empty($conf['auth']['password_policy'])) {
+            $ret = Auth::testPasswordStrength($info['password'],
+                                              $conf['auth']['password_policy']);
+            if (is_a($ret, 'PEAR_Error')) {
+                $notification->push($ret->getMessage(), 'horde.error');
+                break;
             }
+        }
 
-            if (is_a($ret = $auth->addUser($info['user_name'], $credentials), 'PEAR_Error')) {
-                $notification->push(sprintf(_("There was a problem adding '%s' to the system: %s"), $info['user_name'], $ret->getMessage()), 'horde.error');
-            } else {
-                if (isset($info['extra'])) {
-                    $result = Horde::callHook('_horde_hook_signup_addextra',
-                                              array($info['user_name'], $info['extra']));
-                    if (is_a($result, 'PEAR_Error')) {
-                        $notification->push(sprintf(_("Added '%s' to the system, but could not add additional signup information: %s."), $info['user_name'], $result->getMessage()), 'horde.warning');
-                    }
-                }
-                if (Util::getFormData('removeQueuedSignup')) {
-                    $signup->removeQueuedSignup($info['user_name']);
-                }
-                $notification->push(sprintf(_("Successfully added '%s' to the system."), $info['user_name']), 'horde.success');
-                $addForm->unsetVars($vars);
+        $credentials = array('password' => $info['password']);
+        if (isset($info['extra'])) {
+            foreach ($info['extra'] as $field => $value) {
+                $credentials[$field] = $value;
             }
         }
+        
+        $ret = $auth->addUser($info['user_name'], $credentials);
+        if (is_a($ret, 'PEAR_Error')) {
+            $notification->push(sprintf(_("There was a problem adding '%s' to the system: %s"), $info['user_name'], $ret->getMessage()), 'horde.error');
+            break;
+        }
+
+        if (isset($info['extra'])) {
+            $result = Horde::callHook('_horde_hook_signup_addextra',
+                                      array($info['user_name'], $info['extra']));
+            if (is_a($result, 'PEAR_Error')) {
+                $notification->push(sprintf(_("Added '%s' to the system, but could not add additional signup information: %s."), $info['user_name'], $result->getMessage()), 'horde.warning');
+            }
+        }
+        if (Util::getFormData('removeQueuedSignup')) {
+            $signup->removeQueuedSignup($info['user_name']);
+        }
+
+        $notification->push(sprintf(_("Successfully added '%s' to the system."), $info['user_name']), 'horde.success');
+        $addForm->unsetVars($vars);
+        break;
     }
     break;
 
@@ -147,9 +164,12 @@
         } elseif ($user_pass_1 != $user_pass_2) {
             $notification->push(_("Passwords must match."), 'horde.error');
         } else {
-            $result = $auth->updateUser($user_name_1,
-                                        $user_name_2,
-                                        array('password' => $user_pass_1));
+            $result = Auth::testPasswordStrength($user_pass_1, $conf['auth']['password_policy']);
+            if (!is_a($result, 'PEAR_Error')) {
+                $result = $auth->updateUser($user_name_1,
+                                            $user_name_2,
+                                            array('password' => $user_pass_1));
+            }
         }
     }
 
Index: horde/config/conf.xml
===================================================================
--- horde.orig/config/conf.xml	2004-12-02 10:49:36.000000000 -0500
+++ horde/config/conf.xml	2004-12-02 11:40:55.000000000 -0500
@@ -532,6 +532,37 @@
 
     <case name="kolab" desc="Kolab (Cyrus IMAP) authentication" />
    </configswitch>
+   <configheader>Password Strength Tests</configheader>
+   <configsection name="password_policy">
+    <configinteger name="minLength" required="false" desc="The minimum allowed
+    length for passwords.  If left blank, there will be no minimum." />
+    <configinteger name="maxLength" required="false" desc="The maximum allowed
+    length for passwords.  If left blank, there will be no maximum." />
+    <configinteger name="maxSpace" required="false" desc="The maximum number of
+    white space characters (spaces, tabs, etc.) allowed in the password.  If
+    blank, there will be no maximum." />
+    <configinteger name="minUpper" required="false" desc="The minimum number of
+    uppercase characters required in passwords.  If left blank, no uppercase
+    characters will be required." />
+    <configinteger name="minLower" required="false" desc="The minimum number of
+    lowercase characters required in passwords.  If left blank, no lowercase
+    characters will be required." />
+    <configinteger name="minNumeric" required="false" desc="The minimum number
+    of numeric characters (0-9) required in passwords.  If left blank, no
+    numeric characters will be required." />
+    <configinteger name="minAlphaNum" required="false" desc="The minimum number
+    of alphanumeric characters required in passwords.  If left blank, no
+    alphanumeric characters will be required." />
+    <configinteger name="minAlpha" required="false" desc="The minimum number of
+    alphabetic characters required in passwords.  If left blank, no alphabetic
+    characters will be required." />
+    <configinteger name="minSymbol" required="false" desc="The minimum number
+    of symbols (spaces and punctuation marks) required in passwords.  If left
+    blank, no spaces or puncutation marks will be required." />
+    <configinteger name="minClasses" required="false" desc="The minimum number
+    of classes of characters (i.e. uppercase, lowercase, numbers, and symbols)
+    required in a password.  If left blank, this check is not performed." />
+   </configsection>
   </configsection>
  </configtab>
 
Index: horde/framework/Auth/Auth.php
===================================================================
--- horde.orig/framework/Auth/Auth.php	2004-11-08 09:51:37.000000000 -0500
+++ horde/framework/Auth/Auth.php	2004-12-02 11:17:35.000000000 -0500
@@ -1143,6 +1143,73 @@
         return $bin;
     }
 
+    function testPasswordStrength($password, $password_policy)
+    {
+        // Check max/min lengths if specified in the backend config.
+        if (isset($password_policy['minLength']) &&
+            strlen($password) < $password_policy['minLength']) {
+            return PEAR::raiseError(sprintf(_("Your new password must be at least %d characters long!"), $password_policy['minLength']));
+        }
+        if (isset($password_policy['maxLength']) &&
+            strlen($password) > $password_policy['maxLength']) {
+            return PEAR::raiseError(sprintf(_("Your new password is too long; passwords may not be more than %d characters long!"), $password_policy['maxLength']));
+        }
+
+        // Disect the password in a localised way.
+        $classes = array();
+        $alpha = $alnum = $num = $upper = $lower = $space = $symbol = 0;
+        for ($i = 0; $i < strlen($password); $i++) {
+            $char = substr($password, $i, 1);
+            if (ctype_lower($char)) {
+                $lower++; $alpha++; $alnum++; $classes['lower'] = 1;
+            } elseif (ctype_upper($char)) {
+                $upper++; $alpha++; $alnum++; $classes['upper'] = 1;
+            } elseif (ctype_digit($char)) {
+                $num++; $alnum++; $classes['number'] = 1;
+            } elseif (ctype_punct($char)) {
+                $symbol++; $classes['symbol'] = 1;
+            } elseif (ctype_space($char)) {
+                $space++; $classes['symbol'] = 1;
+            }
+        }
+
+        // Check reamaining password policy options.
+        if (isset($password_policy['minUpper']) &&
+            $password_policy['minUpper'] > $upper) {
+            return PEAR::raiseError(sprintf(_("Your new password must contain at least %d uppercase characters."), $password_policy['minUpper']));
+        }
+        if (isset($password_policy['minLower']) &&
+            $password_policy['minLower'] > $lower) {
+            return PEAR::raiseError(sprintf(_("Your new password must contain at least %d lowercase characters."), $password_policy['minLower']));
+        }
+        if (isset($password_policy['minNumeric']) &&
+            $password_policy['minNumeric'] > $num) {
+            return PEAR::raiseError(sprintf(_("Your new password must contain at least %d numeric characters."), $password_policy['minNumeric']));
+        }
+        if (isset($password_policy['minAlpha']) &&
+            $password_policy['minAlpha'] > $alpha) {
+            return PEAR::raisError(sprintf(_("Your new password must contain at least %d alphabetic characters."), $password_policy['minAlpha']));
+        }
+        if (isset($password_policy['minAlphaNum']) &&
+            $password_policy['minAlphaNum'] > $alnum) {
+            return PEAR::raiseError(sprintf(_("Your new password must contain at least %d alphanumeric characters."), $password_policy['minAlphaNum']));
+        }
+        if (isset($password_policy['minClasses']) &&
+            $password_policy['minClasses'] > array_sum($classes)) {
+            return PEAR::raiseError(sprintf(_("Your new password must contain at least %d different types of characters. The types are: lower, upper, numeric, and symbols."), $password_policy['minClasses']));
+        }
+        if (isset($password_policy['maxSpace']) &&
+            $password_policy['maxSpace'] < $space) {
+            if ($password_policy['maxSpace'] > 0) {
+                return PEAR::raiseError(_("Your new password must contain less than %d whitespace characters."), $password_policy['maxSpace']);
+            } else {
+                return PEAR::raiseError(_("Your new password must not contain whitespace characters."));
+            }
+        }
+
+        return true;
+    }
+
     /**
      * Attempts to return a concrete Auth instance based on $driver.
      *
Index: horde/passwd/main.php
===================================================================
--- horde.orig/passwd/main.php	2004-11-19 14:00:33.000000000 -0500
+++ horde/passwd/main.php	2004-12-02 11:07:53.000000000 -0500
@@ -69,74 +69,9 @@
         break;
     }
 
-    // Check max/min lengths if specified in the backend config.
-    if (isset($password_policy['minLength']) &&
-        strlen($new_password0) < $password_policy['minLength']) {
-        $notification->push(sprintf(_("Your new password must be at least %d characters long!"), $password_policy['minLength']), 'horde.warning');
-        break;
-    }
-    if (isset($password_policy['maxLength']) &&
-        strlen($new_password0) > $password_policy['maxLength']) {
-        $notification->push(sprintf(_("Your new password is too long; passwords may not be more than %d characters long!"), $password_policy['maxLength']), 'horde.warning');
-        break;
-    }
-
-    // Disect the password in a localised way.
-    $classes = array();
-    $alpha = $alnum = $num = $upper = $lower = $space = $symbol = 0;
-    for ($i = 0; $i < strlen($new_password0); $i++) {
-        $char = substr($new_password0, $i, 1);
-        if (ctype_lower($char)) {
-            $lower++; $alpha++; $alnum++; $classes['lower'] = 1;
-        } elseif (ctype_upper($char)) {
-            $upper++; $alpha++; $alnum++; $classes['upper'] = 1;
-        } elseif (ctype_digit($char)) {
-            $num++; $alnum++; $classes['number'] = 1;
-        } elseif (ctype_punct($char)) {
-            $symbol++; $classes['symbol'] = 1;
-        } elseif (ctype_space($char)) {
-            $space++; $classes['symbol'] = 1;
-        }
-    }
-
-    // Check reamaining password policy options.
-    if (isset($password_policy['minUpper']) &&
-        $password_policy['minUpper'] > $upper) {
-        $notification->push(sprintf(_("Your new password must contain at least %d uppercase characters."), $password_policy['minUpper']), 'horde.warning');
-        break;
-    }
-    if (isset($password_policy['minLower']) &&
-        $password_policy['minLower'] > $lower) {
-        $notification->push(sprintf(_("Your new password must contain at least %d lowercase characters."), $password_policy['minLower']), 'horde.warning');
-        break;
-    }
-    if (isset($password_policy['minNumeric']) &&
-        $password_policy['minNumeric'] > $num) {
-        $notification->push(sprintf(_("Your new password must contain at least %d numeric characters."), $password_policy['minNumeric']), 'horde.warning');
-        break;
-    }
-    if (isset($password_policy['minAlpha']) &&
-        $password_policy['minAlpha'] > $alpha) {
-        $notification->push(sprintf(_("Your new password must contain at least %d alphabetic characters."), $password_policy['minAlpha']), 'horde.warning');
-        break;
-    }
-    if (isset($password_policy['minAlphaNum']) &&
-        $password_policy['minAlphaNum'] > $alnum) {
-        $notification->push(sprintf(_("Your new password must contain at least %d alphanumeric characters."), $password_policy['minAlphaNum']), 'horde.warning');
-        break;
-    }
-    if (isset($password_policy['minClasses']) &&
-        $password_policy['minClasses'] > array_sum($classes)) {
-        $notification->push(sprintf(_("Your new password must contain at least %d different types of characters. The types are: lower, upper, numeric, and symbols."), $password_policy['minClasses']), 'horde.warning');
-        break;
-    }
-    if (isset($password_policy['maxSpace']) &&
-        $password_policy['maxSpace'] < $space) {
-        if ($password_policy['maxSpace'] > 0) {
-            $notification->push(sprintf(_("Your new password must contain less than %d whitespace characters."), $password_policy['maxSpace']), 'horde.warning');
-        } else {
-            $notification->push(_("Your new password must not contain whitespace characters."), 'horde.warning');
-        }
+    $res = Auth::testPasswordStrength($new_password0, $password_policy);
+    if (is_a($res, 'PEAR_Error')) {
+        $notification->push($res->getMessage(), 'horde.warning');
         break;
     }
 


More information about the dev mailing list