diff --git a/app/Users/Controllers/UserAccountController.php b/app/Users/Controllers/UserAccountController.php index 6bf23df47..d9cb58f8c 100644 --- a/app/Users/Controllers/UserAccountController.php +++ b/app/Users/Controllers/UserAccountController.php @@ -161,7 +161,7 @@ class UserAccountController extends Controller */ public function showAuth(SocialAuthService $socialAuthService) { - $mfaMethods = user()->mfaValues->groupBy('method'); + $mfaMethods = user()->mfaValues()->get()->groupBy('method'); $this->setPageTitle(trans('preferences.auth')); diff --git a/tests/Permissions/RolePermissionsTest.php b/tests/Permissions/RolePermissionsTest.php index d15c1617c..ccb158faf 100644 --- a/tests/Permissions/RolePermissionsTest.php +++ b/tests/Permissions/RolePermissionsTest.php @@ -49,6 +49,7 @@ class RolePermissionsTest extends TestCase $resp = $this->get('/my-account/profile')->assertOk(); $this->withHtml($resp)->assertElementExists('input[name=email][disabled]'); + $resp->assertSee('Unfortunately you don\'t have permission to change your email address.'); $this->put('/my-account/profile', [ 'name' => 'my_new_name', 'email' => 'new_email@example.com', diff --git a/tests/User/UserMyAccountTest.php b/tests/User/UserMyAccountTest.php index 63c54daad..e1b40dadd 100644 --- a/tests/User/UserMyAccountTest.php +++ b/tests/User/UserMyAccountTest.php @@ -2,8 +2,13 @@ namespace Tests\User; +use BookStack\Access\Mfa\MfaValue; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Activity\WatchLevels; +use BookStack\Api\ApiToken; +use BookStack\Uploads\Image; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; use Tests\TestCase; class UserMyAccountTest extends TestCase @@ -26,6 +31,166 @@ class UserMyAccountTest extends TestCase $resp->assertRedirect('/'); } } + + public function test_profile_updating() + { + $editor = $this->users->editor(); + + $resp = $this->actingAs($editor)->get('/my-account/profile'); + $resp->assertSee('Profile Details'); + + $html = $this->withHtml($resp); + $html->assertFieldHasValue('name', $editor->name); + $html->assertFieldHasValue('email', $editor->email); + + $resp = $this->put('/my-account/profile', [ + 'name' => 'Barryius', + 'email' => 'barryius@example.com', + 'language' => 'fr', + ]); + + $resp->assertRedirect('/my-account/profile'); + $this->assertDatabaseHas('users', [ + 'name' => 'Barryius', + 'email' => $editor->email, // No email change due to not having permissions + ]); + $this->assertEquals(setting()->getUser($editor, 'language'), 'fr'); + } + + public function test_profile_user_avatar_update_and_reset() + { + $user = $this->users->viewer(); + $avatarFile = $this->files->uploadedImage('avatar-icon.png'); + + $this->assertEquals(0, $user->image_id); + + $upload = $this->actingAs($user)->call('PUT', "/my-account/profile", [ + 'name' => 'Barry Scott', + ], [], ['profile_image' => $avatarFile], []); + $upload->assertRedirect('/my-account/profile'); + + + $user->refresh(); + $this->assertNotEquals(0, $user->image_id); + /** @var Image $image */ + $image = Image::query()->findOrFail($user->image_id); + $this->assertFileExists(public_path($image->path)); + + $reset = $this->put("/my-account/profile", [ + 'name' => 'Barry Scott', + 'profile_image_reset' => 'true', + ]); + $upload->assertRedirect('/my-account/profile'); + + $user->refresh(); + $this->assertFileDoesNotExist(public_path($image->path)); + $this->assertEquals(0, $user->image_id); + } + + public function test_profile_admin_options_link_shows_if_permissions_allow() + { + $editor = $this->users->editor(); + + $resp = $this->actingAs($editor)->get('/my-account/profile'); + $resp->assertDontSee('Administrator Options'); + $this->withHtml($resp)->assertLinkNotExists(url("/settings/users/{$editor->id}")); + + $this->permissions->grantUserRolePermissions($editor, ['users-manage']); + + $resp = $this->actingAs($editor)->get('/my-account/profile'); + $resp->assertSee('Administrator Options'); + $this->withHtml($resp)->assertLinkExists(url("/settings/users/{$editor->id}")); + } + + public function test_profile_self_delete() + { + $editor = $this->users->editor(); + + $resp = $this->actingAs($editor)->get('/my-account/profile'); + $this->withHtml($resp)->assertLinkExists(url('/my-account/delete'), 'Delete Account'); + + $resp = $this->get('/my-account/delete'); + $resp->assertSee('Delete My Account'); + $this->withHtml($resp)->assertElementContains('form[action$="/my-account"] button', 'Confirm'); + + $resp = $this->delete('/my-account'); + $resp->assertRedirect('/'); + + $this->assertDatabaseMissing('users', ['id' => $editor->id]); + } + + public function test_profile_self_delete_shows_ownership_migration_if_can_manage_users() + { + $editor = $this->users->editor(); + + $resp = $this->actingAs($editor)->get('/my-account/delete'); + $resp->assertDontSee('Migrate Ownership'); + + $this->permissions->grantUserRolePermissions($editor, ['users-manage']); + + $resp = $this->actingAs($editor)->get('/my-account/delete'); + $resp->assertSee('Migrate Ownership'); + } + + public function test_auth_password_change() + { + $editor = $this->users->editor(); + + $resp = $this->actingAs($editor)->get('/my-account/auth'); + $resp->assertSee('Change Password'); + $this->withHtml($resp)->assertElementExists('form[action$="/my-account/auth/password"]'); + + $password = Str::random(); + $resp = $this->put('/my-account/auth/password', [ + 'password' => $password, + 'password-confirm' => $password, + ]); + $resp->assertRedirect('/my-account/auth'); + + $editor->refresh(); + $this->assertTrue(Hash::check($password, $editor->password)); + } + + public function test_auth_password_change_hides_if_not_using_email_auth() + { + $editor = $this->users->editor(); + + $resp = $this->actingAs($editor)->get('/my-account/auth'); + $resp->assertSee('Change Password'); + + config()->set('auth.method', 'oidc'); + + $resp = $this->actingAs($editor)->get('/my-account/auth'); + $resp->assertDontSee('Change Password'); + } + + public function test_auth_page_has_mfa_links() + { + $editor = $this->users->editor(); + $resp = $this->actingAs($editor)->get('/my-account/auth'); + $resp->assertSee('0 methods configured'); + $this->withHtml($resp)->assertLinkExists(url('/mfa/setup')); + + MfaValue::upsertWithValue($editor, 'totp', 'testval'); + + $resp = $this->get('/my-account/auth'); + $resp->assertSee('1 method configured'); + } + + public function test_auth_page_api_tokens() + { + $editor = $this->users->editor(); + $resp = $this->actingAs($editor)->get('/my-account/auth'); + $resp->assertSee('API Tokens'); + $this->withHtml($resp)->assertLinkExists(url("/api-tokens/{$editor->id}/create?context=my-account")); + + ApiToken::factory()->create(['user_id' => $editor->id, 'name' => 'My great token']); + $editor->unsetRelations(); + + $resp = $this->get('/my-account/auth'); + $resp->assertSee('My great token'); + } + public function test_interface_shortcuts_updating() { $this->asEditor();