mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Merge pull request #8013 from open-sausages/pulls/4.1/fix-password-validator-fields
BUG Prevent password validator min score producing false negatives
This commit is contained in:
commit
62631dc3ee
@ -39,39 +39,39 @@ class PasswordValidator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var integer
|
* @var int
|
||||||
*/
|
*/
|
||||||
private static $min_length = null;
|
private static $min_length = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var integer
|
* @var int
|
||||||
*/
|
*/
|
||||||
private static $min_test_score = null;
|
private static $min_test_score = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @config
|
* @config
|
||||||
* @var integer
|
* @var int
|
||||||
*/
|
*/
|
||||||
private static $historic_count = null;
|
private static $historic_count = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var integer
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $minLength = null;
|
protected $minLength = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var integer
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $minScore = null;
|
protected $minScore = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $testNames = null;
|
protected $testNames = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var integer
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $historicalPasswordCount = null;
|
protected $historicalPasswordCount = null;
|
||||||
|
|
||||||
@ -95,18 +95,20 @@ class PasswordValidator
|
|||||||
* Eg: $this->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"))
|
* Eg: $this->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"))
|
||||||
*
|
*
|
||||||
* @param int $minScore The minimum number of character tests that must pass
|
* @param int $minScore The minimum number of character tests that must pass
|
||||||
* @param array $testNames The names of the tests to perform
|
* @param string[] $testNames The names of the tests to perform
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function characterStrength($minScore, $testNames = null)
|
public function characterStrength($minScore, $testNames = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
Deprecation::notice(
|
Deprecation::notice(
|
||||||
'5.0',
|
'5.0',
|
||||||
'Use ->setMinTestScore($score) and ->setTextNames($names) instead.'
|
'Use ->setMinTestScore($score) and ->setTextNames($names) instead.'
|
||||||
);
|
);
|
||||||
return $this->setMinTestScore($minScore)
|
$this->setMinTestScore($minScore);
|
||||||
->setTestNames($testNames);
|
if ($testNames) {
|
||||||
|
$this->setTestNames($testNames);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,7 +125,7 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return integer
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getMinLength()
|
public function getMinLength()
|
||||||
{
|
{
|
||||||
@ -134,7 +136,7 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $minLength
|
* @param int $minLength
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setMinLength($minLength)
|
public function setMinLength($minLength)
|
||||||
@ -155,7 +157,7 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $minScore
|
* @param int $minScore
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setMinTestScore($minScore)
|
public function setMinTestScore($minScore)
|
||||||
@ -165,7 +167,9 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* Gets the list of tests to use for this validator
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
public function getTestNames()
|
public function getTestNames()
|
||||||
{
|
{
|
||||||
@ -176,7 +180,9 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $testNames
|
* Set list of tests to use for this validator
|
||||||
|
*
|
||||||
|
* @param string[] $testNames
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setTestNames($testNames)
|
public function setTestNames($testNames)
|
||||||
@ -186,7 +192,7 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return integer
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getHistoricCount()
|
public function getHistoricCount()
|
||||||
{
|
{
|
||||||
@ -197,7 +203,7 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $count
|
* @param int $count
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setHistoricCount($count)
|
public function setHistoricCount($count)
|
||||||
@ -207,6 +213,8 @@ class PasswordValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Gets all possible tests
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getTests()
|
public function getTests()
|
||||||
@ -226,7 +234,7 @@ class PasswordValidator
|
|||||||
$minLength = $this->getMinLength();
|
$minLength = $this->getMinLength();
|
||||||
if ($minLength && strlen($password) < $minLength) {
|
if ($minLength && strlen($password) < $minLength) {
|
||||||
$error = _t(
|
$error = _t(
|
||||||
'SilverStripe\\Security\\PasswordValidator.TOOSHORT',
|
__CLASS__ . '.TOOSHORT',
|
||||||
'Password is too short, it must be {minimum} or more characters long',
|
'Password is too short, it must be {minimum} or more characters long',
|
||||||
['minimum' => $this->minLength]
|
['minimum' => $this->minLength]
|
||||||
);
|
);
|
||||||
@ -245,16 +253,16 @@ class PasswordValidator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$missedTests[] = _t(
|
$missedTests[] = _t(
|
||||||
'SilverStripe\\Security\\PasswordValidator.STRENGTHTEST' . strtoupper($name),
|
__CLASS__ . '.STRENGTHTEST' . strtoupper($name),
|
||||||
$name,
|
$name,
|
||||||
'The user needs to add this to their password for more complexity'
|
'The user needs to add this to their password for more complexity'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$score = count($this->testNames) - count($missedTests);
|
$score = count($testNames) - count($missedTests);
|
||||||
if ($score < $minTestScore) {
|
if ($missedTests && $score < $minTestScore) {
|
||||||
$error = _t(
|
$error = _t(
|
||||||
'SilverStripe\\Security\\PasswordValidator.LOWCHARSTRENGTH',
|
__CLASS__ . '.LOWCHARSTRENGTH',
|
||||||
'Please increase password strength by adding some of the following characters: {chars}',
|
'Please increase password strength by adding some of the following characters: {chars}',
|
||||||
['chars' => implode(', ', $missedTests)]
|
['chars' => implode(', ', $missedTests)]
|
||||||
);
|
);
|
||||||
@ -272,7 +280,7 @@ class PasswordValidator
|
|||||||
foreach ($previousPasswords as $previousPassword) {
|
foreach ($previousPasswords as $previousPassword) {
|
||||||
if ($previousPassword->checkPassword($password)) {
|
if ($previousPassword->checkPassword($password)) {
|
||||||
$error = _t(
|
$error = _t(
|
||||||
'SilverStripe\\Security\\PasswordValidator.PREVPASSWORD',
|
__CLASS__ . '.PREVPASSWORD',
|
||||||
'You\'ve already used that password in the past, please choose a new password'
|
'You\'ve already used that password in the past, please choose a new password'
|
||||||
);
|
);
|
||||||
$valid->addError($error, 'bad', 'PREVIOUS_PASSWORD');
|
$valid->addError($error, 'bad', 'PREVIOUS_PASSWORD');
|
||||||
|
@ -8,8 +8,9 @@ class TestPasswordValidator extends PasswordValidator
|
|||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->minLength(7);
|
$this->setMinLength(7);
|
||||||
$this->checkHistoricalPasswords(6);
|
$this->setHistoricCount(6);
|
||||||
$this->characterStrength(3, array('lowercase', 'uppercase', 'digits', 'punctuation'));
|
$this->setMinTestScore(3);
|
||||||
|
$this->setTestNames(['lowercase', 'uppercase', 'digits', 'punctuation']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,25 +28,42 @@ class PasswordValidatorTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
$v = new PasswordValidator();
|
$v = new PasswordValidator();
|
||||||
|
|
||||||
$v->minLength(4);
|
$v->setMinLength(4);
|
||||||
$r = $v->validate('123', new Member());
|
$r = $v->validate('123', new Member());
|
||||||
$this->assertFalse($r->isValid(), 'Password too short');
|
$this->assertFalse($r->isValid(), 'Password too short');
|
||||||
|
|
||||||
$v->minLength(4);
|
$v->setMinLength(4);
|
||||||
$r = $v->validate('1234', new Member());
|
$r = $v->validate('1234', new Member());
|
||||||
$this->assertTrue($r->isValid(), 'Password long enough');
|
$this->assertTrue($r->isValid(), 'Password long enough');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testValidateMinScore()
|
public function testValidateMinScore()
|
||||||
{
|
{
|
||||||
|
// Set both score and set of tests
|
||||||
$v = new PasswordValidator();
|
$v = new PasswordValidator();
|
||||||
$v->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
$v->setMinTestScore(3);
|
||||||
|
$v->setTestNames(["lowercase", "uppercase", "digits", "punctuation"]);
|
||||||
|
|
||||||
$r = $v->validate('aA', new Member());
|
$r = $v->validate('aA', new Member());
|
||||||
$this->assertFalse($r->isValid(), 'Passing too few tests');
|
$this->assertFalse($r->isValid(), 'Passing too few tests');
|
||||||
|
|
||||||
$r = $v->validate('aA1', new Member());
|
$r = $v->validate('aA1', new Member());
|
||||||
$this->assertTrue($r->isValid(), 'Passing enough tests');
|
$this->assertTrue($r->isValid(), 'Passing enough tests');
|
||||||
|
|
||||||
|
// Ensure min score without tests works (uses default tests)
|
||||||
|
$v = new PasswordValidator();
|
||||||
|
$v->setMinTestScore(3);
|
||||||
|
|
||||||
|
$r = $v->validate('aA', new Member());
|
||||||
|
$this->assertFalse($r->isValid(), 'Passing too few tests');
|
||||||
|
|
||||||
|
$r = $v->validate('aA1', new Member());
|
||||||
|
$this->assertTrue($r->isValid(), 'Passing enough tests');
|
||||||
|
|
||||||
|
// Ensure that min score is only triggered if there are any failing tests at all
|
||||||
|
$v->setMinTestScore(1000);
|
||||||
|
$r = $v->validate('aA1!', new Member());
|
||||||
|
$this->assertTrue($r->isValid(), 'Passing enough tests');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,7 +72,7 @@ class PasswordValidatorTest extends SapphireTest
|
|||||||
public function testHistoricalPasswordCount()
|
public function testHistoricalPasswordCount()
|
||||||
{
|
{
|
||||||
$validator = new PasswordValidator;
|
$validator = new PasswordValidator;
|
||||||
$validator->checkHistoricalPasswords(3);
|
$validator->setHistoricCount(3);
|
||||||
Member::set_password_validator($validator);
|
Member::set_password_validator($validator);
|
||||||
|
|
||||||
$member = new Member;
|
$member = new Member;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user