]> BookStack Code Mirror - bookstack/blob - tests/Auth/AuthTest.php
Merge branch 'feature/sort-shelf-books' of git://github.com/guillaumehanotel/BookStac...
[bookstack] / tests / Auth / AuthTest.php
1 <?php namespace Tests\Auth;
2
3 use BookStack\Auth\Role;
4 use BookStack\Auth\User;
5 use BookStack\Entities\Models\Page;
6 use BookStack\Notifications\ConfirmEmail;
7 use BookStack\Notifications\ResetPassword;
8 use BookStack\Settings\SettingService;
9 use DB;
10 use Hash;
11 use Illuminate\Support\Facades\Notification;
12 use Illuminate\Support\Str;
13 use Tests\BrowserKitTest;
14
15 class AuthTest extends BrowserKitTest
16 {
17
18     public function test_auth_working()
19     {
20         $this->visit('/')
21             ->seePageIs('/login');
22     }
23
24     public function test_login()
25     {
26         $this->login('[email protected]', 'password')
27             ->seePageIs('/');
28     }
29
30     public function test_public_viewing()
31     {
32         $settings = app(SettingService::class);
33         $settings->put('app-public', 'true');
34         $this->visit('/')
35             ->seePageIs('/')
36             ->see('Log In');
37     }
38
39     public function test_registration_showing()
40     {
41         // Ensure registration form is showing
42         $this->setSettings(['registration-enabled' => 'true']);
43         $this->visit('/login')
44             ->see('Sign up')
45             ->click('Sign up')
46             ->seePageIs('/register');
47     }
48
49     public function test_normal_registration()
50     {
51         // Set settings and get user instance
52         $this->setSettings(['registration-enabled' => 'true']);
53         $user = factory(User::class)->make();
54
55         // Test form and ensure user is created
56         $this->visit('/register')
57             ->see('Sign Up')
58             ->type($user->name, '#name')
59             ->type($user->email, '#email')
60             ->type($user->password, '#password')
61             ->press('Create Account')
62             ->seePageIs('/')
63             ->see($user->name)
64             ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email]);
65     }
66
67     public function test_empty_registration_redirects_back_with_errors()
68     {
69         // Set settings and get user instance
70         $this->setSettings(['registration-enabled' => 'true']);
71
72         // Test form and ensure user is created
73         $this->visit('/register')
74             ->press('Create Account')
75             ->see('The name field is required')
76             ->seePageIs('/register');
77     }
78
79     public function test_registration_validation()
80     {
81         $this->setSettings(['registration-enabled' => 'true']);
82
83         $this->visit('/register')
84             ->type('1', '#name')
85             ->type('1', '#email')
86             ->type('1', '#password')
87             ->press('Create Account')
88             ->see('The name must be at least 2 characters.')
89             ->see('The email must be a valid email address.')
90             ->see('The password must be at least 8 characters.')
91             ->seePageIs('/register');
92     }
93
94     public function test_sign_up_link_on_login()
95     {
96         $this->visit('/login')
97             ->dontSee('Sign up');
98
99         $this->setSettings(['registration-enabled' => 'true']);
100
101         $this->visit('/login')
102             ->see('Sign up');
103     }
104
105     public function test_confirmed_registration()
106     {
107         // Fake notifications
108         Notification::fake();
109
110         // Set settings and get user instance
111         $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
112         $user = factory(User::class)->make();
113
114         // Go through registration process
115         $this->visit('/register')
116             ->see('Sign Up')
117             ->type($user->name, '#name')
118             ->type($user->email, '#email')
119             ->type($user->password, '#password')
120             ->press('Create Account')
121             ->seePageIs('/register/confirm')
122             ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
123
124         // Ensure notification sent
125         $dbUser = User::where('email', '=', $user->email)->first();
126         Notification::assertSentTo($dbUser, ConfirmEmail::class);
127
128         // Test access and resend confirmation email
129         $this->login($user->email, $user->password)
130             ->seePageIs('/register/confirm/awaiting')
131             ->see('Resend')
132             ->visit('/books')
133             ->seePageIs('/register/confirm/awaiting')
134             ->press('Resend Confirmation Email');
135
136         // Get confirmation and confirm notification matches
137         $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
138         Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
139             return $notification->token === $emailConfirmation->token;
140         });
141         
142         // Check confirmation email confirmation activation.
143         $this->visit('/register/confirm/' . $emailConfirmation->token)
144             ->seePageIs('/')
145             ->see($user->name)
146             ->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
147             ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
148     }
149
150     public function test_restricted_registration()
151     {
152         $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true', 'registration-restrict' => 'example.com']);
153         $user = factory(User::class)->make();
154         // Go through registration process
155         $this->visit('/register')
156             ->type($user->name, '#name')
157             ->type($user->email, '#email')
158             ->type($user->password, '#password')
159             ->press('Create Account')
160             ->seePageIs('/register')
161             ->dontSeeInDatabase('users', ['email' => $user->email])
162             ->see('That email domain does not have access to this application');
163
164         $user->email = '[email protected]';
165
166         $this->visit('/register')
167             ->type($user->name, '#name')
168             ->type($user->email, '#email')
169             ->type($user->password, '#password')
170             ->press('Create Account')
171             ->seePageIs('/register/confirm')
172             ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
173
174         $this->visit('/')
175             ->seePageIs('/register/confirm/awaiting');
176
177         auth()->logout();
178
179         $this->visit('/')->seePageIs('/login')
180             ->type($user->email, '#email')
181             ->type($user->password, '#password')
182             ->press('Log In')
183             ->seePageIs('/register/confirm/awaiting')
184             ->seeText('Email Address Not Confirmed');
185     }
186
187     public function test_restricted_registration_with_confirmation_disabled()
188     {
189         $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'false', 'registration-restrict' => 'example.com']);
190         $user = factory(User::class)->make();
191         // Go through registration process
192         $this->visit('/register')
193             ->type($user->name, '#name')
194             ->type($user->email, '#email')
195             ->type($user->password, '#password')
196             ->press('Create Account')
197             ->seePageIs('/register')
198             ->dontSeeInDatabase('users', ['email' => $user->email])
199             ->see('That email domain does not have access to this application');
200
201         $user->email = '[email protected]';
202
203         $this->visit('/register')
204             ->type($user->name, '#name')
205             ->type($user->email, '#email')
206             ->type($user->password, '#password')
207             ->press('Create Account')
208             ->seePageIs('/register/confirm')
209             ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
210
211         $this->visit('/')
212             ->seePageIs('/register/confirm/awaiting');
213
214         auth()->logout();
215         $this->visit('/')->seePageIs('/login')
216             ->type($user->email, '#email')
217             ->type($user->password, '#password')
218             ->press('Log In')
219             ->seePageIs('/register/confirm/awaiting')
220             ->seeText('Email Address Not Confirmed');
221     }
222
223     public function test_user_creation()
224     {
225         /** @var User $user */
226         $user = factory(User::class)->make();
227         $adminRole = Role::getRole('admin');
228
229         $this->asAdmin()
230             ->visit('/settings/users')
231             ->click('Add New User')
232             ->type($user->name, '#name')
233             ->type($user->email, '#email')
234             ->check("roles[{$adminRole->id}]")
235             ->type($user->password, '#password')
236             ->type($user->password, '#password-confirm')
237             ->press('Save')
238             ->seePageIs('/settings/users')
239             ->seeInDatabase('users', $user->only(['name', 'email']))
240             ->see($user->name);
241
242         $user->refresh();
243         $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
244     }
245
246     public function test_user_updating()
247     {
248         $user = $this->getNormalUser();
249         $password = $user->password;
250         $this->asAdmin()
251             ->visit('/settings/users')
252             ->click($user->name)
253             ->seePageIs('/settings/users/' . $user->id)
254             ->see($user->email)
255             ->type('Barry Scott', '#name')
256             ->press('Save')
257             ->seePageIs('/settings/users')
258             ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
259             ->notSeeInDatabase('users', ['name' => $user->name]);
260
261         $user->refresh();
262         $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
263     }
264
265     public function test_user_password_update()
266     {
267         $user = $this->getNormalUser();
268         $userProfilePage = '/settings/users/' . $user->id;
269         $this->asAdmin()
270             ->visit($userProfilePage)
271             ->type('newpassword', '#password')
272             ->press('Save')
273             ->seePageIs($userProfilePage)
274             ->see('Password confirmation required')
275
276             ->type('newpassword', '#password')
277             ->type('newpassword', '#password-confirm')
278             ->press('Save')
279             ->seePageIs('/settings/users');
280
281             $userPassword = User::find($user->id)->password;
282             $this->assertTrue(Hash::check('newpassword', $userPassword));
283     }
284
285     public function test_user_deletion()
286     {
287         $userDetails = factory(User::class)->make();
288         $user = $this->getEditor($userDetails->toArray());
289
290         $this->asAdmin()
291             ->visit('/settings/users/' . $user->id)
292             ->click('Delete User')
293             ->see($user->name)
294             ->press('Confirm')
295             ->seePageIs('/settings/users')
296             ->notSeeInDatabase('users', ['name' => $user->name]);
297     }
298
299     public function test_user_cannot_be_deleted_if_last_admin()
300     {
301         $adminRole = Role::getRole('admin');
302
303         // Delete all but one admin user if there are more than one
304         $adminUsers = $adminRole->users;
305         if (count($adminUsers) > 1) {
306             foreach ($adminUsers->splice(1) as $user) {
307                 $user->delete();
308             }
309         }
310
311         // Ensure we currently only have 1 admin user
312         $this->assertEquals(1, $adminRole->users()->count());
313         $user = $adminRole->users->first();
314
315         $this->asAdmin()->visit('/settings/users/' . $user->id)
316             ->click('Delete User')
317             ->press('Confirm')
318             ->seePageIs('/settings/users/' . $user->id)
319             ->see('You cannot delete the only admin');
320     }
321
322     public function test_logout()
323     {
324         $this->asAdmin()
325             ->visit('/')
326             ->seePageIs('/')
327             ->visit('/logout')
328             ->visit('/')
329             ->seePageIs('/login');
330     }
331
332     public function test_reset_password_flow()
333     {
334         Notification::fake();
335
336         $this->visit('/login')->click('Forgot Password?')
337             ->seePageIs('/password/email')
338             ->type('[email protected]', 'email')
339             ->press('Send Reset Link')
340             ->see('A password reset link will be sent to [email protected] if that email address is found in the system.');
341
342         $this->seeInDatabase('password_resets', [
343             'email' => '[email protected]'
344         ]);
345
346         $user = User::where('email', '=', '[email protected]')->first();
347
348         Notification::assertSentTo($user, ResetPassword::class);
349         $n = Notification::sent($user, ResetPassword::class);
350
351         $this->visit('/password/reset/' . $n->first()->token)
352             ->see('Reset Password')
353             ->submitForm('Reset Password', [
354                 'email' => '[email protected]',
355                 'password' => 'randompass',
356                 'password_confirmation' => 'randompass'
357             ])->seePageIs('/')
358             ->see('Your password has been successfully reset');
359     }
360
361     public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery()
362     {
363         $this->visit('/login')->click('Forgot Password?')
364             ->seePageIs('/password/email')
365             ->type('[email protected]', 'email')
366             ->press('Send Reset Link')
367             ->see('A password reset link will be sent to [email protected] if that email address is found in the system.')
368             ->dontSee('We can\'t find a user');
369
370
371         $this->visit('/password/reset/arandometokenvalue')
372             ->see('Reset Password')
373             ->submitForm('Reset Password', [
374                 'email' => '[email protected]',
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.');
381     }
382
383     public function test_reset_password_page_shows_sign_links()
384     {
385         $this->setSettings(['registration-enabled' => 'true']);
386         $this->visit('/password/email')
387             ->seeLink('Log in')
388             ->seeLink('Sign up');
389     }
390
391     public function test_login_redirects_to_initially_requested_url_correctly()
392     {
393         config()->set('app.url', 'http://localhost');
394         $page = Page::query()->first();
395
396         $this->visit($page->getUrl())
397             ->seePageUrlIs(url('/login'));
398         $this->login('[email protected]', 'password')
399             ->seePageUrlIs($page->getUrl());
400     }
401
402     public function test_login_intended_redirect_does_not_redirect_to_external_pages()
403     {
404         config()->set('app.url', 'http://localhost');
405         $this->setSettings(['app-public' => true]);
406
407         $this->get('/login', ['referer' => 'https://example.com']);
408         $login = $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
409
410         $login->assertRedirectedTo('http://localhost');
411     }
412
413     public function test_login_authenticates_admins_on_all_guards()
414     {
415         $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
416         $this->assertTrue(auth()->check());
417         $this->assertTrue(auth('ldap')->check());
418         $this->assertTrue(auth('saml2')->check());
419     }
420
421     public function test_login_authenticates_nonadmins_on_default_guard_only()
422     {
423         $editor = $this->getEditor();
424         $editor->password = bcrypt('password');
425         $editor->save();
426
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());
431     }
432
433     public function test_failed_logins_are_logged_when_message_configured()
434     {
435         $log = $this->withTestLogger();
436         config()->set(['logging.failed_login.message' => 'Failed login for %u']);
437
438         $this->post('/login', ['email' => '[email protected]', 'password' => 'cattreedog']);
439         $this->assertTrue($log->hasWarningThatContains('Failed login for [email protected]'));
440
441         $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
442         $this->assertFalse($log->hasWarningThatContains('Failed login for [email protected]'));
443     }
444
445     /**
446      * Perform a login
447      */
448     protected function login(string $email, string $password): AuthTest
449     {
450         return $this->visit('/login')
451             ->type($email, '#email')
452             ->type($password, '#password')
453             ->press('Log In');
454     }
455 }