X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/ee24635e06a8c01d751f80caba47c57f76e8989d..HEAD:/tests/Auth/AuthTest.php diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 106b71875..bffd8bbdb 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -2,14 +2,8 @@ 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 BookStack\Access\Mfa\MfaSession; +use Illuminate\Support\Facades\Hash; 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('/https/source.bookstackapp.com/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('/https/source.bookstackapp.com/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('/https/source.bookstackapp.com/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(); @@ -215,7 +45,7 @@ class AuthTest extends TestCase public function test_mfa_session_cleared_on_logout() { - $user = $this->getEditor(); + $user = $this->users->editor(); $mfaSession = $this->app->make(MfaSession::class); $mfaSession->markVerifiedForUser($user); @@ -225,101 +55,10 @@ 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('/https/source.bookstackapp.com/password/email') . '"]', 'Forgot Password?'); - - $resp = $this->get('/password/email'); - $this->withHtml($resp)->assertElementContains('form[action="' . url('/https/source.bookstackapp.com/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'); - /** @var Page $page */ - $page = Page::query()->first(); + $page = $this->entities->page(); $this->get($page->getUrl())->assertRedirect(url('/login')); $this->login('admin@admin.com', 'password') @@ -356,7 +95,7 @@ class AuthTest extends TestCase public function test_login_authenticates_nonadmins_on_default_guard_only() { - $editor = $this->getEditor(); + $editor = $this->users->editor(); $editor->password = bcrypt('password'); $editor->save(); @@ -382,7 +121,7 @@ class AuthTest extends TestCase public function test_logged_in_user_with_unconfirmed_email_is_logged_out() { $this->setSettings(['registration-confirmation' => 'true']); - $user = $this->getEditor(); + $user = $this->users->editor(); $user->email_confirmed = false; $user->save(); @@ -393,6 +132,38 @@ class AuthTest extends TestCase $this->assertFalse(auth()->check()); } + public function test_login_attempts_are_rate_limited() + { + for ($i = 0; $i < 5; $i++) { + $resp = $this->login('bennynotexisting@example.com', 'pw123'); + } + $resp = $this->followRedirects($resp); + $resp->assertSee('These credentials do not match our records.'); + + // Check the fifth attempt provides a lockout response + $resp = $this->followRedirects($this->login('bennynotexisting@example.com', 'pw123')); + $resp->assertSee('Too many login attempts. Please try again in'); + } + + public function test_login_specifically_disabled_for_guest_account() + { + $guest = $this->users->guest(); + + $resp = $this->post('/login', ['email' => $guest->email, 'password' => 'password']); + $resp->assertRedirect('/login'); + $resp = $this->followRedirects($resp); + $resp->assertSee('These credentials do not match our records.'); + + // Test login even with password somehow set + $guest->password = Hash::make('password'); + $guest->save(); + + $resp = $this->post('/login', ['email' => $guest->email, 'password' => 'password']); + $resp->assertRedirect('/login'); + $resp = $this->followRedirects($resp); + $resp->assertSee('These credentials do not match our records.'); + } + /** * Perform a login. */