]> BookStack Code Mirror - bookstack/blob - tests/Auth/AuthTest.php
Apply fixes from StyleCI
[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('/register/confirm/awaiting')
135             ->press('Resend Confirmation Email');
136
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;
141         });
142
143         // Check confirmation email confirmation activation.
144         $this->visit('/register/confirm/' . $emailConfirmation->token)
145             ->seePageIs('/')
146             ->see($user->name)
147             ->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
148             ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
149     }
150
151     public function test_restricted_registration()
152     {
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');
164
165         $user->email = '[email protected]';
166
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]);
174
175         $this->visit('/')
176             ->seePageIs('/register/confirm/awaiting');
177
178         auth()->logout();
179
180         $this->visit('/')->seePageIs('/login')
181             ->type($user->email, '#email')
182             ->type($user->password, '#password')
183             ->press('Log In')
184             ->seePageIs('/register/confirm/awaiting')
185             ->seeText('Email Address Not Confirmed');
186     }
187
188     public function test_restricted_registration_with_confirmation_disabled()
189     {
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');
201
202         $user->email = '[email protected]';
203
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]);
211
212         $this->visit('/')
213             ->seePageIs('/register/confirm/awaiting');
214
215         auth()->logout();
216         $this->visit('/')->seePageIs('/login')
217             ->type($user->email, '#email')
218             ->type($user->password, '#password')
219             ->press('Log In')
220             ->seePageIs('/register/confirm/awaiting')
221             ->seeText('Email Address Not Confirmed');
222     }
223
224     public function test_user_creation()
225     {
226         /** @var User $user */
227         $user = factory(User::class)->make();
228         $adminRole = Role::getRole('admin');
229
230         $this->asAdmin()
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')
238             ->press('Save')
239             ->seePageIs('/settings/users')
240             ->seeInDatabase('users', $user->only(['name', 'email']))
241             ->see($user->name);
242
243         $user->refresh();
244         $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
245     }
246
247     public function test_user_updating()
248     {
249         $user = $this->getNormalUser();
250         $password = $user->password;
251         $this->asAdmin()
252             ->visit('/settings/users')
253             ->click($user->name)
254             ->seePageIs('/settings/users/' . $user->id)
255             ->see($user->email)
256             ->type('Barry Scott', '#name')
257             ->press('Save')
258             ->seePageIs('/settings/users')
259             ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
260             ->notSeeInDatabase('users', ['name' => $user->name]);
261
262         $user->refresh();
263         $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
264     }
265
266     public function test_user_password_update()
267     {
268         $user = $this->getNormalUser();
269         $userProfilePage = '/settings/users/' . $user->id;
270         $this->asAdmin()
271             ->visit($userProfilePage)
272             ->type('newpassword', '#password')
273             ->press('Save')
274             ->seePageIs($userProfilePage)
275             ->see('Password confirmation required')
276
277             ->type('newpassword', '#password')
278             ->type('newpassword', '#password-confirm')
279             ->press('Save')
280             ->seePageIs('/settings/users');
281
282         $userPassword = User::find($user->id)->password;
283         $this->assertTrue(Hash::check('newpassword', $userPassword));
284     }
285
286     public function test_user_deletion()
287     {
288         $userDetails = factory(User::class)->make();
289         $user = $this->getEditor($userDetails->toArray());
290
291         $this->asAdmin()
292             ->visit('/settings/users/' . $user->id)
293             ->click('Delete User')
294             ->see($user->name)
295             ->press('Confirm')
296             ->seePageIs('/settings/users')
297             ->notSeeInDatabase('users', ['name' => $user->name]);
298     }
299
300     public function test_user_cannot_be_deleted_if_last_admin()
301     {
302         $adminRole = Role::getRole('admin');
303
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) {
308                 $user->delete();
309             }
310         }
311
312         // Ensure we currently only have 1 admin user
313         $this->assertEquals(1, $adminRole->users()->count());
314         $user = $adminRole->users->first();
315
316         $this->asAdmin()->visit('/settings/users/' . $user->id)
317             ->click('Delete User')
318             ->press('Confirm')
319             ->seePageIs('/settings/users/' . $user->id)
320             ->see('You cannot delete the only admin');
321     }
322
323     public function test_logout()
324     {
325         $this->asAdmin()
326             ->visit('/')
327             ->seePageIs('/')
328             ->visit('/logout')
329             ->visit('/')
330             ->seePageIs('/login');
331     }
332
333     public function test_reset_password_flow()
334     {
335         Notification::fake();
336
337         $this->visit('/login')->click('Forgot Password?')
338             ->seePageIs('/password/email')
339             ->type('[email protected]', '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.');
342
343         $this->seeInDatabase('password_resets', [
344             'email' => '[email protected]',
345         ]);
346
347         $user = User::where('email', '=', '[email protected]')->first();
348
349         Notification::assertSentTo($user, ResetPassword::class);
350         $n = Notification::sent($user, ResetPassword::class);
351
352         $this->visit('/password/reset/' . $n->first()->token)
353             ->see('Reset Password')
354             ->submitForm('Reset Password', [
355                 'email'                 => '[email protected]',
356                 'password'              => 'randompass',
357                 'password_confirmation' => 'randompass',
358             ])->seePageIs('/')
359             ->see('Your password has been successfully reset');
360     }
361
362     public function test_reset_password_flow_shows_success_message_even_if_wrong_password_to_prevent_user_discovery()
363     {
364         $this->visit('/login')->click('Forgot Password?')
365             ->seePageIs('/password/email')
366             ->type('[email protected]', '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');
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 }