diff --git a/docs/installation/configuration-options.md b/docs/installation/configuration-options.md index 5cc51f8..e95f165 100644 --- a/docs/installation/configuration-options.md +++ b/docs/installation/configuration-options.md @@ -220,5 +220,17 @@ Set to `true` to restrict user assignments to roles only. If `true` registration and last login IPs are not logged into users table, instead a dummy 127.0.0.1 is used +#### minPasswordRequirements (type: `array`, default: `['lower' => 1, 'digit' => 1, 'upper' => 1]`) + +Minimum requirements when a new password is automatically generated. +Array structure: `"requirement" => minimum_number_characters`. + +Possible array keys: +- lower: minimum number of lowercase characters; +- upper: minimum number of uppercase characters; +- digit: minimum number of digits; +- special: minimum number of special characters; +- min: minimum number of characters (= minimum length). + © [2amigos](http://www.2amigos.us/) 2013-2019 diff --git a/src/User/Helper/SecurityHelper.php b/src/User/Helper/SecurityHelper.php index f540f0d..dd130dd 100644 --- a/src/User/Helper/SecurityHelper.php +++ b/src/User/Helper/SecurityHelper.php @@ -13,6 +13,7 @@ namespace Da\User\Helper; use yii\base\Exception; use yii\base\Security; +use yii\base\InvalidConfigException; class SecurityHelper { @@ -60,25 +61,36 @@ class SecurityHelper return $this->security->validatePassword($password, $hash); } - public function generatePassword($length) + public function generatePassword($length, $minPasswordRequirements) { $sets = [ - 'abcdefghjkmnpqrstuvwxyz', - 'ABCDEFGHJKMNPQRSTUVWXYZ', - '23456789', + 'lower' => 'abcdefghjkmnpqrstuvwxyz', + 'upper' => 'ABCDEFGHJKMNPQRSTUVWXYZ', + 'digit' => '123456789', + 'special' => '!#$%&()*+,-./:;<=>?@[\]^_{|}~' ]; $all = ''; $password = ''; - foreach ($sets as $set) { - $password .= $set[array_rand(str_split($set))]; + + if (isset($minPasswordRequirements['min']) && $length < $minPasswordRequirements['min']) { + $length = $minPasswordRequirements['min']; + } + foreach ($sets as $setKey => $set) { + if (isset($minPasswordRequirements[$setKey])) { + for ($i = 0; $i < $minPasswordRequirements[$setKey]; $i++) { + $password .= $set[array_rand(str_split($set))]; + } + } $all .= $set; } - + $passwordLength = strlen($password); + if ($passwordLength > $length) { + throw new InvalidConfigException('The minimum length is incompatible with other minimum requirements.'); + } $all = str_split($all); - for ($i = 0; $i < $length - count($sets); ++$i) { + for ($i = 0; $i < $length - $passwordLength; ++$i) { $password .= $all[array_rand($all)]; } - $password = str_shuffle($password); return $password; diff --git a/src/User/Module.php b/src/User/Module.php index 26b8b17..f86c8da 100644 --- a/src/User/Module.php +++ b/src/User/Module.php @@ -210,6 +210,23 @@ class Module extends BaseModule * @var boolean whether to disable IP logging into user table */ public $disableIpLogging = false; + + /** + * @var array Minimum requirements when a new password is automatically generated. + * Array structure: `requirement => minimum number characters`. + * + * Possible array keys: + * - lower: minimum number of lowercase characters; + * - upper: minimum number of uppercase characters; + * - digit: minimum number of digits; + * - special: minimum number of special characters; + * - min: minimum number of characters (= minimum length). + */ + public $minPasswordRequirements = [ + 'lower' => 1, + 'digit' => 1, + 'upper' => 1, + ]; /** * @return string with the hit to be used with the give consent checkbox diff --git a/src/User/Service/UserCreateService.php b/src/User/Service/UserCreateService.php index 60ff7eb..5f7622d 100644 --- a/src/User/Service/UserCreateService.php +++ b/src/User/Service/UserCreateService.php @@ -57,7 +57,7 @@ class UserCreateService implements ServiceInterface $model->confirmed_at = time(); $model->password = !empty($model->password) ? $model->password - : $this->securityHelper->generatePassword(8); + : $this->securityHelper->generatePassword(8, $this->getModule('user')->minPasswordRequirements); /** @var UserEvent $event */ $event = $this->make(UserEvent::class, [$model]); diff --git a/src/User/Service/UserRegisterService.php b/src/User/Service/UserRegisterService.php index eb15983..747d9eb 100644 --- a/src/User/Service/UserRegisterService.php +++ b/src/User/Service/UserRegisterService.php @@ -51,7 +51,7 @@ class UserRegisterService implements ServiceInterface try { $model->confirmed_at = $this->getModule()->enableEmailConfirmation ? null : time(); $model->password = $this->getModule()->generatePasswords - ? $this->securityHelper->generatePassword(8) + ? $this->securityHelper->generatePassword(8, $this->getModule('user')->minPasswordRequirements) : $model->password; $event = $this->make(UserEvent::class, [$model]);