]> BookStack Code Mirror - bookstack/commitdiff
Added role based MFA control
authorDan Brown <redacted>
Sat, 3 Jul 2021 12:34:48 +0000 (13:34 +0100)
committerDan Brown <redacted>
Sat, 3 Jul 2021 12:34:48 +0000 (13:34 +0100)
- 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.

app/Auth/Permissions/PermissionsRepo.php
app/Auth/Role.php
app/Http/Middleware/EnforceMfaRequirements.php [new file with mode: 0644]
database/migrations/2021_07_03_085038_add_mfa_enforced_to_roles_table.php [new file with mode: 0644]
resources/lang/en/settings.php
resources/views/settings/roles/form.blade.php
resources/views/settings/roles/index.blade.php
routes/web.php
tests/Permissions/RolesTest.php

index 4d191679da64b7125ed9c2a895275aef53144e40..988146700f80e1760c6d667ac0fe29dc0de22542 100644 (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);
index 94ba39d1d1f15fb7952f51fa18664a6440e3ae95..dcd960948039d85279768fe9ee247416e851a6e9 100644 (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
 {
diff --git a/app/Http/Middleware/EnforceMfaRequirements.php b/app/Http/Middleware/EnforceMfaRequirements.php
new file mode 100644 (file)
index 0000000..957b42a
--- /dev/null
@@ -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);
+    }
+}
diff --git a/database/migrations/2021_07_03_085038_add_mfa_enforced_to_roles_table.php b/database/migrations/2021_07_03_085038_add_mfa_enforced_to_roles_table.php
new file mode 100644 (file)
index 0000000..c14d47e
--- /dev/null
@@ -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');
+        });
+    }
+}
index 789ef9d1b29e72d661aaba9663b9972ab909d90b..d9b4854fe5a35ed1b9da794b909a9e8fb549ee10 100755 (executable)
@@ -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',
index 604acbb165021a5f8bc50814106f5447d77dcda0..d1a61f0cd3896d391465d4aca6c5d9f5be63301c 100644 (file)
             </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">
index 47cd8c920fffa07909215e12e8f8d3d5d8dedee9..898a96eeffa93838480beb66d31ebe990179675b 100644 (file)
                 @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
index 7ab5890e0f0032c40a14a4c947bd1488d2455604..3be6218b0518b1ff1d5b896fb71821b7851a0c22 100644 (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');
index 09c3233e38c3dd77a6d758be55d068b637e15b2c..b9b1805b6df213deac96139e63f7e6fdb58d3d08 100644 (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')
Morty Proxy This is a proxified and sanitized view of the page, visit original site.