Meged from upstream master

This commit is contained in:
Alec Pritchard
2018-02-16 15:33:13 +00:00
22 changed files with 653 additions and 29 deletions

View File

@ -1,11 +1,22 @@
# CHANGELOG
## 1.1.2 - Work in progress
## 1.1.4 - Work in progress
- Enh: Check enableEmailConfirmation on registration (faenir)
- Fix #154: Fix DateTime constructor with Unix timestamps (tonydspaniard)
## 1.1.2-3 - February 9, 2018
- Bug: Bugfix for Model events UserEvent::EVENT_BEFORE_CONFIRMATION and UserEvent::EVENT_AFTER_CONFIRMATION (ajmedway)
- Bug: Bugfix for Model events UserEvent::EVENT_BEFORE_CREATE and UserEvent::EVENT_AFTER_CREATE (ajmedway)
- Enh #137: Added the ability to make `enableAutologin` configurable (pappfer)
- Enh #135: Added Estonian translation (tonisormisson)
- Bug #133: Fix user search returning no results in admin page (phiurs)
- Bug #125: Fix validation in non-ajax requests (faenir)
- Bug #122: Fix wrong email message for email address change (liviuk2)
- Bug #102: Implemented password expiration feature (maxxer)
- Enh #143: Introduced "conflict" configuration in composer.json (maxxer)
- Enh #145: Allowed the `+` sign in username (maxxer)
- Bug #9: Documentation about migration from Dektrium tools (maxxer)
- Bug #110: Honor `enableFlashMessages` in `PasswordRecoveryService` (maxxer)
## 1.1.1 - November 27, 2017
- Bug #115: Convert client_id to string because pgsql fail with type convertion (Dezinger)

View File

@ -89,6 +89,10 @@
"pattern-skip-version": "(-build|-patch)"
}
},
"conflict": {
"dektrium/yii2-rbac": "*",
"dektrium/yii2-user": "*"
},
"extra": {
"bootstrap": "Da\\User\\Bootstrap"
}

View File

@ -110,10 +110,27 @@ Once we have it installed, we have to configure it on your `config.php` file.
'modules' => [
'user' => [
'class' => Da\User\Module::class,
// ...other configs from here: [Configuration Options](installation/configuration-options.md), e.g.
// 'generatePasswords' => true,
// 'switchIdentitySessionKey' => 'myown_usuario_admin_user_key',
]
]
```
NOTE: If you are using the Yii2 Basic Template, make sure you remove this (default user model config) from your `config.php`,
i.e. `config/web.php` file:
```php
'components' => [
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
],
```
This will ensure the proper functionality of login/guest user detection etc.
Configuration may differ from template to template, the following are some guidelines for sidekit app template and
the official Yii2 advanced application template:
@ -127,6 +144,7 @@ See also all the possible configuration options available:
- [Console Commands](installation/console-commands.md)
- [Mailer](installation/mailer.md)
- [Available Actions](installation/available-actions.md)
- [Migration guide from Dektrium tools](installation/migration-guide-from-dektrium-tools.md)
Enhancing and Overriding
------------------------
@ -171,4 +189,4 @@ Contributing
- [How to Contribute](contributing/how-to.md)
- [Clean Code](contributing/clean-code.md)
© [2amigos](http://www.2amigos.us/) 2013-2017
© [2amigos](http://www.2amigos.us/) 2013-2018

View File

@ -12,7 +12,7 @@ packages, and everything you need to start project in no time with batteries inc
> [Check this manual page](sidekit-application-template.md) if you decided to use it.
If you want to use the default [Basic Application Template](https://github.com/yiisoft/yii2-app-basic) with our
extension, then check [Basic Application Template documentation page](basic-application-template.md).
extension, then check [Basic Application Template documentation page](yii2-application-template.md).
Step 1 - Install Advanced Application template
----------------------------------------------

View File

@ -48,6 +48,25 @@ If `true` it will enable password recovery process.
If `true` and `allowPasswordRecovery` is false, it will enable administrator to send a password recovery email to a
user.
#### maxPasswordAge (type: `integer`, default: `null`)
If set to an integer value it will check user password age. If the days since last password change are greater than this configuration value
user will be forced to change it. This enforcement is done only at login stage. In order to perform the check in every action you must configure
a filter into your controller like this:
```
use Da\User\Filter\PasswordAgeEnforceFilter;
class SiteController extends Controller
{
public function behaviors()
{
return [
[...]
'enforcePasswordAge' => [
'class' => PasswordAgeEnforceFilter::className(),
],
```
This will redirect the user to their account page until the password has been updated.
#### allowAccountDelete (type: `boolean`, default: `true`)
If `true` users will be able to remove their own accounts.

View File

@ -0,0 +1,387 @@
# Migration guide from Dektrium tools
yii2-usuario is 99% compatible with [dektrium](https://github.com/dektrium/) tools.
## Package removal
First of all you need to remove the old packages. Depending on your installation you
need to remove one or both:
```
composer remove dektrium/yii2-user
composer remove dektrium/yii2-rbac
```
## Configuration
Configure the `config/console.php` stuff:
```php
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
```
Configure the controller map for migrations
```php
'controllerMap' => [
'migrate' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationNamespaces' => [
'Da\User\Migration',
],
],
],
```
Remove the *modules > rbac* configuration parameter, and replace the value of *modules > user* with:
```php
'user' => Da\User\Module::class,
```
In `config/web.php` remove *module > rbac* configuration and change the *modules > user* with:
```php
...
'user' => [
'class' => Da\User\Module::class,
// Othe yii2-usuario configuration parameters
'enableRegistration' => false,
],
...
```
* If you had `modelMap` customization you have to replace them with `classMap`.
* In your extended model replace the `BaseUser` inheritance from `dektrium\user\models\User` to `Da\User\Model\User`
* If you had controller remapping replace the inheritance from `dektrium\user\controllers\XX` to `Da\User\Controller\XX`
## Rbac migrations
[yii2-rbac](https://github.com/dektrium/yii2-rbac) had a nice tool which are rbac migrations, which helped writing new permissions and roles.
There's no such feature in yii2-usuario, but in case you need to still apply them you can:
1. create a migration component which basically it's the same as the original [Migration](https://github.com/dektrium/yii2-rbac/blob/master/migrations/Migration.php) object, with some minor changes. Copy the content below and save it in your `@app/components/RbacMigration.php`:
```php
<?php
/*
* This file is part of the Dektrium project.
*
* (c) Dektrium project <http://github.com/dektrium/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace app\components;
use yii\rbac\DbManager;
use yii\db\Migration;
use yii\di\Instance;
use yii\rbac\Item;
use yii\rbac\Permission;
use yii\rbac\Role;
use yii\rbac\Rule;
/**
* Migration for applying new RBAC items.
*
* @author Dmitry Erofeev <dmeroff@gmail.com>
*/
class RbacMigration extends Migration
{
/**
* @var string|DbManager The auth manager component ID that this migration should work with.
*/
public $authManager = 'authManager';
/**
* Initializes the migration.
* This method will set [[authManager]] to be the 'authManager' application component, if it is `null`.
*/
public function init()
{
parent::init();
$this->authManager = Instance::ensure($this->authManager, DbManager::className());
}
/**
* Creates new permission.
*
* @param string $name The name of the permission
* @param string $description The description of the permission
* @param string|null $ruleName The rule associated with the permission
* @param mixed|null $data The additional data associated with the permission
* @return Permission
*/
protected function createPermission($name, $description = '', $ruleName = null, $data = null)
{
echo " > create permission $name ...";
$time = microtime(true);
$permission = $this->authManager->createPermission($name);
$permission->description = $description;
$permission->ruleName = $ruleName;
$permission->data = $data;
$this->authManager->add($permission);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
return $permission;
}
/**
* Creates new role.
*
* @param string $name The name of the role
* @param string $description The description of the role
* @param string|null $ruleName The rule associated with the role
* @param mixed|null $data The additional data associated with the role
* @return Role
*/
protected function createRole($name, $description = '', $ruleName = null, $data = null)
{
echo " > create role $name ...";
$time = microtime(true);
$role = $this->authManager->createRole($name);
$role->description = $description;
$role->ruleName = $ruleName;
$role->data = $data;
$this->authManager->add($role);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
return $role;
}
/**
* Creates new rule.
*
* @param string $ruleName The name of the rule
* @param string|array $definition The class of the rule
* @return Rule
*/
protected function createRule($ruleName, $definition)
{
echo " > create rule $ruleName ...";
$time = microtime(true);
if (is_array($definition)) {
$definition['name'] = $ruleName;
} else {
$definition = [
'class' => $definition,
'name' => $ruleName,
];
}
/** @var Rule $rule */
$rule = \Yii::createObject($definition);
$this->authManager->add($rule);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
return $rule;
}
/**
* Finds either role or permission or throws an exception if it is not found.
*
* @param string $name
* @return Permission|Role|null
*/
protected function findItem($name)
{
$item = $this->authManager->getRole($name);
if ($item instanceof Role) {
return $item;
}
$item = $this->authManager->getPermission($name);
if ($item instanceof Permission) {
return $item;
}
return null;
}
/**
* Finds the role or throws an exception if it is not found.
*
* @param string $name
* @return Role|null
*/
protected function findRole($name)
{
$role = $this->authManager->getRole($name);
if ($role instanceof Role) {
return $role;
}
return null;
}
/**
* Finds the permission or throws an exception if it is not found.
*
* @param string $name
* @return Permission|null
*/
protected function findPermission($name)
{
$permission = $this->authManager->getPermission($name);
if ($permission instanceof Permission) {
return $permission;
}
return null;
}
/**
* Removes auth item.
*
* @param string|Item $item Either item name or item instance to be removed.
*/
protected function removeItem($item)
{
if (is_string($item)) {
$item = $this->findItem($item);
}
echo " > removing $item->name ...";
$time = microtime(true);
$this->authManager->remove($item);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Adds child.
*
* @param Item|string $parent Either name or Item instance which is parent
* @param Item|string $child Either name or Item instance which is child
*/
protected function addChild($parent, $child)
{
if (is_string($parent)) {
$parent = $this->findItem($parent);
}
if (is_string($child)) {
$child = $this->findItem($child);
}
echo " > adding $child->name as child to $parent->name ...";
$time = microtime(true);
$this->authManager->addChild($parent, $child);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Assigns a role to a user.
*
* @param string|Role $role
* @param string|int $userId
*/
protected function assign($role, $userId)
{
if (is_string($role)) {
$role = $this->findRole($role);
}
echo " > assigning $role->name to user $userId ...";
$time = microtime(true);
$this->authManager->assign($role, $userId);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Updates role.
*
* @param string|Role $role
* @param string $description
* @param string $ruleName
* @param mixed $data
* @return Role
*/
protected function updateRole($role, $description = '', $ruleName = null, $data = null)
{
if (is_string($role)) {
$role = $this->findRole($role);
}
echo " > update role $role->name ...";
$time = microtime(true);
$role->description = $description;
$role->ruleName = $ruleName;
$role->data = $data;
$this->authManager->update($role->name, $role);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
return $role;
}
/**
* Updates permission.
*
* @param string|Permission $permission
* @param string $description
* @param string $ruleName
* @param mixed $data
* @return Permission
*/
protected function updatePermission($permission, $description = '', $ruleName = null, $data = null)
{
if (is_string($permission)) {
$permission = $this->findPermission($permission);
}
echo " > update permission $permission->name ...";
$time = microtime(true);
$permission->description = $description;
$permission->ruleName = $ruleName;
$permission->data = $data;
$this->authManager->update($permission->name, $permission);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
return $permission;
}
/**
* Updates rule.
*
* @param string $ruleName
* @param string $className
* @return Rule
*/
protected function updateRule($ruleName, $className)
{
echo " > update rule $ruleName ...";
$time = microtime(true);
/** @var Rule $rule */
$rule = \Yii::createObject([
'class' => $className,
'name' => $ruleName,
]);
$this->authManager->update($ruleName, $rule);
echo ' done (time: ' . sprintf('%.3f', microtime(true) - $time) . "s)\n";
return $rule;
}
}
```
2. change the inheritance of the `@app/rbac/migrations` files to `app\components\RbacMigration as Migration`
... and you're done! You can still apply your rbac migrations with `./yii migrate/up --migrationPath=@yii/rbac/migrations`.

View File

@ -25,6 +25,10 @@ use yii\console\Application as ConsoleApplication;
use yii\i18n\PhpMessageSource;
use yii\web\Application as WebApplication;
use yii\base\Event;
use Da\User\Event\FormEvent;
use Da\User\Controller\SecurityController;
/**
* Bootstrap class of the yii2-usuario extension. Configures container services, initializes translations,
* builds class map, and does the other setup actions participating in the application bootstrap process.
@ -91,6 +95,7 @@ class Bootstrap implements BootstrapInterface
// services
$di->set(Service\AccountConfirmationService::class);
$di->set(Service\EmailChangeService::class);
$di->set(Service\PasswordExpireService::class);
$di->set(Service\PasswordRecoveryService::class);
$di->set(Service\ResendConfirmationService::class);
$di->set(Service\ResetPasswordService::class);
@ -144,12 +149,24 @@ class Bootstrap implements BootstrapInterface
$di->set(Search\RoleSearch::class);
}
// Attach an event to check if the password has expired
if (!is_null(Yii::$app->getModule('user')->maxPasswordAge)) {
Event::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN, function (FormEvent $event) {
$user = $event->form->user;
if ($user->password_age >= Yii::$app->getModule('user')->maxPasswordAge) {
// Force password change
Yii::$app->session->setFlash('warning', Yii::t('usuario', 'Your password has expired, you must change it now'));
Yii::$app->response->redirect(['/user/settings/account'])->send();
}
});
}
if ($app instanceof WebApplication) {
// override Yii
$di->set(
'yii\web\User',
[
'enableAutoLogin' => $app->getModule('user')->enableAutologin,
'enableAutoLogin' => $app->getModule('user')->enableAutoLogin,
'loginUrl' => ['/user/security/login'],
'identityClass' => $di->get(ClassMapHelper::class)->get(User::class),
]

View File

@ -18,6 +18,7 @@ use Da\User\Model\Profile;
use Da\User\Model\User;
use Da\User\Query\UserQuery;
use Da\User\Search\UserSearch;
use Da\User\Service\PasswordExpireService;
use Da\User\Service\PasswordRecoveryService;
use Da\User\Service\SwitchIdentityService;
use Da\User\Service\UserBlockService;
@ -328,4 +329,20 @@ class AdminController extends Controller
return $this->redirect(['index']);
}
/**
* Forces the user to change password at next login
* @param integer $id
*/
public function actionForcePasswordChange($id)
{
/** @var User $user */
$user = $this->userQuery->where(['id' => $id])->one();
if ($this->make(PasswordExpireService::class, [$user])->run()) {
Yii::$app->session->setFlash("success", Yii::t('usuario', 'User will be required to change password at next login'));
} else {
Yii::$app->session->setFlash("danger", Yii::t('usuario', 'There was an error in saving user'));
}
$this->redirect(['index']);
}
}

View File

@ -106,13 +106,17 @@ class RegistrationController extends Controller
$mailService = MailFactory::makeWelcomeMailerService($user);
if ($this->make(UserRegisterService::class, [$user, $mailService])->run()) {
Yii::$app->session->setFlash(
'info',
Yii::t(
'usuario',
'Your account has been created and a message with further instructions has been sent to your email'
)
);
if ($this->module->enableEmailConfirmation) {
Yii::$app->session->setFlash(
'info',
Yii::t(
'usuario',
'Your account has been created and a message with further instructions has been sent to your email'
)
);
} else {
Yii::$app->session->setFlash('info', Yii::t('usuario', 'Your account has been created'));
}
$this->trigger(FormEvent::EVENT_AFTER_REGISTER, $event);
return $this->render(
'/shared/message',

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the 2amigos/yii2-usuario project.
*
* (c) 2amigOS! <http://2amigos.us/>
* @author Lorenzo Milesi <maxxer@yetopen.it>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Da\User\Filter;
use Yii;
use yii\base\ActionFilter;
class PasswordAgeEnforceFilter extends ActionFilter
{
public function beforeAction($action)
{
$maxPasswordAge = Yii::$app->getModule('user')->maxPasswordAge;
// If feature is not set do nothing (or raise a configuration error?)
if (is_null($maxPasswordAge)) {
return parent::beforeAction($action);
}
if (Yii::$app->user->isGuest) {
// Not our business
return parent::beforeAction($action);
}
if (Yii::$app->user->identity->password_age >= $maxPasswordAge) {
// Force password change
Yii::$app->getSession()->setFlash('warning', Yii::t('usuario', 'Your password has expired, you must change it now'));
return Yii::$app->response->redirect(['/user/settings/account'])->send();
}
return parent::beforeAction($action);
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the 2amigos/yii2-usuario project.
*
* (c) 2amigOS! <http://2amigos.us/>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Da\User\Migration;
use yii\db\Migration;
class m000000_000007_enable_password_expiration extends Migration
{
public function safeUp()
{
$this->addColumn('{{%user}}', 'password_changed_at', $this->integer()->null());
}
public function safeDown()
{
$this->dropColumn('{{%user}}', 'password_changed_at');
}
}

View File

@ -22,6 +22,7 @@ use yii\base\InvalidParamException;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\db\Expression;
use yii\helpers\ArrayHelper;
use yii\web\Application;
use yii\web\IdentityInterface;
@ -49,6 +50,8 @@ use yii\web\IdentityInterface;
* @property int $created_at
* @property int $updated_at
* @property int $last_login_at
* @property int $password_changed_at
* @property int $password_age
*
* Defined relations:
* @property SocialNetworkAccount[] $socialNetworkAccounts
@ -95,6 +98,7 @@ class User extends ActiveRecord implements IdentityInterface
'password_hash',
$security->generatePasswordHash($this->password, $this->getModule()->blowfishCost)
);
$this->password_changed_at = time();
}
return parent::beforeSave($insert);
@ -147,6 +151,8 @@ class User extends ActiveRecord implements IdentityInterface
'created_at' => Yii::t('usuario', 'Registration time'),
'confirmed_at' => Yii::t('usuario', 'Confirmation time'),
'last_login_at' => Yii::t('usuario', 'Last login'),
'password_changed_at' => Yii::t('usuario', 'Last password change'),
'password_age' => Yii::t('usuario', 'Password age'),
];
}
@ -175,7 +181,7 @@ class User extends ActiveRecord implements IdentityInterface
return [
// username rules
'usernameRequired' => ['username', 'required', 'on' => ['register', 'create', 'connect', 'update']],
'usernameMatch' => ['username', 'match', 'pattern' => '/^[-a-zA-Z0-9_\.@]+$/'],
'usernameMatch' => ['username', 'match', 'pattern' => '/^[-a-zA-Z0-9_\.@\+]+$/'],
'usernameLength' => ['username', 'string', 'min' => 3, 'max' => 255],
'usernameTrim' => ['username', 'trim'],
'usernameUnique' => [
@ -328,4 +334,17 @@ class User extends ActiveRecord implements IdentityInterface
{
throw new NotSupportedException('Method "' . __CLASS__ . '::' . __METHOD__ . '" is not implemented.');
}
/**
* Returns password age in days
* @return integer
*/
public function getPassword_age()
{
if (is_null($this->password_changed_at)) {
return $this->getModule()->maxPasswordAge;
}
$d = new \DateTime("@{$this->password_changed_at}");
return $d->diff(new \DateTime(), true)->format("%a");
}
}

View File

@ -29,9 +29,9 @@ class Module extends BaseModule
*/
public $twoFactorAuthenticationCycles = 1;
/**
* @var bool whether to allow autologin or not
* @var bool whether to allow auto login or not
*/
public $enableAutologin = true;
public $enableAutoLogin = true;
/**
* @var bool whether to allow registration process or not
*/
@ -132,4 +132,8 @@ class Module extends BaseModule
* @var string the session key name to impersonate users. Please, modify it for security reasons!
*/
public $switchIdentitySessionKey = 'yuik_usuario';
/**
* @var integer If != NULL sets a max password age in days
*/
public $maxPasswordAge = null;
}

View File

@ -66,8 +66,6 @@ class RuleSearch extends Rule
if (!$this->validate()) {
$query->where('0=1');
var_dump($this->load($params));
die();
}
return $this->make(

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the 2amigos/yii2-usuario project.
*
* (c) 2amigOS! <http://2amigos.us/>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Da\User\Service;
use Da\User\Contracts\ServiceInterface;
use Da\User\Model\User;
class PasswordExpireService implements ServiceInterface
{
protected $model;
public function __construct(User $model)
{
$this->model = $model;
}
public function run()
{
return $this->model->updateAttributes(['last_login_at' => time()]);
}
}

View File

@ -16,12 +16,14 @@ use Da\User\Factory\TokenFactory;
use Da\User\Model\User;
use Da\User\Query\UserQuery;
use Da\User\Traits\MailAwareTrait;
use Da\User\Traits\ModuleAwareTrait;
use Exception;
use Yii;
class PasswordRecoveryService implements ServiceInterface
{
use MailAwareTrait;
use ModuleAwareTrait;
protected $query;
@ -58,10 +60,12 @@ class PasswordRecoveryService implements ServiceInterface
return false;
}
Yii::$app->session->setFlash(
'info',
Yii::t('usuario', 'An email has been sent with instructions for resetting your password')
);
if ($this->getModule()->enableFlashMessages == true) {
Yii::$app->session->setFlash(
'info',
Yii::t('usuario', 'An email has been sent with instructions for resetting your password')
);
}
return true;
} catch (Exception $e) {

View File

@ -14,9 +14,12 @@ namespace Da\User\Service;
use Da\User\Contracts\ServiceInterface;
use Da\User\Event\UserEvent;
use Da\User\Model\User;
use Da\User\Traits\MailAwareTrait;
class UserConfirmationService implements ServiceInterface
{
use MailAwareTrait;
protected $model;
public function __construct(User $model)
@ -26,9 +29,12 @@ class UserConfirmationService implements ServiceInterface
public function run()
{
$this->model->trigger(UserEvent::EVENT_BEFORE_CONFIRMATION);
$model = $this->model;
$event = $this->make(UserEvent::class, [$model]);
$this->model->trigger(UserEvent::EVENT_BEFORE_CONFIRMATION, $event);
if ((bool)$this->model->updateAttributes(['confirmed_at' => time()])) {
$this->model->trigger(UserEvent::EVENT_AFTER_CONFIRMATION);
$this->model->trigger(UserEvent::EVENT_AFTER_CONFIRMATION, $event);
return true;
}

View File

@ -57,14 +57,15 @@ class UserCreateService implements ServiceInterface
? $model->password
: $this->securityHelper->generatePassword(8);
$model->trigger(UserEvent::EVENT_BEFORE_CREATE);
$event = $this->make(UserEvent::class, [$model]);
$model->trigger(UserEvent::EVENT_BEFORE_CREATE, $event);
if (!$model->save()) {
$transaction->rollBack();
return false;
}
$model->trigger(UserEvent::EVENT_AFTER_CREATE);
$model->trigger(UserEvent::EVENT_AFTER_CREATE, $event);
if (!$this->sendMail($model)) {
Yii::$app->session->setFlash(
'warning',

View File

@ -54,7 +54,7 @@ class ReCaptchaWidget extends InputWidget
*/
public function init()
{
if (Yii::$app->get('recaptcha')) {
if (!Yii::$app->get('recaptcha')) {
throw new InvalidConfigException(Yii::t('usuario', 'The "recaptcha" component must be configured.'));
}

View File

@ -92,6 +92,7 @@ $module = Yii::$app->getModule('user');
'format' => 'raw',
'visible' => Yii::$app->getModule('user')->enableEmailConfirmation,
],
'password_age',
[
'header' => Yii::t('usuario', 'Block status'),
'value' => function ($model) {
@ -121,7 +122,7 @@ $module = Yii::$app->getModule('user');
],
[
'class' => 'yii\grid\ActionColumn',
'template' => '{switch} {reset} {update} {delete}',
'template' => '{switch} {reset} {force-password-change} {update} {delete}',
'buttons' => [
'switch' => function ($url, $model) use ($module) {
if ($model->id != Yii::$app->user->id && $module->enableSwitchIdentities) {
@ -158,7 +159,24 @@ $module = Yii::$app->getModule('user');
}
return null;
}
},
'force-password-change' => function ($url, $model) use ($module) {
if (is_null($module->maxPasswordAge)) {
return null;
}
return Html::a(
'<span class="glyphicon glyphicon-time"></span>',
['/user/admin/force-password-change', 'id' => $model->id],
[
'title' => Yii::t('usuario', 'Force password change at next login'),
'data-confirm' => Yii::t(
'usuario',
'Are you sure you wish the user to change their password at next login?'
),
'data-method' => 'POST',
]
);
},
]
],
],

View File

@ -73,6 +73,7 @@ CREATE TABLE `user` (
`auth_tf_enabled` tinyint(1) DEFAULT '0',
`flags` int(11) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL,
`password_changed_at` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_unique_email` (`email`),

View File

@ -50,7 +50,7 @@ class RegistrationCest
$I->see(Html::encode('This email address has already been taken'));
$this->register($I, 'tester@example.com', 'tester', 'tester');
$I->see('Your account has been created and a message with further instructions has been sent to your email');
$I->see('Your account has been created');
$user = $I->grabRecord(User::className(), ['email' => 'tester@example.com']);
$I->assertTrue($user->isConfirmed);
@ -96,7 +96,7 @@ class RegistrationCest
]);
$I->amOnRoute('/user/registration/register');
$this->register($I, 'tester@example.com', 'tester');
$I->see('Your account has been created and a message with further instructions has been sent to your email');
$I->see('Your account has been created');
$user = $I->grabRecord(User::className(), ['email' => 'tester@example.com']);
$I->assertEquals('tester', $user->username);
/** @var yii\swiftmailer\Message $message */