From 14a8e9e9d58f8cc086f3b9f55bd0d307dceb4178 Mon Sep 17 00:00:00 2001 From: Miguel N Date: Wed, 6 Jan 2021 05:03:38 +0000 Subject: [PATCH] Force users to change password This commit applies the password_expiration setting to all users. Users won't be able to do anything other than update password until it's done. --- app/Http/Controllers/UserController.php | 2 + app/Http/Kernel.php | 2 + .../PasswordExpirationMiddleware.php | 40 +++++++++ .../PasswordExpirationRedirectMiddleware.php | 28 ++++++ ...550_add_password_last_updated_to_users.php | 34 +++++++ .../administration/settings.blade.php | 3 +- .../user/profile/useraccount.blade.php | 17 ++++ routes/web.php | 90 +++++++++++-------- 8 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 app/Http/Middleware/PasswordExpirationMiddleware.php create mode 100644 app/Http/Middleware/PasswordExpirationRedirectMiddleware.php create mode 100644 database/migrations/2021_01_06_040550_add_password_last_updated_to_users.php diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 6d94f08..2b9feb7 100755 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -172,6 +172,8 @@ class UserController extends Controller if (! is_null($user)) { $user->password = Hash::make($request->newPassword); + $user->password_last_updated = now(); + $user->save(); Log::info('User '.$user->name.' has changed their password', [ diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index f0d3604..10fc2cf 100755 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -85,6 +85,8 @@ class Kernel extends HttpKernel 'usernameUUID' => \App\Http\Middleware\UsernameUUID::class, 'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class, '2fa' => \PragmaRX\Google2FALaravel\Middleware::class, + 'passwordexpiration' => \App\Http\Middleware\PasswordExpirationMiddleware::class, + 'passwordredirect' => \App\Http\Middleware\PasswordExpirationRedirectMiddleware::class, 'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class, 'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class, 'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class, diff --git a/app/Http/Middleware/PasswordExpirationMiddleware.php b/app/Http/Middleware/PasswordExpirationMiddleware.php new file mode 100644 index 0000000..e76210c --- /dev/null +++ b/app/Http/Middleware/PasswordExpirationMiddleware.php @@ -0,0 +1,40 @@ +password_last_updated)->diffInDays(now()); + $updateThreshold = Options::getOption('password_expiry'); + + if ($updateThreshold !== 0 && $sinceUpdate > $updateThreshold) + { + session()->put('passwordExpired', true); + } + else + { + session()->put('passwordExpired', false); + } + + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/PasswordExpirationRedirectMiddleware.php b/app/Http/Middleware/PasswordExpirationRedirectMiddleware.php new file mode 100644 index 0000000..b4a687f --- /dev/null +++ b/app/Http/Middleware/PasswordExpirationRedirectMiddleware.php @@ -0,0 +1,28 @@ +timestamp('password_last_updated')->after('remember_token')->nullable(); + $table->boolean('administratively_locked')->after('email')->default(false)->comment('Account locked by settings changes, e.g. 2fa grace period timeout'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('password_last_updated'); + $table->dropColumn('administratively_locked'); + }); + } +} diff --git a/resources/views/dashboard/administration/settings.blade.php b/resources/views/dashboard/administration/settings.blade.php index 8bccefa..472feb9 100755 --- a/resources/views/dashboard/administration/settings.blade.php +++ b/resources/views/dashboard/administration/settings.blade.php @@ -100,8 +100,7 @@

DANGER: Insecure security policy

-

Your current password security policy is set to off. This allows users to choose potentially unsafe passwords.

-

We strongly recommend you update this value to Low or Medium.

+

Your current password security policy is set to off. This allows users to choose potentially unsafe passwords. We strongly recommend you update this value to Medium.

diff --git a/resources/views/dashboard/user/profile/useraccount.blade.php b/resources/views/dashboard/user/profile/useraccount.blade.php index fde820d..b05dde1 100755 --- a/resources/views/dashboard/user/profile/useraccount.blade.php +++ b/resources/views/dashboard/user/profile/useraccount.blade.php @@ -205,6 +205,23 @@ + @if(session('passwordExpired')) + +
+
+
+

Your password has expired

+

+ You've been redirected here because your password has expired. All users must change their password every {{ \App\Facades\Options::getOption('password_expiry') }} days. + This is put in place to make sure user accounts remain secure. +

+ +

Please change update your password now. You won't be able to use the application until you do this.

+
+
+
+ @endif +
diff --git a/routes/web.php b/routes/web.php index 8a44766..b13425b 100755 --- a/routes/web.php +++ b/routes/web.php @@ -67,45 +67,50 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo Route::get('/accounts/danger-zone/{ID}/{action}/{token}', [UserController::class, 'processDeleteConfirmation']) ->name('processDeleteConfirmation'); - Route::group(['middleware' => ['auth', 'forcelogout', '2fa', 'verified']], function () { - Route::get('/dashboard', [DashboardController::class, 'index']) + Route::group(['middleware' => ['auth', 'forcelogout', 'passwordexpiration', '2fa', 'verified']], function () { + + + + Route::group(['middleware' => ['passwordredirect']], function(){ + + Route::get('/dashboard', [DashboardController::class, 'index']) ->name('dashboard') ->middleware('eligibility'); - Route::get('users/directory', [ProfileController::class, 'index']) - ->name('directory'); + Route::get('users/directory', [ProfileController::class, 'index']) + ->name('directory'); - Route::resource('teams', TeamController::class); + Route::resource('teams', TeamController::class); - Route::post('teams/{team}/invites/send', [TeamController::class, 'invite']) - ->name('sendInvite'); + Route::post('teams/{team}/invites/send', [TeamController::class, 'invite']) + ->name('sendInvite'); - Route::get('teams/{team}/switch', [TeamController::class, 'switchTeam']) - ->name('switchTeam'); + Route::get('teams/{team}/switch', [TeamController::class, 'switchTeam']) + ->name('switchTeam'); - Route::patch('teams/{team}/vacancies/update', [TeamController::class, 'assignVacancies']) - ->name('assignVacancies'); + Route::patch('teams/{team}/vacancies/update', [TeamController::class, 'assignVacancies']) + ->name('assignVacancies'); - Route::get('teams/invites/{action}/{token}', [TeamController::class, 'processInviteAction']) - ->name('processInvite'); + Route::get('teams/invites/{action}/{token}', [TeamController::class, 'processInviteAction']) + ->name('processInvite'); - Route::get('team/files', [TeamFileController::class, 'index']) - ->name('showTeamFiles'); + Route::get('team/files', [TeamFileController::class, 'index']) + ->name('showTeamFiles'); - Route::post('team/files/upload', [TeamFileController::class, 'store']) - ->name('uploadTeamFile'); + Route::post('team/files/upload', [TeamFileController::class, 'store']) + ->name('uploadTeamFile'); - Route::delete('team/files/{teamFile}/delete', [TeamFileController::class, 'destroy']) - ->name('deleteTeamFile'); + Route::delete('team/files/{teamFile}/delete', [TeamFileController::class, 'destroy']) + ->name('deleteTeamFile'); - Route::get('team/files/{teamFile}/download', [TeamFileController::class, 'download']) - ->name('downloadTeamFile'); + Route::get('team/files/{teamFile}/download', [TeamFileController::class, 'download']) + ->name('downloadTeamFile'); + + }); - - - Route::group(['prefix' => '/applications'], function () { + Route::group(['prefix' => '/applications', 'middleware' => ['passwordredirect']], function () { Route::get('/my-applications', [ApplicationController::class, 'showUserApps']) ->name('showUserApps') ->middleware('eligibility'); @@ -136,7 +141,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo ->name('voteApplication'); }); - Route::group(['prefix' => 'appointments'], function () { + Route::group(['prefix' => 'appointments', 'middleware' => ['passwordredirect']], function () { Route::post('schedule/appointments/{application}', [AppointmentController::class, 'saveAppointment']) ->name('scheduleAppointment'); @@ -144,7 +149,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo ->name('updateAppointment'); }); - Route::group(['prefix' => 'apply', 'middleware' => ['eligibility']], function () { + Route::group(['prefix' => 'apply', 'middleware' => ['eligibility', 'passwordredirect']], function () { Route::get('positions/{vacancySlug}', [ApplicationController::class, 'renderApplicationForm']) ->name('renderApplicationForm'); @@ -152,15 +157,21 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo ->name('saveApplicationForm'); }); + // Further locking down the profile section by adding the middleware to everything but the required routes Route::group(['prefix' => '/profile'], function () { Route::get('/settings', [ProfileController::class, 'showProfile']) - ->name('showProfileSettings'); + ->name('showProfileSettings') + ->middleware('passwordredirect'); Route::patch('/settings/save', [ProfileController::class, 'saveProfile']) - ->name('saveProfileSettings'); + ->name('saveProfileSettings') + ->middleware('passwordredirect'); Route::get('user/{user}', [ProfileController::class, 'showSingleProfile']) - ->name('showSingleProfile'); + ->name('showSingleProfile') + ->middleware('passwordredirect'); + + Route::get('/settings/account', [UserController::class, 'showAccount']) ->name('showAccountSettings'); @@ -169,23 +180,30 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo Route::patch('/settings/account/change-password', [UserController::class, 'changePassword']) ->name('changePassword'); + + Route::patch('/settings/account/change-email', [UserController::class, 'changeEmail']) - ->name('changeEmail'); + ->name('changeEmail') + ->middleware('passwordredirect'); Route::post('/settings/account/flush-sessions', [UserController::class, 'flushSessions']) - ->name('flushSessions'); + ->name('flushSessions') + ->middleware('passwordredirect'); Route::patch('/settings/account/twofa/enable', [UserController::class, 'add2FASecret']) - ->name('enable2FA'); + ->name('enable2FA') + ->middleware('passwordredirect'); Route::patch('/settings/account/twofa/disable', [UserController::class, 'remove2FASecret']) - ->name('disable2FA'); + ->name('disable2FA') + ->middleware('passwordredirect'); Route::patch('/settings/account/dg/delete', [UserController::class, 'userDelete']) - ->name('userDelete'); + ->name('userDelete') + ->middleware('passwordredirect'); }); - Route::group(['prefix' => '/hr'], function () { + Route::group(['prefix' => '/hr', 'middleware' => ['passwordredirect']], function () { Route::get('staff-members', [UserController::class, 'showStaffMembers']) ->name('staffMemberList'); @@ -199,7 +217,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo ->name('terminateStaffMember'); }); - Route::group(['prefix' => 'admin'], function () { + Route::group(['prefix' => 'admin', 'middleware' => ['passwordredirect']], function () { Route::get('settings', [OptionsController::class, 'index']) ->name('showSettings');