X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/4b0c4e621a55d5f6f59de7451c7442a4571ad72e..refs/pull/2902/head:/tests/Auth/AuthTest.php diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 3d36d85b2..657728c17 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -1,14 +1,22 @@ -visit('/') @@ -81,7 +89,7 @@ class AuthTest extends BrowserKitTest ->press('Create Account') ->see('The name must be at least 2 characters.') ->see('The email must be a valid email address.') - ->see('The password must be at least 6 characters.') + ->see('The password must be at least 8 characters.') ->seePageIs('/register'); } @@ -124,15 +132,16 @@ class AuthTest extends BrowserKitTest ->seePageIs('/register/confirm/awaiting') ->see('Resend') ->visit('/books') - ->seePageIs('/register/confirm/awaiting') + ->seePageIs('/login') + ->visit('/register/confirm/awaiting') ->press('Resend Confirmation 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) { + $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->visit('/register/confirm/' . $emailConfirmation->token) ->seePageIs('/') @@ -165,6 +174,8 @@ class AuthTest extends BrowserKitTest ->seePageIs('/register/confirm') ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + $this->assertNull(auth()->user()); + $this->visit('/')->seePageIs('/login') ->type($user->email, '#email') ->type($user->password, '#password') @@ -197,6 +208,8 @@ class AuthTest extends BrowserKitTest ->seePageIs('/register/confirm') ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]); + $this->assertNull(auth()->user()); + $this->visit('/')->seePageIs('/login') ->type($user->email, '#email') ->type($user->password, '#password') @@ -207,20 +220,25 @@ class AuthTest extends BrowserKitTest public function test_user_creation() { + /** @var User $user */ $user = factory(User::class)->make(); + $adminRole = Role::getRole('admin'); $this->asAdmin() ->visit('/settings/users') ->click('Add New User') ->type($user->name, '#name') ->type($user->email, '#email') - ->check('roles[admin]') + ->check("roles[{$adminRole->id}]") ->type($user->password, '#password') ->type($user->password, '#password-confirm') ->press('Save') ->seePageIs('/settings/users') - ->seeInDatabase('users', $user->toArray()) + ->seeInDatabase('users', $user->only(['name', 'email'])) ->see($user->name); + + $user->refresh(); + $this->assertStringStartsWith(Str::slug($user->name), $user->slug); } public function test_user_updating() @@ -237,6 +255,9 @@ class AuthTest extends BrowserKitTest ->seePageIs('/settings/users') ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password]) ->notSeeInDatabase('users', ['name' => $user->name]); + + $user->refresh(); + $this->assertStringStartsWith(Str::slug($user->name), $user->slug); } public function test_user_password_update() @@ -255,8 +276,8 @@ class AuthTest extends BrowserKitTest ->press('Save') ->seePageIs('/settings/users'); - $userPassword = User::find($user->id)->password; - $this->assertTrue(\Hash::check('newpassword', $userPassword)); + $userPassword = User::find($user->id)->password; + $this->assertTrue(Hash::check('newpassword', $userPassword)); } public function test_user_deletion() @@ -275,7 +296,16 @@ class AuthTest extends BrowserKitTest public function test_user_cannot_be_deleted_if_last_admin() { - $adminRole = \BookStack\Auth\Role::getRole('admin'); + $adminRole = Role::getRole('admin'); + + // Delete all but one admin user if there are more than one + $adminUsers = $adminRole->users; + if (count($adminUsers) > 1) { + foreach ($adminUsers->splice(1) as $user) { + $user->delete(); + } + } + // Ensure we currently only have 1 admin user $this->assertEquals(1, $adminRole->users()->count()); $user = $adminRole->users->first(); @@ -297,36 +327,68 @@ class AuthTest extends BrowserKitTest ->seePageIs('/login'); } - public function test_reset_password_flow() + public function test_mfa_session_cleared_on_logout() { + $user = $this->getEditor(); + $mfaSession = $this->app->make(MfaSession::class); + + $mfaSession->markVerifiedForUser($user); + $this->assertTrue($mfaSession->isVerifiedForUser($user)); + + $this->asAdmin()->visit('/logout'); + $this->assertFalse($mfaSession->isVerifiedForUser($user)); + } + public function test_reset_password_flow() + { Notification::fake(); $this->visit('/login')->click('Forgot Password?') ->seePageIs('/password/email') ->type('admin@admin.com', 'email') ->press('Send Reset Link') - ->see('A password reset link has been sent to admin@admin.com'); + ->see('A password reset link will be sent to admin@admin.com if that email address is found in the system.'); $this->seeInDatabase('password_resets', [ - 'email' => 'admin@admin.com' + 'email' => 'admin@admin.com', ]); $user = User::where('email', '=', 'admin@admin.com')->first(); - Notification::assertSentTo($user, \BookStack\Notifications\ResetPassword::class); - $n = Notification::sent($user, \BookStack\Notifications\ResetPassword::class); + Notification::assertSentTo($user, ResetPassword::class); + $n = Notification::sent($user, ResetPassword::class); $this->visit('/password/reset/' . $n->first()->token) ->see('Reset Password') ->submitForm('Reset Password', [ - 'email' => 'admin@admin.com', - 'password' => 'randompass', - 'password_confirmation' => 'randompass' + 'email' => 'admin@admin.com', + 'password' => 'randompass', + 'password_confirmation' => 'randompass', ])->seePageIs('/') ->see('Your password has been successfully reset'); } + public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery() + { + $this->visit('/login')->click('Forgot Password?') + ->seePageIs('/password/email') + ->type('barry@admin.com', 'email') + ->press('Send Reset Link') + ->see('A password reset link will be sent to barry@admin.com if that email address is found in the system.') + ->dontSee('We can\'t find a user'); + + $this->visit('/password/reset/arandometokenvalue') + ->see('Reset Password') + ->submitForm('Reset Password', [ + 'email' => 'barry@admin.com', + 'password' => 'randompass', + 'password_confirmation' => 'randompass', + ])->followRedirects() + ->seePageIs('/password/reset/arandometokenvalue') + ->dontSee('We can\'t find a user') + ->see('The password reset token is invalid for this email address.'); + } + public function test_reset_password_page_shows_sign_links() { $this->setSettings(['registration-enabled' => 'true']); @@ -346,13 +408,61 @@ class AuthTest extends BrowserKitTest ->seePageUrlIs($page->getUrl()); } + public function test_login_intended_redirect_does_not_redirect_to_external_pages() + { + config()->set('app.url', 'http://localhost'); + $this->setSettings(['app-public' => true]); + + $this->get('/login', ['referer' => 'https://example.com']); + $login = $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']); + + $login->assertRedirectedTo('http://localhost'); + } + + public function test_login_intended_redirect_does_not_factor_mfa_routes() + { + $this->get('/books')->assertRedirectedTo('/login'); + $this->get('/mfa/setup')->assertRedirectedTo('/login'); + $login = $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']); + $login->assertRedirectedTo('/books'); + } + + public function test_login_authenticates_admins_on_all_guards() + { + $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']); + $this->assertTrue(auth()->check()); + $this->assertTrue(auth('ldap')->check()); + $this->assertTrue(auth('saml2')->check()); + } + + public function test_login_authenticates_nonadmins_on_default_guard_only() + { + $editor = $this->getEditor(); + $editor->password = bcrypt('password'); + $editor->save(); + + $this->post('/login', ['email' => $editor->email, 'password' => 'password']); + $this->assertTrue(auth()->check()); + $this->assertFalse(auth('ldap')->check()); + $this->assertFalse(auth('saml2')->check()); + } + + public function test_failed_logins_are_logged_when_message_configured() + { + $log = $this->withTestLogger(); + config()->set(['logging.failed_login.message' => 'Failed login for %u']); + + $this->post('/login', ['email' => 'admin@example.com', 'password' => 'cattreedog']); + $this->assertTrue($log->hasWarningThatContains('Failed login for admin@example.com')); + + $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']); + $this->assertFalse($log->hasWarningThatContains('Failed login for admin@admin.com')); + } + /** - * Perform a login - * @param string $email - * @param string $password - * @return $this + * Perform a login. */ - protected function login($email, $password) + protected function login(string $email, string $password): AuthTest { return $this->visit('/login') ->type($email, '#email')