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('/login')
135 ->visit('/register/confirm/awaiting')
136 ->press('Resend Confirmation Email');
138 // Get confirmation and confirm notification matches
139 $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
140 Notification::assertSentTo($dbUser, ConfirmEmail::class, function ($notification, $channels) use ($emailConfirmation) {
141 return $notification->token === $emailConfirmation->token;
144 // Check confirmation email confirmation activation.
145 $this->visit('/register/confirm/' . $emailConfirmation->token)
148 ->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
149 ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
152 public function test_restricted_registration()
154 $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true', 'registration-restrict' => 'example.com']);
155 $user = factory(User::class)->make();
156 // Go through registration process
157 $this->visit('/register')
158 ->type($user->name, '#name')
159 ->type($user->email, '#email')
160 ->type($user->password, '#password')
161 ->press('Create Account')
162 ->seePageIs('/register')
163 ->dontSeeInDatabase('users', ['email' => $user->email])
164 ->see('That email domain does not have access to this application');
168 $this->visit('/register')
169 ->type($user->name, '#name')
170 ->type($user->email, '#email')
171 ->type($user->password, '#password')
172 ->press('Create Account')
173 ->seePageIs('/register/confirm')
174 ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
176 $this->assertNull(auth()->user());
178 $this->visit('/')->seePageIs('/login')
179 ->type($user->email, '#email')
180 ->type($user->password, '#password')
182 ->seePageIs('/register/confirm/awaiting')
183 ->seeText('Email Address Not Confirmed');
186 public function test_restricted_registration_with_confirmation_disabled()
188 $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'false', 'registration-restrict' => 'example.com']);
189 $user = factory(User::class)->make();
190 // Go through registration process
191 $this->visit('/register')
192 ->type($user->name, '#name')
193 ->type($user->email, '#email')
194 ->type($user->password, '#password')
195 ->press('Create Account')
196 ->seePageIs('/register')
197 ->dontSeeInDatabase('users', ['email' => $user->email])
198 ->see('That email domain does not have access to this application');
202 $this->visit('/register')
203 ->type($user->name, '#name')
204 ->type($user->email, '#email')
205 ->type($user->password, '#password')
206 ->press('Create Account')
207 ->seePageIs('/register/confirm')
208 ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
210 $this->assertNull(auth()->user());
212 $this->visit('/')->seePageIs('/login')
213 ->type($user->email, '#email')
214 ->type($user->password, '#password')
216 ->seePageIs('/register/confirm/awaiting')
217 ->seeText('Email Address Not Confirmed');
220 public function test_user_creation()
222 /** @var User $user */
223 $user = factory(User::class)->make();
224 $adminRole = Role::getRole('admin');
227 ->visit('/settings/users')
228 ->click('Add New User')
229 ->type($user->name, '#name')
230 ->type($user->email, '#email')
231 ->check("roles[{$adminRole->id}]")
232 ->type($user->password, '#password')
233 ->type($user->password, '#password-confirm')
235 ->seePageIs('/settings/users')
236 ->seeInDatabase('users', $user->only(['name', 'email']))
240 $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
243 public function test_user_updating()
245 $user = $this->getNormalUser();
246 $password = $user->password;
248 ->visit('/settings/users')
250 ->seePageIs('/settings/users/' . $user->id)
252 ->type('Barry Scott', '#name')
254 ->seePageIs('/settings/users')
255 ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
256 ->notSeeInDatabase('users', ['name' => $user->name]);
259 $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
262 public function test_user_password_update()
264 $user = $this->getNormalUser();
265 $userProfilePage = '/settings/users/' . $user->id;
267 ->visit($userProfilePage)
268 ->type('newpassword', '#password')
270 ->seePageIs($userProfilePage)
271 ->see('Password confirmation required')
273 ->type('newpassword', '#password')
274 ->type('newpassword', '#password-confirm')
276 ->seePageIs('/settings/users');
278 $userPassword = User::find($user->id)->password;
279 $this->assertTrue(Hash::check('newpassword', $userPassword));
282 public function test_user_deletion()
284 $userDetails = factory(User::class)->make();
285 $user = $this->getEditor($userDetails->toArray());
288 ->visit('/settings/users/' . $user->id)
289 ->click('Delete User')
292 ->seePageIs('/settings/users')
293 ->notSeeInDatabase('users', ['name' => $user->name]);
296 public function test_user_cannot_be_deleted_if_last_admin()
298 $adminRole = Role::getRole('admin');
300 // Delete all but one admin user if there are more than one
301 $adminUsers = $adminRole->users;
302 if (count($adminUsers) > 1) {
303 foreach ($adminUsers->splice(1) as $user) {
308 // Ensure we currently only have 1 admin user
309 $this->assertEquals(1, $adminRole->users()->count());
310 $user = $adminRole->users->first();
312 $this->asAdmin()->visit('/settings/users/' . $user->id)
313 ->click('Delete User')
315 ->seePageIs('/settings/users/' . $user->id)
316 ->see('You cannot delete the only admin');
319 public function test_logout()
326 ->seePageIs('/login');
329 public function test_reset_password_flow()
331 Notification::fake();
333 $this->visit('/login')->click('Forgot Password?')
334 ->seePageIs('/password/email')
336 ->press('Send Reset Link')
337 ->see('A password reset link will be sent to
[email protected] if that email address is found in the system.');
339 $this->seeInDatabase('password_resets', [
345 Notification::assertSentTo($user, ResetPassword::class);
346 $n = Notification::sent($user, ResetPassword::class);
348 $this->visit('/password/reset/' . $n->first()->token)
349 ->see('Reset Password')
350 ->submitForm('Reset Password', [
352 'password' => 'randompass',
353 'password_confirmation' => 'randompass',
355 ->see('Your password has been successfully reset');
358 public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery()
360 $this->visit('/login')->click('Forgot Password?')
361 ->seePageIs('/password/email')
363 ->press('Send Reset Link')
364 ->see('A password reset link will be sent to
[email protected] if that email address is found in the system.')
365 ->dontSee('We can\'t find a user');
367 $this->visit('/password/reset/arandometokenvalue')
368 ->see('Reset Password')
369 ->submitForm('Reset Password', [
371 'password' => 'randompass',
372 'password_confirmation' => 'randompass',
373 ])->followRedirects()
374 ->seePageIs('/password/reset/arandometokenvalue')
375 ->dontSee('We can\'t find a user')
376 ->see('The password reset token is invalid for this email address.');
379 public function test_reset_password_page_shows_sign_links()
381 $this->setSettings(['registration-enabled' => 'true']);
382 $this->visit('/password/email')
384 ->seeLink('Sign up');
387 public function test_login_redirects_to_initially_requested_url_correctly()
389 config()->set('app.url', 'http://localhost');
390 $page = Page::query()->first();
392 $this->visit($page->getUrl())
393 ->seePageUrlIs(url('/login'));
395 ->seePageUrlIs($page->getUrl());
398 public function test_login_intended_redirect_does_not_redirect_to_external_pages()
400 config()->set('app.url', 'http://localhost');
401 $this->setSettings(['app-public' => true]);
403 $this->get('/login', ['referer' => 'https://example.com']);
406 $login->assertRedirectedTo('http://localhost');
409 public function test_login_authenticates_admins_on_all_guards()
412 $this->assertTrue(auth()->check());
413 $this->assertTrue(auth('ldap')->check());
414 $this->assertTrue(auth('saml2')->check());
417 public function test_login_authenticates_nonadmins_on_default_guard_only()
419 $editor = $this->getEditor();
420 $editor->password = bcrypt('password');
423 $this->post('/login', ['email' => $editor->email, 'password' => 'password']);
424 $this->assertTrue(auth()->check());
425 $this->assertFalse(auth('ldap')->check());
426 $this->assertFalse(auth('saml2')->check());
429 public function test_failed_logins_are_logged_when_message_configured()
431 $log = $this->withTestLogger();
432 config()->set(['logging.failed_login.message' => 'Failed login for %u']);
444 protected function login(string $email, string $password): AuthTest
446 return $this->visit('/login')
447 ->type($email, '#email')
448 ->type($password, '#password')