[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