Added role based MFA control

- Added new DB column for control and role updated create/update actions.
- Created new middleware as a start to actual enforcement logic.
- Added indicator to role list of whether MFA is enforced.
This commit is contained in:
Dan Brown 2021-07-03 13:34:48 +01:00
parent 529971c534
commit 09c2814dc7
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
9 changed files with 75 additions and 5 deletions

View File

@ -57,6 +57,7 @@ class PermissionsRepo
public function saveNewRole(array $roleData): Role
{
$role = $this->role->newInstance($roleData);
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
@ -90,6 +91,7 @@ class PermissionsRepo
$this->assignRolePermissions($role, $permissions);
$role->fill($roleData);
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$this->permissionService->buildJointPermissionForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);

View File

@ -18,6 +18,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
* @property string $description
* @property string $external_auth_id
* @property string $system_name
* @property bool $mfa_enforced
*/
class Role extends Model implements Loggable
{

View File

@ -0,0 +1,24 @@
<?php
namespace BookStack\Http\Middleware;
use Closure;
class EnforceMfaRequirements
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$mfaRequired = user()->roles()->where('mfa_enforced', '=', true)->exists();
// TODO - Run this after auth (If authenticated)
// TODO - Redirect user to setup MFA or verify via MFA.
// TODO - Store mfa_pass into session for future requests?
return $next($request);
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddMfaEnforcedToRolesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('roles', function (Blueprint $table) {
$table->boolean('mfa_enforced');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('roles', function (Blueprint $table) {
$table->dropColumn('mfa_enforced');
});
}
}

View File

@ -138,6 +138,7 @@ return [
'role_details' => 'Role Details',
'role_name' => 'Role Name',
'role_desc' => 'Short Description of Role',
'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'System Permissions',
'role_manage_users' => 'Manage users',

View File

@ -11,13 +11,16 @@
</div>
<div>
<div class="form-group">
<label for="name">{{ trans('settings.role_name') }}</label>
<label for="display_name">{{ trans('settings.role_name') }}</label>
@include('form.text', ['name' => 'display_name'])
</div>
<div class="form-group">
<label for="name">{{ trans('settings.role_desc') }}</label>
<label for="description">{{ trans('settings.role_desc') }}</label>
@include('form.text', ['name' => 'description'])
</div>
<div class="form-group">
@include('form.checkbox', ['name' => 'mfa_enforced', 'label' => trans('settings.role_mfa_enforced') ])
</div>
@if(config('auth.method') === 'ldap' || config('auth.method') === 'saml2')
<div class="form-group">

View File

@ -27,7 +27,12 @@
@foreach($roles as $role)
<tr>
<td><a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td>
<td>{{ $role->description }}</td>
<td>
@if($role->mfa_enforced)
<span title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </span>
@endif
{{ $role->description }}
</td>
<td class="text-center">{{ $role->users->count() }}</td>
</tr>
@endforeach

View File

@ -224,6 +224,7 @@ Route::group(['middleware' => 'auth'], function () {
Route::put('/roles/{id}', 'RoleController@update');
});
// MFA Setup Routes
Route::get('/mfa/setup', 'Auth\MfaController@setup');
Route::get('/mfa/totp-generate', 'Auth\MfaTotpController@generate');
Route::post('/mfa/totp-confirm', 'Auth\MfaTotpController@confirm');

View File

@ -64,15 +64,16 @@ class RolesTest extends BrowserKitTest
->type('Test Role', 'display_name')
->type('A little test description', 'description')
->press('Save Role')
->seeInDatabase('roles', ['display_name' => $testRoleName, 'description' => $testRoleDesc])
->seeInDatabase('roles', ['display_name' => $testRoleName, 'description' => $testRoleDesc, 'mfa_enforced' => false])
->seePageIs('/settings/roles');
// Updating
$this->asAdmin()->visit('/settings/roles')
->see($testRoleDesc)
->click($testRoleName)
->type($testRoleUpdateName, '#display_name')
->check('#mfa_enforced')
->press('Save Role')
->seeInDatabase('roles', ['display_name' => $testRoleUpdateName, 'description' => $testRoleDesc])
->seeInDatabase('roles', ['display_name' => $testRoleUpdateName, 'description' => $testRoleDesc, 'mfa_enforced' => true])
->seePageIs('/settings/roles');
// Deleting
$this->asAdmin()->visit('/settings/roles')