Password expiration feature #102
It's still missing an enforcement which redirects all actions to profile update until the password is changed
This commit is contained in:
@ -24,6 +24,10 @@ use yii\console\Application as ConsoleApplication;
|
|||||||
use yii\i18n\PhpMessageSource;
|
use yii\i18n\PhpMessageSource;
|
||||||
use yii\web\Application as WebApplication;
|
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,
|
* 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.
|
* builds class map, and does the other setup actions participating in the application bootstrap process.
|
||||||
@ -88,6 +92,7 @@ class Bootstrap implements BootstrapInterface
|
|||||||
// services
|
// services
|
||||||
$di->set(Service\AccountConfirmationService::class);
|
$di->set(Service\AccountConfirmationService::class);
|
||||||
$di->set(Service\EmailChangeService::class);
|
$di->set(Service\EmailChangeService::class);
|
||||||
|
$di->set(Service\PasswordExpireService::class);
|
||||||
$di->set(Service\PasswordRecoveryService::class);
|
$di->set(Service\PasswordRecoveryService::class);
|
||||||
$di->set(Service\ResendConfirmationService::class);
|
$di->set(Service\ResendConfirmationService::class);
|
||||||
$di->set(Service\ResetPasswordService::class);
|
$di->set(Service\ResetPasswordService::class);
|
||||||
@ -141,6 +146,19 @@ class Bootstrap implements BootstrapInterface
|
|||||||
$di->set(Search\RoleSearch::class);
|
$di->set(Search\RoleSearch::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attach an event to check if the password has expired
|
||||||
|
Event::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN, function (FormEvent $event) {
|
||||||
|
if (is_null(Yii::$app->getModule('user')->maxPasswordAge)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$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) {
|
if ($app instanceof WebApplication) {
|
||||||
// override Yii
|
// override Yii
|
||||||
$di->set(
|
$di->set(
|
||||||
|
|||||||
@ -18,6 +18,7 @@ use Da\User\Model\Profile;
|
|||||||
use Da\User\Model\User;
|
use Da\User\Model\User;
|
||||||
use Da\User\Query\UserQuery;
|
use Da\User\Query\UserQuery;
|
||||||
use Da\User\Search\UserSearch;
|
use Da\User\Search\UserSearch;
|
||||||
|
use Da\User\Service\PasswordExpireService;
|
||||||
use Da\User\Service\PasswordRecoveryService;
|
use Da\User\Service\PasswordRecoveryService;
|
||||||
use Da\User\Service\SwitchIdentityService;
|
use Da\User\Service\SwitchIdentityService;
|
||||||
use Da\User\Service\UserBlockService;
|
use Da\User\Service\UserBlockService;
|
||||||
@ -329,4 +330,20 @@ class AdminController extends Controller
|
|||||||
|
|
||||||
return $this->redirect(['index']);
|
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']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}}', 'last_password_change', $this->timestamp()->null());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function safeDown()
|
||||||
|
{
|
||||||
|
$this->dropColumn('{{%user}}', 'last_password_change');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,6 +19,7 @@ use Yii;
|
|||||||
use yii\base\NotSupportedException;
|
use yii\base\NotSupportedException;
|
||||||
use yii\behaviors\TimestampBehavior;
|
use yii\behaviors\TimestampBehavior;
|
||||||
use yii\db\ActiveRecord;
|
use yii\db\ActiveRecord;
|
||||||
|
use yii\db\Expression;
|
||||||
use yii\helpers\ArrayHelper;
|
use yii\helpers\ArrayHelper;
|
||||||
use yii\web\Application;
|
use yii\web\Application;
|
||||||
use yii\web\IdentityInterface;
|
use yii\web\IdentityInterface;
|
||||||
@ -46,6 +47,8 @@ use yii\web\IdentityInterface;
|
|||||||
* @property int $created_at
|
* @property int $created_at
|
||||||
* @property int $updated_at
|
* @property int $updated_at
|
||||||
* @property int $last_login_at
|
* @property int $last_login_at
|
||||||
|
* @property int $last_password_change
|
||||||
|
* @property int $password_age
|
||||||
*
|
*
|
||||||
* Defined relations:
|
* Defined relations:
|
||||||
* @property SocialNetworkAccount[] $socialNetworkAccounts
|
* @property SocialNetworkAccount[] $socialNetworkAccounts
|
||||||
@ -88,6 +91,7 @@ class User extends ActiveRecord implements IdentityInterface
|
|||||||
'password_hash',
|
'password_hash',
|
||||||
$security->generatePasswordHash($this->password, $this->getModule()->blowfishCost)
|
$security->generatePasswordHash($this->password, $this->getModule()->blowfishCost)
|
||||||
);
|
);
|
||||||
|
$this->last_password_change = new Expression("NOW()");
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::beforeSave($insert);
|
return parent::beforeSave($insert);
|
||||||
@ -138,6 +142,8 @@ class User extends ActiveRecord implements IdentityInterface
|
|||||||
'created_at' => Yii::t('usuario', 'Registration time'),
|
'created_at' => Yii::t('usuario', 'Registration time'),
|
||||||
'confirmed_at' => Yii::t('usuario', 'Confirmation time'),
|
'confirmed_at' => Yii::t('usuario', 'Confirmation time'),
|
||||||
'last_login_at' => Yii::t('usuario', 'Last login'),
|
'last_login_at' => Yii::t('usuario', 'Last login'),
|
||||||
|
'last_password_change' => Yii::t('usuario', 'Last password change'),
|
||||||
|
'password_age' => Yii::t('usuario', 'Password age'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,4 +318,17 @@ class User extends ActiveRecord implements IdentityInterface
|
|||||||
{
|
{
|
||||||
throw new NotSupportedException('Method "' . __CLASS__ . '::' . __METHOD__ . '" is not implemented.');
|
throw new NotSupportedException('Method "' . __CLASS__ . '::' . __METHOD__ . '" is not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns password age in days
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getPassword_age()
|
||||||
|
{
|
||||||
|
if (is_null($this->last_password_change)) {
|
||||||
|
return $this->getModule()->maxPasswordAge;
|
||||||
|
}
|
||||||
|
$d = new \DateTime($this->last_password_change);
|
||||||
|
return $d->diff(new \DateTime(), true)->format("%a");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,4 +128,8 @@ class Module extends BaseModule
|
|||||||
* @var string the session key name to impersonate users. Please, modify it for security reasons!
|
* @var string the session key name to impersonate users. Please, modify it for security reasons!
|
||||||
*/
|
*/
|
||||||
public $switchIdentitySessionKey = 'yuik_usuario';
|
public $switchIdentitySessionKey = 'yuik_usuario';
|
||||||
|
/**
|
||||||
|
* @var integer If != NULL sets a max password age in days
|
||||||
|
*/
|
||||||
|
public $maxPasswordAge = null;
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/User/Service/PasswordExpireService.php
Normal file
30
src/User/Service/PasswordExpireService.php
Normal 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()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -92,6 +92,7 @@ $module = Yii::$app->getModule('user');
|
|||||||
'format' => 'raw',
|
'format' => 'raw',
|
||||||
'visible' => Yii::$app->getModule('user')->enableEmailConfirmation,
|
'visible' => Yii::$app->getModule('user')->enableEmailConfirmation,
|
||||||
],
|
],
|
||||||
|
'password_age',
|
||||||
[
|
[
|
||||||
'header' => Yii::t('usuario', 'Block status'),
|
'header' => Yii::t('usuario', 'Block status'),
|
||||||
'value' => function ($model) {
|
'value' => function ($model) {
|
||||||
@ -121,7 +122,7 @@ $module = Yii::$app->getModule('user');
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'class' => 'yii\grid\ActionColumn',
|
'class' => 'yii\grid\ActionColumn',
|
||||||
'template' => '{switch} {reset} {update} {delete}',
|
'template' => '{switch} {reset} {force-password-change} {update} {delete}',
|
||||||
'buttons' => [
|
'buttons' => [
|
||||||
'switch' => function ($url, $model) use ($module) {
|
'switch' => function ($url, $model) use ($module) {
|
||||||
if ($model->id != Yii::$app->user->id && $module->enableSwitchIdentities) {
|
if ($model->id != Yii::$app->user->id && $module->enableSwitchIdentities) {
|
||||||
@ -157,8 +158,25 @@ $module = Yii::$app->getModule('user');
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
'force-password-change' => function ($url, $model) use ($module) {
|
||||||
|
if (is_null($module->maxPasswordAge)) {
|
||||||
return null;
|
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',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
},
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user