]> BookStack Code Mirror - bookstack/blob - tests/Auth/AuthTest.php
Quick test of email confirmation routes and fix of tests
[bookstack] / tests / Auth / AuthTest.php
1 <?php
2
3 namespace Tests\Auth;
4
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;
11 use DB;
12 use Hash;
13 use Illuminate\Support\Facades\Notification;
14 use Illuminate\Support\Str;
15 use Tests\BrowserKitTest;
16
17 class AuthTest extends BrowserKitTest
18 {
19     public function test_auth_working()
20     {
21         $this->visit('/')
22             ->seePageIs('/login');
23     }
24
25     public function test_login()
26     {
27         $this->login('[email protected]', 'password')
28             ->seePageIs('/');
29     }
30
31     public function test_public_viewing()
32     {
33         $settings = app(SettingService::class);
34         $settings->put('app-public', 'true');
35         $this->visit('/')
36             ->seePageIs('/')
37             ->see('Log In');
38     }
39
40     public function test_registration_showing()
41     {
42         // Ensure registration form is showing
43         $this->setSettings(['registration-enabled' => 'true']);
44         $this->visit('/login')
45             ->see('Sign up')
46             ->click('Sign up')
47             ->seePageIs('/register');
48     }
49
50     public function test_normal_registration()
51     {
52         // Set settings and get user instance
53         $this->setSettings(['registration-enabled' => 'true']);
54         $user = factory(User::class)->make();
55
56         // Test form and ensure user is created
57         $this->visit('/register')
58             ->see('Sign Up')
59             ->type($user->name, '#name')
60             ->type($user->email, '#email')
61             ->type($user->password, '#password')
62             ->press('Create Account')
63             ->seePageIs('/')
64             ->see($user->name)
65             ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email]);
66     }
67
68     public function test_empty_registration_redirects_back_with_errors()
69     {
70         // Set settings and get user instance
71         $this->setSettings(['registration-enabled' => 'true']);
72
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');
78     }
79
80     public function test_registration_validation()
81     {
82         $this->setSettings(['registration-enabled' => 'true']);
83
84         $this->visit('/register')
85             ->type('1', '#name')
86             ->type('1', '#email')
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');
93     }
94
95     public function test_sign_up_link_on_login()
96     {
97         $this->visit('/login')
98             ->dontSee('Sign up');
99
100         $this->setSettings(['registration-enabled' => 'true']);
101
102         $this->visit('/login')
103             ->see('Sign up');
104     }
105
106     public function test_confirmed_registration()
107     {
108         // Fake notifications
109         Notification::fake();
110
111         // Set settings and get user instance
112         $this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
113         $user = factory(User::class)->make();
114
115         // Go through registration process
116         $this->visit('/register')
117             ->see('Sign Up')
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]);
124
125         // Ensure notification sent
126         $dbUser = User::where('email', '=', $user->email)->first();
127         Notification::assertSentTo($dbUser, ConfirmEmail::class);
128
129         // Test access and resend confirmation email
130         $this->login($user->email, $user->password)
131             ->seePageIs('/register/confirm/awaiting')
132             ->see('Resend')
133             ->visit('/books')
134             ->seePageIs('/login')
135             ->visit('/register/confirm/awaiting')
136             ->press('Resend Confirmation Email');
137
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;
142         });
143
144         // Check confirmation email confirmation activation.
145         $this->visit('/register/confirm/' . $emailConfirmation->token)
146             ->seePageIs('/')
147             ->see($user->name)
148             ->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
149             ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
150     }
151
152     public function test_restricted_registration()
153     {
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');
165
166         $user->email = '[email protected]';
167
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]);
175
176         $this->assertNull(auth()->user());
177
178         $this->visit('/')->seePageIs('/login')
179             ->type($user->email, '#email')
180             ->type($user->password, '#password')
181             ->press('Log In')
182             ->seePageIs('/register/confirm/awaiting')
183             ->seeText('Email Address Not Confirmed');
184     }
185
186     public function test_restricted_registration_with_confirmation_disabled()
187     {
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');
199
200         $user->email = '[email protected]';
201
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]);
209
210         $this->assertNull(auth()->user());
211
212         $this->visit('/')->seePageIs('/login')
213             ->type($user->email, '#email')
214             ->type($user->password, '#password')
215             ->press('Log In')
216             ->seePageIs('/register/confirm/awaiting')
217             ->seeText('Email Address Not Confirmed');
218     }
219
220     public function test_user_creation()
221     {
222         /** @var User $user */
223         $user = factory(User::class)->make();
224         $adminRole = Role::getRole('admin');
225
226         $this->asAdmin()
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')
234             ->press('Save')
235             ->seePageIs('/settings/users')
236             ->seeInDatabase('users', $user->only(['name', 'email']))
237             ->see($user->name);
238
239         $user->refresh();
240         $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
241     }
242
243     public function test_user_updating()
244     {
245         $user = $this->getNormalUser();
246         $password = $user->password;
247         $this->asAdmin()
248             ->visit('/settings/users')
249             ->click($user->name)
250             ->seePageIs('/settings/users/' . $user->id)
251             ->see($user->email)
252             ->type('Barry Scott', '#name')
253             ->press('Save')
254             ->seePageIs('/settings/users')
255             ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
256             ->notSeeInDatabase('users', ['name' => $user->name]);
257
258         $user->refresh();
259         $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
260     }
261
262     public function test_user_password_update()
263     {
264         $user = $this->getNormalUser();
265         $userProfilePage = '/settings/users/' . $user->id;
266         $this->asAdmin()
267             ->visit($userProfilePage)
268             ->type('newpassword', '#password')
269             ->press('Save')
270             ->seePageIs($userProfilePage)
271             ->see('Password confirmation required')
272
273             ->type('newpassword', '#password')
274             ->type('newpassword', '#password-confirm')
275             ->press('Save')
276             ->seePageIs('/settings/users');
277
278         $userPassword = User::find($user->id)->password;
279         $this->assertTrue(Hash::check('newpassword', $userPassword));
280     }
281
282     public function test_user_deletion()
283     {
284         $userDetails = factory(User::class)->make();
285         $user = $this->getEditor($userDetails->toArray());
286
287         $this->asAdmin()
288             ->visit('/settings/users/' . $user->id)
289             ->click('Delete User')
290             ->see($user->name)
291             ->press('Confirm')
292             ->seePageIs('/settings/users')
293             ->notSeeInDatabase('users', ['name' => $user->name]);
294     }
295
296     public function test_user_cannot_be_deleted_if_last_admin()
297     {
298         $adminRole = Role::getRole('admin');
299
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) {
304                 $user->delete();
305             }
306         }
307
308         // Ensure we currently only have 1 admin user
309         $this->assertEquals(1, $adminRole->users()->count());
310         $user = $adminRole->users->first();
311
312         $this->asAdmin()->visit('/settings/users/' . $user->id)
313             ->click('Delete User')
314             ->press('Confirm')
315             ->seePageIs('/settings/users/' . $user->id)
316             ->see('You cannot delete the only admin');
317     }
318
319     public function test_logout()
320     {
321         $this->asAdmin()
322             ->visit('/')
323             ->seePageIs('/')
324             ->visit('/logout')
325             ->visit('/')
326             ->seePageIs('/login');
327     }
328
329     public function test_reset_password_flow()
330     {
331         Notification::fake();
332
333         $this->visit('/login')->click('Forgot Password?')
334             ->seePageIs('/password/email')
335             ->type('[email protected]', '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.');
338
339         $this->seeInDatabase('password_resets', [
340             'email' => '[email protected]',
341         ]);
342
343         $user = User::where('email', '=', '[email protected]')->first();
344
345         Notification::assertSentTo($user, ResetPassword::class);
346         $n = Notification::sent($user, ResetPassword::class);
347
348         $this->visit('/password/reset/' . $n->first()->token)
349             ->see('Reset Password')
350             ->submitForm('Reset Password', [
351                 'email'                 => '[email protected]',
352                 'password'              => 'randompass',
353                 'password_confirmation' => 'randompass',
354             ])->seePageIs('/')
355             ->see('Your password has been successfully reset');
356     }
357
358     public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery()
359     {
360         $this->visit('/login')->click('Forgot Password?')
361             ->seePageIs('/password/email')
362             ->type('[email protected]', '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');
366
367         $this->visit('/password/reset/arandometokenvalue')
368             ->see('Reset Password')
369             ->submitForm('Reset Password', [
370                 'email'                 => '[email protected]',
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.');
377     }
378
379     public function test_reset_password_page_shows_sign_links()
380     {
381         $this->setSettings(['registration-enabled' => 'true']);
382         $this->visit('/password/email')
383             ->seeLink('Log in')
384             ->seeLink('Sign up');
385     }
386
387     public function test_login_redirects_to_initially_requested_url_correctly()
388     {
389         config()->set('app.url', 'http://localhost');
390         $page = Page::query()->first();
391
392         $this->visit($page->getUrl())
393             ->seePageUrlIs(url('/login'));
394         $this->login('[email protected]', 'password')
395             ->seePageUrlIs($page->getUrl());
396     }
397
398     public function test_login_intended_redirect_does_not_redirect_to_external_pages()
399     {
400         config()->set('app.url', 'http://localhost');
401         $this->setSettings(['app-public' => true]);
402
403         $this->get('/login', ['referer' => 'https://example.com']);
404         $login = $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
405
406         $login->assertRedirectedTo('http://localhost');
407     }
408
409     public function test_login_authenticates_admins_on_all_guards()
410     {
411         $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
412         $this->assertTrue(auth()->check());
413         $this->assertTrue(auth('ldap')->check());
414         $this->assertTrue(auth('saml2')->check());
415     }
416
417     public function test_login_authenticates_nonadmins_on_default_guard_only()
418     {
419         $editor = $this->getEditor();
420         $editor->password = bcrypt('password');
421         $editor->save();
422
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());
427     }
428
429     public function test_failed_logins_are_logged_when_message_configured()
430     {
431         $log = $this->withTestLogger();
432         config()->set(['logging.failed_login.message' => 'Failed login for %u']);
433
434         $this->post('/login', ['email' => '[email protected]', 'password' => 'cattreedog']);
435         $this->assertTrue($log->hasWarningThatContains('Failed login for [email protected]'));
436
437         $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
438         $this->assertFalse($log->hasWarningThatContains('Failed login for [email protected]'));
439     }
440
441     /**
442      * Perform a login.
443      */
444     protected function login(string $email, string $password): AuthTest
445     {
446         return $this->visit('/login')
447             ->type($email, '#email')
448             ->type($password, '#password')
449             ->press('Log In');
450     }
451 }