extended password policy

This commit is contained in:
Roland Gruber 2014-04-05 18:42:46 +00:00
parent 85f21de260
commit b1097df77b
7 changed files with 297 additions and 9 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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"),

View File

@ -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");

View File

@ -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;
}

View File

@ -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);

144
lam/tests/lib/security.inc Normal file
View File

@ -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
*/
<?php
$_SERVER ['REMOTE_ADDR'] = '127.0.0.1';
include_once (dirname ( __FILE__ ) . '/../utils/configuration.inc');
include_once (dirname ( __FILE__ ) . '/../../lib/security.inc');
/**
* Checks password checking functions.
*
* @author Roland Gruber
*
*/
class SecurityTest extends PHPUnit_Framework_TestCase {
private $cfg = null;
protected function setUp() {
testCreateDefaultConfig ();
$this->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));
}
}
}
?>

View File

@ -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
*/
<?php
include_once (dirname(__FILE__) . '/../../lib/config.inc');
/**
* Creates a default configuration and adds it to $_SESSION['cfgMain'] and $_SESSION['config'].
*/
function testCreateDefaultConfig() {
$cfgMain = new LAMCfgMain();
$cfgMain->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;
}
?>