5 use BookStack\Auth\Role;
6 use BookStack\Auth\User;
7 use BookStack\Entities\Models\Page;
8 use BookStack\Notifications\ConfirmEmail;
9 use BookStack\Notifications\ResetPassword;
10 use BookStack\Settings\SettingService;
13 use Illuminate\Support\Facades\Notification;
14 use Illuminate\Support\Str;
15 use Tests\BrowserKitTest;
17 class AuthTest extends BrowserKitTest
19 public function test_auth_working()
22 ->seePageIs('/login');
25 public function test_login()
31 public function test_public_viewing()
33 $settings = app(SettingService::class);
34 $settings->put('app-public', 'true');
40 public function test_registration_showing()
42 // Ensure registration form is showing
43 $this->setSettings(['registration-enabled' => 'true']);
44 $this->visit('/login')
47 ->seePageIs('/register');
50 public function test_normal_registration()
52 // Set settings and get user instance
53 $this->setSettings(['registration-enabled' => 'true']);
54 $user = factory(User::class)->make();
56 // Test form and ensure user is created
57 $this->visit('/register')
59 ->type($user->name, '#name')
60 ->type($user->email, '#email')
61 ->type($user->password, '#password')
62 ->press('Create Account')
65 ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email]);
68 public function test_empty_registration_redirects_back_with_errors()
70 // Set settings and get user instance
71 $this->setSettings(['registration-enabled' => 'true']);
73 // Test form and ensure user is created
74 $this->visit('/register')
75 ->press('Create Account')
76 ->see('The name field is required')
77 ->seePageIs('/register');
80 public function test_registration_validation()
82 $this->setSettings(['registration-enabled' => 'true']);
84 $this->visit('/register')
87 ->type('1', '#password')
88 ->press('Create Account')
89 ->see('The name must be at least 2 characters.')
90 ->see('The email must be a valid email address.')
91 ->see('The password must be at least 8 characters.')
92 ->seePageIs('/register');
95 public function test_sign_up_link_on_login()
97 $this->visit('/login')
100 $this->setSettings(['registration-enabled' => 'true']);
102 $this->visit('/login')
106 public function test_confirmed_registration()
108 // Fake notifications
109 Notification::fake();
111 // Set settings and get user instance
112 $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
113 $user = factory(User::class)->make();
115 // Go through registration process
116 $this->visit('/register')
118 ->type($user->name, '#name')
119 ->type($user->email, '#email')
120 ->type($user->password, '#password')
121 ->press('Create Account')
122 ->seePageIs('/register/confirm')
123 ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
125 // Ensure notification sent
126 $dbUser = User::where('email', '=', $user->email)->first();
127 Notification::assertSentTo($dbUser, ConfirmEmail::class);
129 // Test access and resend confirmation email
130 $this->login($user->email, $user->password)
131 ->seePageIs('/register/confirm/awaiting')
134 ->seePageIs('/register/confirm/awaiting')
135 ->press('Resend Confirmation Email');
137 // Get confirmation and confirm notification matches
138 $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
139 Notification::assertSentTo($dbUser, ConfirmEmail::class, function ($notification, $channels) use ($emailConfirmation) {
140 return $notification->token === $emailConfirmation->token;
143 // Check confirmation email confirmation activation.
144 $this->visit('/register/confirm/' . $emailConfirmation->token)
147 ->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
148 ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
151 public function test_restricted_registration()
153 $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true', 'registration-restrict' => 'example.com']);
154 $user = factory(User::class)->make();
155 // Go through registration process
156 $this->visit('/register')
157 ->type($user->name, '#name')
158 ->type($user->email, '#email')
159 ->type($user->password, '#password')
160 ->press('Create Account')
161 ->seePageIs('/register')
162 ->dontSeeInDatabase('users', ['email' => $user->email])
163 ->see('That email domain does not have access to this application');
167 $this->visit('/register')
168 ->type($user->name, '#name')
169 ->type($user->email, '#email')
170 ->type($user->password, '#password')
171 ->press('Create Account')
172 ->seePageIs('/register/confirm')
173 ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
176 ->seePageIs('/register/confirm/awaiting');
180 $this->visit('/')->seePageIs('/login')
181 ->type($user->email, '#email')
182 ->type($user->password, '#password')
184 ->seePageIs('/register/confirm/awaiting')
185 ->seeText('Email Address Not Confirmed');
188 public function test_restricted_registration_with_confirmation_disabled()
190 $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'false', 'registration-restrict' => 'example.com']);
191 $user = factory(User::class)->make();
192 // Go through registration process
193 $this->visit('/register')
194 ->type($user->name, '#name')
195 ->type($user->email, '#email')
196 ->type($user->password, '#password')
197 ->press('Create Account')
198 ->seePageIs('/register')
199 ->dontSeeInDatabase('users', ['email' => $user->email])
200 ->see('That email domain does not have access to this application');
204 $this->visit('/register')
205 ->type($user->name, '#name')
206 ->type($user->email, '#email')
207 ->type($user->password, '#password')
208 ->press('Create Account')
209 ->seePageIs('/register/confirm')
210 ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
213 ->seePageIs('/register/confirm/awaiting');
216 $this->visit('/')->seePageIs('/login')
217 ->type($user->email, '#email')
218 ->type($user->password, '#password')
220 ->seePageIs('/register/confirm/awaiting')
221 ->seeText('Email Address Not Confirmed');
224 public function test_user_creation()
226 /** @var User $user */
227 $user = factory(User::class)->make();
228 $adminRole = Role::getRole('admin');
231 ->visit('/settings/users')
232 ->click('Add New User')
233 ->type($user->name, '#name')
234 ->type($user->email, '#email')
235 ->check("roles[{$adminRole->id}]")
236 ->type($user->password, '#password')
237 ->type($user->password, '#password-confirm')
239 ->seePageIs('/settings/users')
240 ->seeInDatabase('users', $user->only(['name', 'email']))
244 $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
247 public function test_user_updating()
249 $user = $this->getNormalUser();
250 $password = $user->password;
252 ->visit('/settings/users')
254 ->seePageIs('/settings/users/' . $user->id)
256 ->type('Barry Scott', '#name')
258 ->seePageIs('/settings/users')
259 ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
260 ->notSeeInDatabase('users', ['name' => $user->name]);
263 $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
266 public function test_user_password_update()
268 $user = $this->getNormalUser();
269 $userProfilePage = '/settings/users/' . $user->id;
271 ->visit($userProfilePage)
272 ->type('newpassword', '#password')
274 ->seePageIs($userProfilePage)
275 ->see('Password confirmation required')
277 ->type('newpassword', '#password')
278 ->type('newpassword', '#password-confirm')
280 ->seePageIs('/settings/users');
282 $userPassword = User::find($user->id)->password;
283 $this->assertTrue(Hash::check('newpassword', $userPassword));
286 public function test_user_deletion()
288 $userDetails = factory(User::class)->make();
289 $user = $this->getEditor($userDetails->toArray());
292 ->visit('/settings/users/' . $user->id)
293 ->click('Delete User')
296 ->seePageIs('/settings/users')
297 ->notSeeInDatabase('users', ['name' => $user->name]);
300 public function test_user_cannot_be_deleted_if_last_admin()
302 $adminRole = Role::getRole('admin');
304 // Delete all but one admin user if there are more than one
305 $adminUsers = $adminRole->users;
306 if (count($adminUsers) > 1) {
307 foreach ($adminUsers->splice(1) as $user) {
312 // Ensure we currently only have 1 admin user
313 $this->assertEquals(1, $adminRole->users()->count());
314 $user = $adminRole->users->first();
316 $this->asAdmin()->visit('/settings/users/' . $user->id)
317 ->click('Delete User')
319 ->seePageIs('/settings/users/' . $user->id)
320 ->see('You cannot delete the only admin');
323 public function test_logout()
330 ->seePageIs('/login');
333 public function test_reset_password_flow()
335 Notification::fake();
337 $this->visit('/login')->click('Forgot Password?')
338 ->seePageIs('/password/email')
340 ->press('Send Reset Link')
341 ->see('A password reset link will be sent to
[email protected] if that email address is found in the system.');
343 $this->seeInDatabase('password_resets', [
349 Notification::assertSentTo($user, ResetPassword::class);
350 $n = Notification::sent($user, ResetPassword::class);
352 $this->visit('/password/reset/' . $n->first()->token)
353 ->see('Reset Password')
354 ->submitForm('Reset Password', [
356 'password' => 'randompass',
357 'password_confirmation' => 'randompass',
359 ->see('Your password has been successfully reset');
362 public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery()
364 $this->visit('/login')->click('Forgot Password?')
365 ->seePageIs('/password/email')
367 ->press('Send Reset Link')
368 ->see('A password reset link will be sent to
[email protected] if that email address is found in the system.')
369 ->dontSee('We can\'t find a user');
371 $this->visit('/password/reset/arandometokenvalue')
372 ->see('Reset Password')
373 ->submitForm('Reset Password', [
375 'password' => 'randompass',
376 'password_confirmation' => 'randompass',
377 ])->followRedirects()
378 ->seePageIs('/password/reset/arandometokenvalue')
379 ->dontSee('We can\'t find a user')
380 ->see('The password reset token is invalid for this email address.');
383 public function test_reset_password_page_shows_sign_links()
385 $this->setSettings(['registration-enabled' => 'true']);
386 $this->visit('/password/email')
388 ->seeLink('Sign up');
391 public function test_login_redirects_to_initially_requested_url_correctly()
393 config()->set('app.url', 'http://localhost');
394 $page = Page::query()->first();
396 $this->visit($page->getUrl())
397 ->seePageUrlIs(url('/login'));
399 ->seePageUrlIs($page->getUrl());
402 public function test_login_intended_redirect_does_not_redirect_to_external_pages()
404 config()->set('app.url', 'http://localhost');
405 $this->setSettings(['app-public' => true]);
407 $this->get('/login', ['referer' => 'https://example.com']);
410 $login->assertRedirectedTo('http://localhost');
413 public function test_login_authenticates_admins_on_all_guards()
416 $this->assertTrue(auth()->check());
417 $this->assertTrue(auth('ldap')->check());
418 $this->assertTrue(auth('saml2')->check());
421 public function test_login_authenticates_nonadmins_on_default_guard_only()
423 $editor = $this->getEditor();
424 $editor->password = bcrypt('password');
427 $this->post('/login', ['email' => $editor->email, 'password' => 'password']);
428 $this->assertTrue(auth()->check());
429 $this->assertFalse(auth('ldap')->check());
430 $this->assertFalse(auth('saml2')->check());
433 public function test_failed_logins_are_logged_when_message_configured()
435 $log = $this->withTestLogger();
436 config()->set(['logging.failed_login.message' => 'Failed login for %u']);
448 protected function login(string $email, string $password): AuthTest
450 return $this->visit('/login')
451 ->type($email, '#email')
452 ->type($password, '#password')