2008-04-26 08:31:52 +02:00
|
|
|
<?php
|
|
|
|
|
2016-06-23 01:37:22 +02:00
|
|
|
namespace SilverStripe\Security;
|
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Core\Object;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\ValidationResult;
|
|
|
|
|
2008-04-26 08:31:52 +02:00
|
|
|
/**
|
|
|
|
* This class represents a validator for member passwords.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2008-04-26 08:31:52 +02:00
|
|
|
* <code>
|
|
|
|
* $pwdVal = new PasswordValidator();
|
|
|
|
* $pwdValidator->minLength(7);
|
|
|
|
* $pwdValidator->checkHistoricalPasswords(6);
|
2013-10-15 12:44:50 +02:00
|
|
|
* $pwdValidator->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2008-04-26 08:31:52 +02:00
|
|
|
* Member::set_password_validator($pwdValidator);
|
|
|
|
* </code>
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class PasswordValidator extends Object
|
|
|
|
{
|
2013-10-21 19:10:37 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
private static $character_strength_tests = array(
|
|
|
|
'lowercase' => '/[a-z]/',
|
|
|
|
'uppercase' => '/[A-Z]/',
|
|
|
|
'digits' => '/[0-9]/',
|
|
|
|
'punctuation' => '/[^A-Za-z0-9]/',
|
|
|
|
);
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
protected $minLength, $minScore, $testNames, $historicalPasswordCount;
|
2008-04-26 08:31:52 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Minimum password length
|
|
|
|
*
|
|
|
|
* @param int $minLength
|
|
|
|
* @return $this
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function minLength($minLength)
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$this->minLength = $minLength;
|
|
|
|
return $this;
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Check the character strength of the password.
|
|
|
|
*
|
|
|
|
* Eg: $this->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"))
|
|
|
|
*
|
|
|
|
* @param int $minScore The minimum number of character tests that must pass
|
|
|
|
* @param array $testNames The names of the tests to perform
|
|
|
|
* @return $this
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function characterStrength($minScore, $testNames)
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$this->minScore = $minScore;
|
|
|
|
$this->testNames = $testNames;
|
|
|
|
return $this;
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Check a number of previous passwords that the user has used, and don't let them change to that.
|
|
|
|
*
|
|
|
|
* @param int $count
|
|
|
|
* @return $this
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function checkHistoricalPasswords($count)
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$this->historicalPasswordCount = $count;
|
|
|
|
return $this;
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* @param String $password
|
|
|
|
* @param Member $member
|
|
|
|
* @return ValidationResult
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function validate($password, $member)
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$valid = ValidationResult::create();
|
2013-06-08 00:48:27 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($this->minLength) {
|
|
|
|
if (strlen($password) < $this->minLength) {
|
|
|
|
$valid->addError(
|
|
|
|
sprintf(
|
|
|
|
_t(
|
|
|
|
'PasswordValidator.TOOSHORT',
|
|
|
|
'Password is too short, it must be %s or more characters long'
|
|
|
|
),
|
|
|
|
$this->minLength
|
|
|
|
),
|
|
|
|
'bad',
|
|
|
|
'TOO_SHORT'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2008-04-26 08:31:52 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($this->minScore) {
|
|
|
|
$score = 0;
|
|
|
|
$missedTests = array();
|
|
|
|
foreach ($this->testNames as $name) {
|
|
|
|
if (preg_match(self::config()->character_strength_tests[$name], $password)) {
|
|
|
|
$score++;
|
|
|
|
} else {
|
|
|
|
$missedTests[] = _t(
|
|
|
|
'PasswordValidator.STRENGTHTEST' . strtoupper($name),
|
|
|
|
$name,
|
|
|
|
'The user needs to add this to their password for more complexity'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2013-06-08 00:48:27 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($score < $this->minScore) {
|
|
|
|
$valid->addError(
|
|
|
|
sprintf(
|
|
|
|
_t(
|
|
|
|
'PasswordValidator.LOWCHARSTRENGTH',
|
|
|
|
'Please increase password strength by adding some of the following characters: %s'
|
|
|
|
),
|
|
|
|
implode(', ', $missedTests)
|
|
|
|
),
|
|
|
|
'bad',
|
|
|
|
'LOW_CHARACTER_STRENGTH'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2013-06-08 00:48:27 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($this->historicalPasswordCount) {
|
|
|
|
$previousPasswords = MemberPassword::get()
|
|
|
|
->where(array('"MemberPassword"."MemberID"' => $member->ID))
|
|
|
|
->sort('"Created" DESC, "ID" DESC')
|
|
|
|
->limit($this->historicalPasswordCount);
|
|
|
|
/** @var MemberPassword $previousPassword */
|
|
|
|
foreach ($previousPasswords as $previousPassword) {
|
|
|
|
if ($previousPassword->checkPassword($password)) {
|
|
|
|
$valid->addError(
|
|
|
|
_t(
|
|
|
|
'PasswordValidator.PREVPASSWORD',
|
|
|
|
'You\'ve already used that password in the past, please choose a new password'
|
|
|
|
),
|
|
|
|
'bad',
|
|
|
|
'PREVIOUS_PASSWORD'
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-15 08:53:05 +02:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
return $valid;
|
|
|
|
}
|
2012-03-24 04:04:52 +01:00
|
|
|
}
|