From 90b4257889a5f9a63ee5d9934e90557e67ebca56 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 22 Sep 2022 17:15:15 +0100 Subject: [PATCH] Split out registration and pw-reset tests methods --- tests/Auth/AuthTest.php | 260 ------------------------------- tests/Auth/RegistrationTest.php | 177 +++++++++++++++++++++ tests/Auth/ResetPasswordTest.php | 101 ++++++++++++ 3 files changed, 278 insertions(+), 260 deletions(-) create mode 100644 tests/Auth/RegistrationTest.php create mode 100644 tests/Auth/ResetPasswordTest.php diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 106b71875..f0b473472 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -3,13 +3,7 @@ namespace Tests\Auth; use BookStack\Auth\Access\Mfa\MfaSession; -use BookStack\Auth\Role; -use BookStack\Auth\User; use BookStack\Entities\Models\Page; -use BookStack\Notifications\ConfirmEmail; -use BookStack\Notifications\ResetPassword; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Notification; use Illuminate\Testing\TestResponse; use Tests\TestCase; @@ -33,68 +27,6 @@ class AuthTest extends TestCase ->assertSee('Log in'); } - public function test_registration_showing() - { - // Ensure registration form is showing - $this->setSettings(['registration-enabled' => 'true']); - $resp = $this->get('/login'); - $this->withHtml($resp)->assertElementContains('a[href="' . url('/register') . '"]', 'Sign up'); - } - - public function test_normal_registration() - { - // Set settings and get user instance - /** @var Role $registrationRole */ - $registrationRole = Role::query()->first(); - $this->setSettings(['registration-enabled' => 'true', 'registration-role' => $registrationRole->id]); - /** @var User $user */ - $user = User::factory()->make(); - - // Test form and ensure user is created - $resp = $this->get('/register') - ->assertSee('Sign Up'); - $this->withHtml($resp)->assertElementContains('form[action="' . url('/register') . '"]', 'Create Account'); - - $resp = $this->post('/register', $user->only('password', 'name', 'email')); - $resp->assertRedirect('/'); - - $resp = $this->get('/'); - $resp->assertOk(); - $resp->assertSee($user->name); - - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email]); - - $user = User::query()->where('email', '=', $user->email)->first(); - $this->assertEquals(1, $user->roles()->count()); - $this->assertEquals($registrationRole->id, $user->roles()->first()->id); - } - - public function test_empty_registration_redirects_back_with_errors() - { - // Set settings and get user instance - $this->setSettings(['registration-enabled' => 'true']); - - // Test form and ensure user is created - $this->get('/register'); - $this->post('/register', [])->assertRedirect('/register'); - $this->get('/register')->assertSee('The name field is required'); - } - - public function test_registration_validation() - { - $this->setSettings(['registration-enabled' => 'true']); - - $this->get('/register'); - $resp = $this->followingRedirects()->post('/register', [ - 'name' => '1', - 'email' => '1', - 'password' => '1', - ]); - $resp->assertSee('The name must be at least 2 characters.'); - $resp->assertSee('The email must be a valid email address.'); - $resp->assertSee('The password must be at least 8 characters.'); - } - public function test_sign_up_link_on_login() { $this->get('/login')->assertDontSee('Sign up'); @@ -104,108 +36,6 @@ class AuthTest extends TestCase $this->get('/login')->assertSee('Sign up'); } - public function test_confirmed_registration() - { - // Fake notifications - Notification::fake(); - - // Set settings and get user instance - $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']); - $user = User::factory()->make(); - - // Go through registration process - $resp = $this->post('/register', $user->only('name', 'email', 'password')); - $resp->assertRedirect('/register/confirm'); - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); - - // Ensure notification sent - /** @var User $dbUser */ - $dbUser = User::query()->where('email', '=', $user->email)->first(); - Notification::assertSentTo($dbUser, ConfirmEmail::class); - - // Test access and resend confirmation email - $resp = $this->login($user->email, $user->password); - $resp->assertRedirect('/register/confirm/awaiting'); - - $resp = $this->get('/register/confirm/awaiting'); - $this->withHtml($resp)->assertElementContains('form[action="' . url('/register/confirm/resend') . '"]', 'Resend'); - - $this->get('/books')->assertRedirect('/login'); - $this->post('/register/confirm/resend', $user->only('email')); - - // Get confirmation and confirm notification matches - $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first(); - Notification::assertSentTo($dbUser, ConfirmEmail::class, function ($notification, $channels) use ($emailConfirmation) { - return $notification->token === $emailConfirmation->token; - }); - - // Check confirmation email confirmation activation. - $this->get('/register/confirm/' . $emailConfirmation->token)->assertRedirect('/login'); - $this->get('/login')->assertSee('Your email has been confirmed! You should now be able to login using this email address.'); - $this->assertDatabaseMissing('email_confirmations', ['token' => $emailConfirmation->token]); - $this->assertDatabaseHas('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]); - } - - public function test_restricted_registration() - { - $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true', 'registration-restrict' => 'example.com']); - $user = User::factory()->make(); - - // Go through registration process - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register'); - $resp = $this->get('/register'); - $resp->assertSee('That email domain does not have access to this application'); - $this->assertDatabaseMissing('users', $user->only('email')); - - $user->email = 'barry@example.com'; - - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register/confirm'); - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); - - $this->assertNull(auth()->user()); - - $this->get('/')->assertRedirect('/login'); - $resp = $this->followingRedirects()->post('/login', $user->only('email', 'password')); - $resp->assertSee('Email Address Not Confirmed'); - $this->assertNull(auth()->user()); - } - - public function test_restricted_registration_with_confirmation_disabled() - { - $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'false', 'registration-restrict' => 'example.com']); - $user = User::factory()->make(); - - // Go through registration process - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register'); - $this->assertDatabaseMissing('users', $user->only('email')); - $this->get('/register')->assertSee('That email domain does not have access to this application'); - - $user->email = 'barry@example.com'; - - $this->post('/register', $user->only('name', 'email', 'password')) - ->assertRedirect('/register/confirm'); - $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); - - $this->assertNull(auth()->user()); - - $this->get('/')->assertRedirect('/login'); - $resp = $this->post('/login', $user->only('email', 'password')); - $resp->assertRedirect('/register/confirm/awaiting'); - $this->get('/register/confirm/awaiting')->assertSee('Email Address Not Confirmed'); - $this->assertNull(auth()->user()); - } - - public function test_registration_role_unset_by_default() - { - $this->assertFalse(setting('registration-role')); - - $resp = $this->asAdmin()->get('/settings/registration'); - $this->withHtml($resp)->assertElementContains('select[name="setting-registration-role"] option[value="0"][selected]', '-- None --'); - } - public function test_logout() { $this->asAdmin()->get('/')->assertOk(); @@ -225,96 +55,6 @@ class AuthTest extends TestCase $this->assertFalse($mfaSession->isVerifiedForUser($user)); } - public function test_reset_password_flow() - { - Notification::fake(); - - $resp = $this->get('/login'); - $this->withHtml($resp)->assertElementContains('a[href="' . url('/password/email') . '"]', 'Forgot Password?'); - - $resp = $this->get('/password/email'); - $this->withHtml($resp)->assertElementContains('form[action="' . url('/password/email') . '"]', 'Send Reset Link'); - - $resp = $this->post('/password/email', [ - 'email' => 'admin@admin.com', - ]); - $resp->assertRedirect('/password/email'); - - $resp = $this->get('/password/email'); - $resp->assertSee('A password reset link will be sent to admin@admin.com if that email address is found in the system.'); - - $this->assertDatabaseHas('password_resets', [ - 'email' => 'admin@admin.com', - ]); - - /** @var User $user */ - $user = User::query()->where('email', '=', 'admin@admin.com')->first(); - - Notification::assertSentTo($user, ResetPassword::class); - $n = Notification::sent($user, ResetPassword::class); - - $this->get('/password/reset/' . $n->first()->token) - ->assertOk() - ->assertSee('Reset Password'); - - $resp = $this->post('/password/reset', [ - 'email' => 'admin@admin.com', - 'password' => 'randompass', - 'password_confirmation' => 'randompass', - 'token' => $n->first()->token, - ]); - $resp->assertRedirect('/'); - - $this->get('/')->assertSee('Your password has been successfully reset'); - } - - public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery() - { - $this->get('/password/email'); - $resp = $this->followingRedirects()->post('/password/email', [ - 'email' => 'barry@admin.com', - ]); - $resp->assertSee('A password reset link will be sent to barry@admin.com if that email address is found in the system.'); - $resp->assertDontSee('We can\'t find a user'); - - $this->get('/password/reset/arandometokenvalue')->assertSee('Reset Password'); - $resp = $this->post('/password/reset', [ - 'email' => 'barry@admin.com', - 'password' => 'randompass', - 'password_confirmation' => 'randompass', - 'token' => 'arandometokenvalue', - ]); - $resp->assertRedirect('/password/reset/arandometokenvalue'); - - $this->get('/password/reset/arandometokenvalue') - ->assertDontSee('We can\'t find a user') - ->assertSee('The password reset token is invalid for this email address.'); - } - - public function test_reset_password_page_shows_sign_links() - { - $this->setSettings(['registration-enabled' => 'true']); - $resp = $this->get('/password/email'); - $this->withHtml($resp)->assertElementContains('a', 'Log in') - ->assertElementContains('a', 'Sign up'); - } - - public function test_reset_password_request_is_throttled() - { - $editor = $this->getEditor(); - Notification::fake(); - $this->get('/password/email'); - $this->followingRedirects()->post('/password/email', [ - 'email' => $editor->email, - ]); - - $resp = $this->followingRedirects()->post('/password/email', [ - 'email' => $editor->email, - ]); - Notification::assertTimesSent(1, ResetPassword::class); - $resp->assertSee('A password reset link will be sent to ' . $editor->email . ' if that email address is found in the system.'); - } - public function test_login_redirects_to_initially_requested_url_correctly() { config()->set('app.url', 'http://localhost'); diff --git a/tests/Auth/RegistrationTest.php b/tests/Auth/RegistrationTest.php new file mode 100644 index 000000000..45d265b72 --- /dev/null +++ b/tests/Auth/RegistrationTest.php @@ -0,0 +1,177 @@ +<?php + +namespace Tests\Auth; + +use BookStack\Auth\Role; +use BookStack\Auth\User; +use BookStack\Notifications\ConfirmEmail; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Notification; +use Tests\TestCase; + +class RegistrationTest extends TestCase +{ + public function test_confirmed_registration() + { + // Fake notifications + Notification::fake(); + + // Set settings and get user instance + $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']); + $user = User::factory()->make(); + + // Go through registration process + $resp = $this->post('/register', $user->only('name', 'email', 'password')); + $resp->assertRedirect('/register/confirm'); + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + + // Ensure notification sent + /** @var User $dbUser */ + $dbUser = User::query()->where('email', '=', $user->email)->first(); + Notification::assertSentTo($dbUser, ConfirmEmail::class); + + // Test access and resend confirmation email + $resp = $this->post('/login', ['email' => $user->email, 'password' => $user->password]); + $resp->assertRedirect('/register/confirm/awaiting'); + + $resp = $this->get('/register/confirm/awaiting'); + $this->withHtml($resp)->assertElementContains('form[action="' . url('/register/confirm/resend') . '"]', 'Resend'); + + $this->get('/books')->assertRedirect('/login'); + $this->post('/register/confirm/resend', $user->only('email')); + + // Get confirmation and confirm notification matches + $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first(); + Notification::assertSentTo($dbUser, ConfirmEmail::class, function ($notification, $channels) use ($emailConfirmation) { + return $notification->token === $emailConfirmation->token; + }); + + // Check confirmation email confirmation activation. + $this->get('/register/confirm/' . $emailConfirmation->token)->assertRedirect('/login'); + $this->get('/login')->assertSee('Your email has been confirmed! You should now be able to login using this email address.'); + $this->assertDatabaseMissing('email_confirmations', ['token' => $emailConfirmation->token]); + $this->assertDatabaseHas('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]); + } + + public function test_restricted_registration() + { + $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true', 'registration-restrict' => 'example.com']); + $user = User::factory()->make(); + + // Go through registration process + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register'); + $resp = $this->get('/register'); + $resp->assertSee('That email domain does not have access to this application'); + $this->assertDatabaseMissing('users', $user->only('email')); + + $user->email = 'barry@example.com'; + + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register/confirm'); + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + + $this->assertNull(auth()->user()); + + $this->get('/')->assertRedirect('/login'); + $resp = $this->followingRedirects()->post('/login', $user->only('email', 'password')); + $resp->assertSee('Email Address Not Confirmed'); + $this->assertNull(auth()->user()); + } + + public function test_restricted_registration_with_confirmation_disabled() + { + $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'false', 'registration-restrict' => 'example.com']); + $user = User::factory()->make(); + + // Go through registration process + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register'); + $this->assertDatabaseMissing('users', $user->only('email')); + $this->get('/register')->assertSee('That email domain does not have access to this application'); + + $user->email = 'barry@example.com'; + + $this->post('/register', $user->only('name', 'email', 'password')) + ->assertRedirect('/register/confirm'); + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + + $this->assertNull(auth()->user()); + + $this->get('/')->assertRedirect('/login'); + $resp = $this->post('/login', $user->only('email', 'password')); + $resp->assertRedirect('/register/confirm/awaiting'); + $this->get('/register/confirm/awaiting')->assertSee('Email Address Not Confirmed'); + $this->assertNull(auth()->user()); + } + + public function test_registration_role_unset_by_default() + { + $this->assertFalse(setting('registration-role')); + + $resp = $this->asAdmin()->get('/settings/registration'); + $this->withHtml($resp)->assertElementContains('select[name="setting-registration-role"] option[value="0"][selected]', '-- None --'); + } + + public function test_registration_showing() + { + // Ensure registration form is showing + $this->setSettings(['registration-enabled' => 'true']); + $resp = $this->get('/login'); + $this->withHtml($resp)->assertElementContains('a[href="' . url('/register') . '"]', 'Sign up'); + } + + public function test_normal_registration() + { + // Set settings and get user instance + /** @var Role $registrationRole */ + $registrationRole = Role::query()->first(); + $this->setSettings(['registration-enabled' => 'true', 'registration-role' => $registrationRole->id]); + /** @var User $user */ + $user = User::factory()->make(); + + // Test form and ensure user is created + $resp = $this->get('/register') + ->assertSee('Sign Up'); + $this->withHtml($resp)->assertElementContains('form[action="' . url('/register') . '"]', 'Create Account'); + + $resp = $this->post('/register', $user->only('password', 'name', 'email')); + $resp->assertRedirect('/'); + + $resp = $this->get('/'); + $resp->assertOk(); + $resp->assertSee($user->name); + + $this->assertDatabaseHas('users', ['name' => $user->name, 'email' => $user->email]); + + $user = User::query()->where('email', '=', $user->email)->first(); + $this->assertEquals(1, $user->roles()->count()); + $this->assertEquals($registrationRole->id, $user->roles()->first()->id); + } + + public function test_empty_registration_redirects_back_with_errors() + { + // Set settings and get user instance + $this->setSettings(['registration-enabled' => 'true']); + + // Test form and ensure user is created + $this->get('/register'); + $this->post('/register', [])->assertRedirect('/register'); + $this->get('/register')->assertSee('The name field is required'); + } + + public function test_registration_validation() + { + $this->setSettings(['registration-enabled' => 'true']); + + $this->get('/register'); + $resp = $this->followingRedirects()->post('/register', [ + 'name' => '1', + 'email' => '1', + 'password' => '1', + ]); + $resp->assertSee('The name must be at least 2 characters.'); + $resp->assertSee('The email must be a valid email address.'); + $resp->assertSee('The password must be at least 8 characters.'); + } +} diff --git a/tests/Auth/ResetPasswordTest.php b/tests/Auth/ResetPasswordTest.php new file mode 100644 index 000000000..1ab579b26 --- /dev/null +++ b/tests/Auth/ResetPasswordTest.php @@ -0,0 +1,101 @@ +<?php + +namespace Tests\Auth; + +use BookStack\Auth\User; +use BookStack\Notifications\ResetPassword; +use Illuminate\Support\Facades\Notification; +use Tests\TestCase; + +class ResetPasswordTest extends TestCase +{ + public function test_reset_password_flow() + { + Notification::fake(); + + $resp = $this->get('/login'); + $this->withHtml($resp)->assertElementContains('a[href="' . url('/password/email') . '"]', 'Forgot Password?'); + + $resp = $this->get('/password/email'); + $this->withHtml($resp)->assertElementContains('form[action="' . url('/password/email') . '"]', 'Send Reset Link'); + + $resp = $this->post('/password/email', [ + 'email' => 'admin@admin.com', + ]); + $resp->assertRedirect('/password/email'); + + $resp = $this->get('/password/email'); + $resp->assertSee('A password reset link will be sent to admin@admin.com if that email address is found in the system.'); + + $this->assertDatabaseHas('password_resets', [ + 'email' => 'admin@admin.com', + ]); + + /** @var User $user */ + $user = User::query()->where('email', '=', 'admin@admin.com')->first(); + + Notification::assertSentTo($user, ResetPassword::class); + $n = Notification::sent($user, ResetPassword::class); + + $this->get('/password/reset/' . $n->first()->token) + ->assertOk() + ->assertSee('Reset Password'); + + $resp = $this->post('/password/reset', [ + 'email' => 'admin@admin.com', + 'password' => 'randompass', + 'password_confirmation' => 'randompass', + 'token' => $n->first()->token, + ]); + $resp->assertRedirect('/'); + + $this->get('/')->assertSee('Your password has been successfully reset'); + } + + public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery() + { + $this->get('/password/email'); + $resp = $this->followingRedirects()->post('/password/email', [ + 'email' => 'barry@admin.com', + ]); + $resp->assertSee('A password reset link will be sent to barry@admin.com if that email address is found in the system.'); + $resp->assertDontSee('We can\'t find a user'); + + $this->get('/password/reset/arandometokenvalue')->assertSee('Reset Password'); + $resp = $this->post('/password/reset', [ + 'email' => 'barry@admin.com', + 'password' => 'randompass', + 'password_confirmation' => 'randompass', + 'token' => 'arandometokenvalue', + ]); + $resp->assertRedirect('/password/reset/arandometokenvalue'); + + $this->get('/password/reset/arandometokenvalue') + ->assertDontSee('We can\'t find a user') + ->assertSee('The password reset token is invalid for this email address.'); + } + + public function test_reset_password_page_shows_sign_links() + { + $this->setSettings(['registration-enabled' => 'true']); + $resp = $this->get('/password/email'); + $this->withHtml($resp)->assertElementContains('a', 'Log in') + ->assertElementContains('a', 'Sign up'); + } + + public function test_reset_password_request_is_throttled() + { + $editor = $this->getEditor(); + Notification::fake(); + $this->get('/password/email'); + $this->followingRedirects()->post('/password/email', [ + 'email' => $editor->email, + ]); + + $resp = $this->followingRedirects()->post('/password/email', [ + 'email' => $editor->email, + ]); + Notification::assertTimesSent(1, ResetPassword::class); + $resp->assertSee('A password reset link will be sent to ' . $editor->email . ' if that email address is found in the system.'); + } +}