From b1097df77bd6de52a079970821ffa8bf1ccfbfac Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sat, 5 Apr 2014 18:42:46 +0000 Subject: [PATCH] extended password policy --- .../manual-sources/images/configGeneral2.png | Bin 19744 -> 28190 bytes lam/help/help.inc | 6 + lam/lib/config.inc | 17 ++- lam/lib/security.inc | 84 +++++++++- lam/templates/config/mainmanage.php | 13 ++ lam/tests/lib/security.inc | 144 ++++++++++++++++++ lam/tests/utils/configuration.inc | 42 +++++ 7 files changed, 297 insertions(+), 9 deletions(-) create mode 100644 lam/tests/lib/security.inc create mode 100644 lam/tests/utils/configuration.inc diff --git a/lam/docs/manual-sources/images/configGeneral2.png b/lam/docs/manual-sources/images/configGeneral2.png index 1de04938e577d32f62d9bf987929e7557b0c4907..c79ac2f58bd1a5ad19175eef7f05ea3f5d3c01a0 100644 GIT binary patch literal 28190 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 UJQN!gryvj|1$FsCS+jtD0|2im7ytkO literal 19744 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

mJR;Z>T04U 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 M%LtVS=zaSC08kqON&o-= diff --git a/lam/help/help.inc b/lam/help/help.inc index 5eb6d127..83cb7ab0 100644 --- a/lam/help/help.inc +++ b/lam/help/help.inc @@ -159,6 +159,12 @@ $helpArray = array ( "Text" => _('Defines if the PHP error reporting setting from php.ini is used or the setting preferred by LAM ("E_ALL & ~E_NOTICE"). If you do not develop LAM modules please use the default. This will prevent displaying messages that are useful only for developers.')), "245" => array ("Headline" => _('Encrypt session'), "Text" => _('Encrypts sensitive data like passwords in your session. This requires the PHP MCrypt extension.')), + "246" => array ("Headline" => _('Number of rules that must match'), + "Text" => _('Specifies the number of above password rules that must be fulfilled.')), + "247" => array ("Headline" => _('Password must not contain user name'), + "Text" => _('Specifies if the password must not contain the user name.')), + "248" => array ("Headline" => _('Password must not contain part of user/first/last name'), + "Text" => _('Specifies if the password must not contain 3 or more characters of the user/first/last name.')), "250" => array ("Headline" => _("Filter"), "Text" => _("Here you can input simple filter expressions (e.g. 'value' or 'v*'). The filter is case-sensitive.")), "260" => array ("Headline" => _("Additional LDAP filter"), diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 39db0054..ff57829e 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -1506,6 +1506,15 @@ class LAMCfgMain { /** minimum character classes (upper, lower, numeric, symbols) */ public $passwordMinClasses = 0; + /** number of password rules that must match (-1 = all) */ + public $checkedRulesCount = -1; + + /** password may contain the user name */ + public $passwordMustNotContainUser = 'false'; + + /** password may contain more than 2 characters of user/first/last name */ + public $passwordMustNotContain3Chars = 'false'; + /** path to config file */ private $conffile; @@ -1525,8 +1534,9 @@ class LAMCfgMain { private $settings = array("password", "default", "sessionTimeout", "logLevel", "logDestination", "allowedHosts", "passwordMinLength", "passwordMinUpper", "passwordMinLower", "passwordMinNumeric", - "passwordMinClasses", "passwordMinSymbol", "mailEOL", 'errorReporting', - 'encryptSession', 'allowedHostsSelfService'); + "passwordMinClasses", "passwordMinSymbol", 'checkedRulesCount', + 'passwordMustNotContainUser', 'passwordMustNotContain3Chars', + "mailEOL", 'errorReporting', 'encryptSession', 'allowedHostsSelfService'); /** * Loads preferences from config file @@ -1615,6 +1625,9 @@ class LAMCfgMain { if (!in_array("passwordMinNumeric", $saved)) array_push($file_array, "\n\n# Password: minimum numeric characters\n" . "passwordMinNumeric: " . $this->passwordMinNumeric); if (!in_array("passwordMinSymbol", $saved)) array_push($file_array, "\n\n# Password: minimum symbolic characters\n" . "passwordMinSymbol: " . $this->passwordMinSymbol); if (!in_array("passwordMinClasses", $saved)) array_push($file_array, "\n\n# Password: minimum character classes (0-4)\n" . "passwordMinClasses: " . $this->passwordMinClasses); + if (!in_array("checkedRulesCount", $saved)) array_push($file_array, "\n\n# Password: checked rules\n" . "checkedRulesCount: " . $this->checkedRulesCount); + if (!in_array("passwordMustNotContain3Chars", $saved)) array_push($file_array, "\n\n# Password: must not contain part of user name\n" . "passwordMustNotContain3Chars: " . $this->passwordMustNotContain3Chars); + if (!in_array("passwordMustNotContainUser", $saved)) array_push($file_array, "\n\n# Password: must not contain user name\n" . "passwordMustNotContainUser: " . $this->passwordMustNotContainUser); if (!in_array("mailEOL", $saved)) array_push($file_array, "\n\n# Email format (default/unix)\n" . "mailEOL: " . $this->mailEOL); if (!in_array("errorReporting", $saved)) array_push($file_array, "\n\n# PHP error reporting (default/system)\n" . "errorReporting: " . $this->errorReporting); $file = @fopen($this->conffile, "w"); diff --git a/lam/lib/security.inc b/lam/lib/security.inc index 904db586..57afbc64 100644 --- a/lam/lib/security.inc +++ b/lam/lib/security.inc @@ -315,10 +315,12 @@ function checkIfDeleteEntriesIsAllowed($scope) { /** * Checks if the password fulfills the password policies. * - * @param string $password password + * @param String $password password + * @param String $userName user name + * @param array $otherUserAttrs user's first/last name * @return mixed true if ok, string with error message if not valid */ -function checkPasswordStrength($password) { +function checkPasswordStrength($password, $userName, $otherUserAttrs) { if ($password == null) { $password = ""; } @@ -347,22 +349,48 @@ function checkPasswordStrength($password) { $symbols++; } } + $rulesMatched = 0; + $rulesFailed = 0; // check lower case - if ($lower < $cfg->passwordMinLower) { + if (($cfg->checkedRulesCount == -1) && ($lower < $cfg->passwordMinLower)) { return sprintf(_('The password is too weak. You have to enter at least %s lower case characters.'), $cfg->passwordMinLower); } + if ($lower < $cfg->passwordMinLower) { + $rulesFailed++; + } + else { + $rulesMatched++; + } // check upper case - if ($upper < $cfg->passwordMinUpper) { + if (($cfg->checkedRulesCount == -1) && ($upper < $cfg->passwordMinUpper)) { return sprintf(_('The password is too weak. You have to enter at least %s upper case characters.'), $cfg->passwordMinUpper); } + if ($upper < $cfg->passwordMinUpper) { + $rulesFailed++; + } + else { + $rulesMatched++; + } // check numeric - if ($numeric < $cfg->passwordMinNumeric) { + if (($cfg->checkedRulesCount == -1) && ($numeric < $cfg->passwordMinNumeric)) { return sprintf(_('The password is too weak. You have to enter at least %s numeric characters.'), $cfg->passwordMinNumeric); } + if ($numeric < $cfg->passwordMinNumeric) { + $rulesFailed++; + } + else { + $rulesMatched++; + } // check symbols - if ($symbols < $cfg->passwordMinSymbol) { + if (($cfg->checkedRulesCount == -1) && ($symbols < $cfg->passwordMinSymbol)) { return sprintf(_('The password is too weak. You have to enter at least %s symbolic characters.'), $cfg->passwordMinSymbol); } + if ($symbols < $cfg->passwordMinSymbol) { + $rulesFailed++; + } + else { + $rulesMatched++; + } // check classes $classes = 0; if ($lower > 0) { @@ -377,9 +405,51 @@ function checkPasswordStrength($password) { if ($symbols > 0) { $classes++; } - if ($classes < $cfg->passwordMinClasses) { + if (($cfg->checkedRulesCount == -1) && ($classes < $cfg->passwordMinClasses)) { return sprintf(_('The password is too weak. You have to enter at least %s different character classes (upper/lower case, numbers and symbols).'), $cfg->passwordMinClasses); } + if ($classes < $cfg->passwordMinClasses) { + $rulesFailed++; + } + else { + $rulesMatched++; + } + // check rules count + if (($cfg->checkedRulesCount != -1) && ($rulesMatched < $cfg->checkedRulesCount)) { + return sprintf(_('The password is too weak. It needs to match at least %s password complexity rules.'), $cfg->checkedRulesCount); + } + // check user name + if (($cfg->passwordMustNotContainUser == 'true') && !empty($userName)) { + $pwdLow = strtolower($password); + $userLow = strtolower($userName); + if (strpos($pwdLow, $userLow) !== false) { + return _('The password is too weak. You may not use the user name as part of the password.'); + } + } + // check part of user name and additional attributes + if (($cfg->passwordMustNotContain3Chars == 'true') && (!empty($userName) || !empty($otherUserAttrs))) { + $pwdLow = strtolower($password); + // check if contains part of user name + if (!empty($userName) && (strlen($userName) > 2)) { + $userLow = strtolower($userName); + for ($i = 0; $i < strlen($userLow) - 3; $i++) { + $part = substr($userLow, 0, 3); + if (strpos($pwdLow, $part) !== false) { + return _('The password is too weak. You may not use parts of the user name for the password.'); + } + } + } + // check other attributes + foreach ($otherUserAttrs as $other) { + $low = strtolower($other); + for ($i = 0; $i < strlen($low) - 3; $i++) { + $part = substr($low, 0, 3); + if (strpos($pwdLow, $part) !== false) { + return _('The password is too weak. You may not use parts of user attributes for the password.'); + } + } + } + } return true; } diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index a88dff6a..93a29c0e 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -153,6 +153,9 @@ if (isset($_POST['submitFormData'])) { $cfg->passwordMinNumeric = $_POST['passwordMinNumeric']; $cfg->passwordMinSymbol = $_POST['passwordMinSymbol']; $cfg->passwordMinClasses = $_POST['passwordMinClasses']; + $cfg->checkedRulesCount = $_POST['passwordRulesCount']; + $cfg->passwordMustNotContain3Chars = isset($_POST['passwordMustNotContain3Chars']) && ($_POST['passwordMustNotContain3Chars'] == 'on') ? 'true' : 'false'; + $cfg->passwordMustNotContainUser = isset($_POST['passwordMustNotContainUser']) && ($_POST['passwordMustNotContainUser'] == 'on') ? 'true' : 'false'; if (isset($_POST['sslCaCertUpload'])) { if (!isset($_FILES['sslCaCert']) || ($_FILES['sslCaCert']['size'] == 0)) { $errors[] = _('No file selected.'); @@ -381,11 +384,21 @@ $policyTable = new htmlTable(); $options20 = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); $options4 = array(0, 1, 2, 3, 4); $policyTable->addElement(new htmlTableExtendedSelect('passwordMinLength', $options20, array($cfg->passwordMinLength), _('Minimum password length'), '242'), true); +$policyTable->addVerticalSpace('5px'); $policyTable->addElement(new htmlTableExtendedSelect('passwordMinLower', $options20, array($cfg->passwordMinLower), _('Minimum lowercase characters'), '242'), true); $policyTable->addElement(new htmlTableExtendedSelect('passwordMinUpper', $options20, array($cfg->passwordMinUpper), _('Minimum uppercase characters'), '242'), true); $policyTable->addElement(new htmlTableExtendedSelect('passwordMinNumeric', $options20, array($cfg->passwordMinNumeric), _('Minimum numeric characters'), '242'), true); $policyTable->addElement(new htmlTableExtendedSelect('passwordMinSymbol', $options20, array($cfg->passwordMinSymbol), _('Minimum symbolic characters'), '242'), true); $policyTable->addElement(new htmlTableExtendedSelect('passwordMinClasses', $options4, array($cfg->passwordMinClasses), _('Minimum character classes'), '242'), true); +$policyTable->addVerticalSpace('5px'); +$rulesCountOptions = array(_('all') => '-1', '3' => '3', '4' => '4'); +$rulesCountSelect = new htmlTableExtendedSelect('passwordRulesCount', $rulesCountOptions, array($cfg->checkedRulesCount), _('Number of rules that must match'), '246'); +$rulesCountSelect->setHasDescriptiveElements(true); +$policyTable->addElement($rulesCountSelect, true); +$passwordMustNotContainUser = ($cfg->passwordMustNotContainUser === 'true') ? true : false; +$policyTable->addElement(new htmlTableExtendedInputCheckbox('passwordMustNotContainUser',$passwordMustNotContainUser , _('Password must not contain user name'), '247'), true); +$passwordMustNotContain3Chars = ($cfg->passwordMustNotContain3Chars === 'true') ? true : false; +$policyTable->addElement(new htmlTableExtendedInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248'), true); $container->addElement($policyTable, true); $container->addElement(new htmlSpacer(null, '10px'), true); diff --git a/lam/tests/lib/security.inc b/lam/tests/lib/security.inc new file mode 100644 index 00000000..3be52b04 --- /dev/null +++ b/lam/tests/lib/security.inc @@ -0,0 +1,144 @@ +/* +$Id$ + + This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) + Copyright (C) 2014 Roland Gruber + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +cfg = &$_SESSION ['cfgMain']; + $this->resetPasswordRules(); + } + + public function testMinLength() { + $this->cfg->passwordMinLength = 5; + $this->checkPwd(array('55555', '666666'), array('1', '22', '333', '4444')); + } + + public function testMinUpper() { + $this->cfg->passwordMinUpper = 3; + $this->checkPwd(array('55A5AA55', '6BB666BB66', 'ABC'), array ('1A', '2C2C', 'AB3', '44BB')); + } + + public function testMinLower() { + $this->cfg->passwordMinLower = 3; + $this->checkPwd(array('55a5aa55', '6bb666bb66', 'abc'), array ('1a', '2c2c', 'ab3', '44bbABC')); + } + + public function testMinNumeric() { + $this->cfg->passwordMinNumeric = 3; + $this->checkPwd(array('333', '4444'), array('1', '22', '33A', '44bb')); + } + + public function testMinSymbol() { + $this->cfg->passwordMinSymbol = 3; + $this->checkPwd(array('---', '++++'), array('1.', '2.2.', '3+3+A', '44bb')); + } + + public function testMinClasses() { + $this->cfg->passwordMinClasses = 3; + $this->checkPwd(array('aB.', 'aB.1', 'aa.B99'), array('1', '2.', '3+-', '44bb')); + } + + public function testRulesCount() { + $this->cfg->passwordMinUpper = 3; + $this->cfg->passwordMinLower = 3; + $this->cfg->passwordMinNumeric = 3; + $this->cfg->passwordMinSymbol = 3; + $this->cfg->passwordMinClasses = 3; + // all rules + $this->cfg->checkedRulesCount = -1; + $this->checkPwd(array('ABC---abc123', 'ABC123xxx.-.-'), array('1', '2.', '3+-', '44bb', 'ABCabc---22')); + // at least 3 rules + $this->cfg->checkedRulesCount = 3; + $this->checkPwd(array('ABC---abc', 'ABC123.-.-', 'ABCabc-'), array('1', '2.', '3+-', '44bb', 'ABC--22')); + } + + public function testUser() { + $this->cfg->passwordMustNotContainUser = 'true'; + $this->checkPwd(array('u', 'us', 'use', 'use1r'), array('user', '2user', 'user3'), 'user'); + } + + public function testUserAttributes() { + $this->cfg->passwordMustNotContain3Chars = 'true'; + $this->checkPwd(array('u', 'us', 'us1e', 'us1er'), array('use', 'user', '2user', 'user3'), 'user'); + $this->checkPwd( + array('uf', 'usfi', 'us1ela3s', 'us1er.la#st'), + array('use', 'user', '2user', 'user3', 'las', 'last', 'fir', 'first'), + 'user', + array('first', 'last')); + } + + /** + * Resets the password rules to do no checks at all. + */ + private function resetPasswordRules() { + $this->cfg->passwordMinLength = 0; + $this->cfg->passwordMinUpper = 0; + $this->cfg->passwordMinLower = 0; + $this->cfg->passwordMinNumeric = 0; + $this->cfg->passwordMinSymbol = 0; + $this->cfg->passwordMinClasses = 0; + $this->cfg->checkedRulesCount = -1; + $this->cfg->passwordMustNotContainUser = 'false'; + $this->cfg->passwordMustNotContain3Chars = 'false'; + } + + /** + * Checks if the given passwords are correctly accepted/rejected. + * + * @param array $pwdsToAccept passwords that must be accepted + * @param array $pwdsToReject passwords that must be rejected + * @param String $userName user name + * @param array $otherUserAttrs other user attributes to check + */ + private function checkPwd($pwdsToAccept, $pwdsToReject, $userName = null, $otherUserAttrs = null) { + if ($userName == null) { + $userName = 'username'; + } + if ($otherUserAttrs == null) { + $otherUserAttrs = array (); + } + foreach ($pwdsToAccept as $pwd) { + $this->assertTrue(checkPasswordStrength($pwd, $userName, $otherUserAttrs)); + } + foreach ($pwdsToReject as $pwd) { + $this->assertNotTrue(checkPasswordStrength($pwd, $userName, $otherUserAttrs)); + } + } + +} + +?> \ No newline at end of file diff --git a/lam/tests/utils/configuration.inc b/lam/tests/utils/configuration.inc new file mode 100644 index 00000000..2e3d3242 --- /dev/null +++ b/lam/tests/utils/configuration.inc @@ -0,0 +1,42 @@ +/* +$Id$ + + This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) + Copyright (C) 2014 Roland Gruber + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +logDestination = 'NONE'; + $_SESSION['cfgMain'] = $cfgMain; + $cfgPath = dirname(__FILE__) . '/../../config/phpunit.conf'; + if (file_exists($cfgPath)) { + unlink($cfgPath); + } + touch($cfgPath); + $config = new LAMConfig('phpunit'); + $_SESSION['config'] = $config; +} + +?> \ No newline at end of file