]> BookStack Code Mirror - bookstack/blob - tests/Permissions/RolesTest.php
Laravel 8 shift squash & merge (#3029)
[bookstack] / tests / Permissions / RolesTest.php
1 <?php
2
3 namespace Tests\Permissions;
4
5 use BookStack\Actions\ActivityType;
6 use BookStack\Actions\Comment;
7 use BookStack\Auth\Role;
8 use BookStack\Auth\User;
9 use BookStack\Entities\Models\Book;
10 use BookStack\Entities\Models\Bookshelf;
11 use BookStack\Entities\Models\Chapter;
12 use BookStack\Entities\Models\Entity;
13 use BookStack\Entities\Models\Page;
14 use BookStack\Uploads\Image;
15 use Tests\TestCase;
16 use Tests\TestResponse;
17
18 class RolesTest extends TestCase
19 {
20     protected $user;
21
22     protected function setUp(): void
23     {
24         parent::setUp();
25         $this->user = $this->getViewer();
26     }
27
28     public function test_admin_can_see_settings()
29     {
30         $this->asAdmin()->get('/settings')->assertSee('Settings');
31     }
32
33     public function test_cannot_delete_admin_role()
34     {
35         $adminRole = Role::getRole('admin');
36         $deletePageUrl = '/settings/roles/delete/' . $adminRole->id;
37
38         $this->asAdmin()->get($deletePageUrl);
39         $this->delete($deletePageUrl)->assertRedirect($deletePageUrl);
40         $this->get($deletePageUrl)->assertSee('cannot be deleted');
41     }
42
43     public function test_role_cannot_be_deleted_if_default()
44     {
45         $newRole = $this->createNewRole();
46         $this->setSettings(['registration-role' => $newRole->id]);
47
48         $deletePageUrl = '/settings/roles/delete/' . $newRole->id;
49         $this->asAdmin()->get($deletePageUrl);
50         $this->delete($deletePageUrl)->assertRedirect($deletePageUrl);
51         $this->get($deletePageUrl)->assertSee('cannot be deleted');
52     }
53
54     public function test_role_create_update_delete_flow()
55     {
56         $testRoleName = 'Test Role';
57         $testRoleDesc = 'a little test description';
58         $testRoleUpdateName = 'An Super Updated role';
59
60         // Creation
61         $resp = $this->asAdmin()->get('/settings');
62         $resp->assertElementContains('a[href="' . url('/settings/roles') . '"]', 'Roles');
63
64         $resp = $this->get('/settings/roles');
65         $resp->assertElementContains('a[href="' . url('/settings/roles/new') . '"]', 'Create New Role');
66
67         $resp = $this->get('/settings/roles/new');
68         $resp->assertElementContains('form[action="' . url('/settings/roles/new') . '"]', 'Save Role');
69
70         $resp = $this->post('/settings/roles/new', [
71             'display_name' => $testRoleName,
72             'description'  => $testRoleDesc,
73         ]);
74         $resp->assertRedirect('/settings/roles');
75
76         $resp = $this->get('/settings/roles');
77         $resp->assertSee($testRoleName);
78         $resp->assertSee($testRoleDesc);
79         $this->assertDatabaseHas('roles', [
80             'display_name' => $testRoleName,
81             'description'  => $testRoleDesc,
82             'mfa_enforced' => false,
83         ]);
84
85         /** @var Role $role */
86         $role = Role::query()->where('display_name', '=', $testRoleName)->first();
87
88         // Updating
89         $resp = $this->get('/settings/roles/' . $role->id);
90         $resp->assertSee($testRoleName);
91         $resp->assertSee($testRoleDesc);
92         $resp->assertElementContains('form[action="' . url('/settings/roles/' . $role->id) . '"]', 'Save Role');
93
94         $resp = $this->put('/settings/roles/' . $role->id, [
95             'display_name' => $testRoleUpdateName,
96             'description'  => $testRoleDesc,
97             'mfa_enforced' => 'true',
98         ]);
99         $resp->assertRedirect('/settings/roles');
100         $this->assertDatabaseHas('roles', [
101             'display_name' => $testRoleUpdateName,
102             'description'  => $testRoleDesc,
103             'mfa_enforced' => true,
104         ]);
105
106         // Deleting
107         $resp = $this->get('/settings/roles/' . $role->id);
108         $resp->assertElementContains('a[href="' . url("/settings/roles/delete/$role->id") . '"]', 'Delete Role');
109
110         $resp = $this->get("/settings/roles/delete/$role->id");
111         $resp->assertSee($testRoleUpdateName);
112         $resp->assertElementContains('form[action="' . url("/settings/roles/delete/$role->id") . '"]', 'Confirm');
113
114         $resp = $this->delete("/settings/roles/delete/$role->id");
115         $resp->assertRedirect('/settings/roles');
116         $this->get('/settings/roles')->assertSee('Role successfully deleted');
117         $this->assertActivityExists(ActivityType::ROLE_DELETE);
118     }
119
120     public function test_admin_role_cannot_be_removed_if_user_last_admin()
121     {
122         /** @var Role $adminRole */
123         $adminRole = Role::query()->where('system_name', '=', 'admin')->first();
124         $adminUser = $this->getAdmin();
125         $adminRole->users()->where('id', '!=', $adminUser->id)->delete();
126         $this->assertEquals(1, $adminRole->users()->count());
127
128         $viewerRole = $this->getViewer()->roles()->first();
129
130         $editUrl = '/settings/users/' . $adminUser->id;
131         $resp = $this->actingAs($adminUser)->put($editUrl, [
132             'name'  => $adminUser->name,
133             'email' => $adminUser->email,
134             'roles' => [
135                 'viewer' => strval($viewerRole->id),
136             ],
137         ]);
138
139         $resp->assertRedirect($editUrl);
140
141         $resp = $this->get($editUrl);
142         $resp->assertSee('This user is the only user assigned to the administrator role');
143     }
144
145     public function test_migrate_users_on_delete_works()
146     {
147         /** @var Role $roleA */
148         $roleA = Role::query()->create(['display_name' => 'Delete Test A']);
149         /** @var Role $roleB */
150         $roleB = Role::query()->create(['display_name' => 'Delete Test B']);
151         $this->user->attachRole($roleB);
152
153         $this->assertCount(0, $roleA->users()->get());
154         $this->assertCount(1, $roleB->users()->get());
155
156         $deletePage = $this->asAdmin()->get("/settings/roles/delete/$roleB->id");
157         $deletePage->assertElementExists('select[name=migrate_role_id]');
158         $this->asAdmin()->delete("/settings/roles/delete/$roleB->id", [
159             'migrate_role_id' => $roleA->id,
160         ]);
161
162         $this->assertCount(1, $roleA->users()->get());
163         $this->assertEquals($this->user->id, $roleA->users()->first()->id);
164     }
165
166     public function test_manage_user_permission()
167     {
168         $this->actingAs($this->user)->get('/settings/users')->assertRedirect('/');
169         $this->giveUserPermissions($this->user, ['users-manage']);
170         $this->actingAs($this->user)->get('/settings/users')->assertOk();
171     }
172
173     public function test_manage_users_permission_shows_link_in_header_if_does_not_have_settings_manage_permision()
174     {
175         $usersLink = 'href="' . url('/settings/users') . '"';
176         $this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
177         $this->giveUserPermissions($this->user, ['users-manage']);
178         $this->actingAs($this->user)->get('/')->assertSee($usersLink, false);
179         $this->giveUserPermissions($this->user, ['settings-manage', 'users-manage']);
180         $this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
181     }
182
183     public function test_user_cannot_change_email_unless_they_have_manage_users_permission()
184     {
185         $userProfileUrl = '/settings/users/' . $this->user->id;
186         $originalEmail = $this->user->email;
187         $this->actingAs($this->user);
188
189         $this->get($userProfileUrl)
190             ->assertOk()
191             ->assertElementExists('input[name=email][disabled]');
192         $this->put($userProfileUrl, [
193             'name'  => 'my_new_name',
194             'email' => '[email protected]',
195         ]);
196         $this->assertDatabaseHas('users', [
197             'id'    => $this->user->id,
198             'email' => $originalEmail,
199             'name'  => 'my_new_name',
200         ]);
201
202         $this->giveUserPermissions($this->user, ['users-manage']);
203
204         $this->get($userProfileUrl)
205             ->assertOk()
206             ->assertElementNotExists('input[name=email][disabled]')
207             ->assertElementExists('input[name=email]');
208         $this->put($userProfileUrl, [
209             'name'  => 'my_new_name_2',
210             'email' => '[email protected]',
211         ]);
212
213         $this->assertDatabaseHas('users', [
214             'id'    => $this->user->id,
215             'email' => '[email protected]',
216             'name'  => 'my_new_name_2',
217         ]);
218     }
219
220     public function test_user_roles_manage_permission()
221     {
222         $this->actingAs($this->user)->get('/settings/roles')->assertRedirect('/');
223         $this->get('/settings/roles/1')->assertRedirect('/');
224         $this->giveUserPermissions($this->user, ['user-roles-manage']);
225         $this->actingAs($this->user)->get('/settings/roles')->assertOk();
226         $this->get('/settings/roles/1')
227             ->assertOk()
228             ->assertSee('Admin');
229     }
230
231     public function test_settings_manage_permission()
232     {
233         $this->actingAs($this->user)->get('/settings')->assertRedirect('/');
234         $this->giveUserPermissions($this->user, ['settings-manage']);
235         $this->get('/settings')->assertOk();
236
237         $resp = $this->post('/settings', []);
238         $resp->assertRedirect('/settings');
239         $resp = $this->get('/settings');
240         $resp->assertSee('Settings saved');
241     }
242
243     public function test_restrictions_manage_all_permission()
244     {
245         $page = Page::query()->get()->first();
246
247         $this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
248         $this->get($page->getUrl('/permissions'))->assertRedirect('/');
249
250         $this->giveUserPermissions($this->user, ['restrictions-manage-all']);
251
252         $this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
253
254         $this->get($page->getUrl('/permissions'))
255             ->assertOk()
256             ->assertSee('Page Permissions');
257     }
258
259     public function test_restrictions_manage_own_permission()
260     {
261         /** @var Page $otherUsersPage */
262         $otherUsersPage = Page::query()->first();
263         $content = $this->createEntityChainBelongingToUser($this->user);
264
265         // Set a different creator on the page we're checking to ensure
266         // that the owner fields are checked
267         $page = $content['page']; /** @var Page $page */
268         $page->created_by = $otherUsersPage->id;
269         $page->owned_by = $this->user->id;
270         $page->save();
271
272         // Check can't restrict other's content
273         $this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
274         $this->get($otherUsersPage->getUrl('/permissions'))->assertRedirect('/');
275
276         // Check can't restrict own content
277         $this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
278         $this->get($page->getUrl('/permissions'))->assertRedirect('/');
279
280         $this->giveUserPermissions($this->user, ['restrictions-manage-own']);
281
282         // Check can't restrict other's content
283         $this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
284         $this->get($otherUsersPage->getUrl('/permissions'))->assertRedirect();
285
286         // Check can restrict own content
287         $this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
288         $this->get($page->getUrl('/permissions'))->assertOk();
289     }
290
291     /**
292      * Check a standard entity access permission.
293      */
294     private function checkAccessPermission(string $permission, array $accessUrls = [], array $visibles = [])
295     {
296         foreach ($accessUrls as $url) {
297             $this->actingAs($this->user)->get($url)->assertRedirect('/');
298         }
299
300         foreach ($visibles as $url => $text) {
301             $this->actingAs($this->user)->get($url)
302                 ->assertElementNotContains('.action-buttons', $text);
303         }
304
305         $this->giveUserPermissions($this->user, [$permission]);
306
307         foreach ($accessUrls as $url) {
308             $this->actingAs($this->user)->get($url)->assertOk();
309         }
310         foreach ($visibles as $url => $text) {
311             $this->actingAs($this->user)->get($url)->assertSee($text);
312         }
313     }
314
315     public function test_bookshelves_create_all_permissions()
316     {
317         $this->checkAccessPermission('bookshelf-create-all', [
318             '/create-shelf',
319         ], [
320             '/shelves' => 'New Shelf',
321         ]);
322
323         $this->post('/shelves', [
324             'name'        => 'test shelf',
325             'description' => 'shelf desc',
326         ])->assertRedirect('/shelves/test-shelf');
327     }
328
329     public function test_bookshelves_edit_own_permission()
330     {
331         /** @var Bookshelf $otherShelf */
332         $otherShelf = Bookshelf::query()->first();
333         $ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
334         $ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
335         $this->regenEntityPermissions($ownShelf);
336
337         $this->checkAccessPermission('bookshelf-update-own', [
338             $ownShelf->getUrl('/edit'),
339         ], [
340             $ownShelf->getUrl() => 'Edit',
341         ]);
342
343         $this->get($otherShelf->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
344         $this->get($otherShelf->getUrl('/edit'))->assertRedirect('/');
345     }
346
347     public function test_bookshelves_edit_all_permission()
348     {
349         /** @var Bookshelf $otherShelf */
350         $otherShelf = Bookshelf::query()->first();
351         $this->checkAccessPermission('bookshelf-update-all', [
352             $otherShelf->getUrl('/edit'),
353         ], [
354             $otherShelf->getUrl() => 'Edit',
355         ]);
356     }
357
358     public function test_bookshelves_delete_own_permission()
359     {
360         $this->giveUserPermissions($this->user, ['bookshelf-update-all']);
361         /** @var Bookshelf $otherShelf */
362         $otherShelf = Bookshelf::query()->first();
363         $ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
364         $ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
365         $this->regenEntityPermissions($ownShelf);
366
367         $this->checkAccessPermission('bookshelf-delete-own', [
368             $ownShelf->getUrl('/delete'),
369         ], [
370             $ownShelf->getUrl() => 'Delete',
371         ]);
372
373         $this->get($otherShelf->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
374         $this->get($otherShelf->getUrl('/delete'))->assertRedirect('/');
375
376         $this->get($ownShelf->getUrl());
377         $this->delete($ownShelf->getUrl())->assertRedirect('/shelves');
378         $this->get('/shelves')->assertDontSee($ownShelf->name);
379     }
380
381     public function test_bookshelves_delete_all_permission()
382     {
383         $this->giveUserPermissions($this->user, ['bookshelf-update-all']);
384         /** @var Bookshelf $otherShelf */
385         $otherShelf = Bookshelf::query()->first();
386         $this->checkAccessPermission('bookshelf-delete-all', [
387             $otherShelf->getUrl('/delete'),
388         ], [
389             $otherShelf->getUrl() => 'Delete',
390         ]);
391
392         $this->delete($otherShelf->getUrl())->assertRedirect('/shelves');
393         $this->get('/shelves')->assertDontSee($otherShelf->name);
394     }
395
396     public function test_books_create_all_permissions()
397     {
398         $this->checkAccessPermission('book-create-all', [
399             '/create-book',
400         ], [
401             '/books' => 'Create New Book',
402         ]);
403
404         $this->post('/books', [
405             'name'        => 'test book',
406             'description' => 'book desc',
407         ])->assertRedirect('/books/test-book');
408     }
409
410     public function test_books_edit_own_permission()
411     {
412         /** @var Book $otherBook */
413         $otherBook = Book::query()->take(1)->get()->first();
414         $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
415         $this->checkAccessPermission('book-update-own', [
416             $ownBook->getUrl() . '/edit',
417         ], [
418             $ownBook->getUrl() => 'Edit',
419         ]);
420
421         $this->get($otherBook->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
422         $this->get($otherBook->getUrl('/edit'))->assertRedirect('/');
423     }
424
425     public function test_books_edit_all_permission()
426     {
427         /** @var Book $otherBook */
428         $otherBook = Book::query()->take(1)->get()->first();
429         $this->checkAccessPermission('book-update-all', [
430             $otherBook->getUrl() . '/edit',
431         ], [
432             $otherBook->getUrl() => 'Edit',
433         ]);
434     }
435
436     public function test_books_delete_own_permission()
437     {
438         $this->giveUserPermissions($this->user, ['book-update-all']);
439         /** @var Book $otherBook */
440         $otherBook = Book::query()->take(1)->get()->first();
441         $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
442         $this->checkAccessPermission('book-delete-own', [
443             $ownBook->getUrl() . '/delete',
444         ], [
445             $ownBook->getUrl() => 'Delete',
446         ]);
447
448         $this->get($otherBook->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
449         $this->get($otherBook->getUrl('/delete'))->assertRedirect('/');
450         $this->get($ownBook->getUrl());
451         $this->delete($ownBook->getUrl())->assertRedirect('/books');
452         $this->get('/books')->assertDontSee($ownBook->name);
453     }
454
455     public function test_books_delete_all_permission()
456     {
457         $this->giveUserPermissions($this->user, ['book-update-all']);
458         /** @var Book $otherBook */
459         $otherBook = Book::query()->take(1)->get()->first();
460         $this->checkAccessPermission('book-delete-all', [
461             $otherBook->getUrl() . '/delete',
462         ], [
463             $otherBook->getUrl() => 'Delete',
464         ]);
465
466         $this->get($otherBook->getUrl());
467         $this->delete($otherBook->getUrl())->assertRedirect('/books');
468         $this->get('/books')->assertDontSee($otherBook->name);
469     }
470
471     public function test_chapter_create_own_permissions()
472     {
473         /** @var Book $book */
474         $book = Book::query()->take(1)->get()->first();
475         $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
476         $this->checkAccessPermission('chapter-create-own', [
477             $ownBook->getUrl('/create-chapter'),
478         ], [
479             $ownBook->getUrl() => 'New Chapter',
480         ]);
481
482         $this->post($ownBook->getUrl('/create-chapter'), [
483             'name'        => 'test chapter',
484             'description' => 'chapter desc',
485         ])->assertRedirect($ownBook->getUrl('/chapter/test-chapter'));
486
487         $this->get($book->getUrl())->assertElementNotContains('.action-buttons', 'New Chapter');
488         $this->get($book->getUrl('/create-chapter'))->assertRedirect('/');
489     }
490
491     public function test_chapter_create_all_permissions()
492     {
493         /** @var Book $book */
494         $book = Book::query()->first();
495         $this->checkAccessPermission('chapter-create-all', [
496             $book->getUrl('/create-chapter'),
497         ], [
498             $book->getUrl() => 'New Chapter',
499         ]);
500
501         $this->post($book->getUrl('/create-chapter'), [
502             'name'        => 'test chapter',
503             'description' => 'chapter desc',
504         ])->assertRedirect($book->getUrl('/chapter/test-chapter'));
505     }
506
507     public function test_chapter_edit_own_permission()
508     {
509         /** @var Chapter $otherChapter */
510         $otherChapter = Chapter::query()->first();
511         $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
512         $this->checkAccessPermission('chapter-update-own', [
513             $ownChapter->getUrl() . '/edit',
514         ], [
515             $ownChapter->getUrl() => 'Edit',
516         ]);
517
518         $this->get($otherChapter->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
519         $this->get($otherChapter->getUrl('/edit'))->assertRedirect('/');
520     }
521
522     public function test_chapter_edit_all_permission()
523     {
524         /** @var Chapter $otherChapter */
525         $otherChapter = Chapter::query()->take(1)->get()->first();
526         $this->checkAccessPermission('chapter-update-all', [
527             $otherChapter->getUrl() . '/edit',
528         ], [
529             $otherChapter->getUrl() => 'Edit',
530         ]);
531     }
532
533     public function test_chapter_delete_own_permission()
534     {
535         $this->giveUserPermissions($this->user, ['chapter-update-all']);
536         /** @var Chapter $otherChapter */
537         $otherChapter = Chapter::query()->first();
538         $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
539         $this->checkAccessPermission('chapter-delete-own', [
540             $ownChapter->getUrl() . '/delete',
541         ], [
542             $ownChapter->getUrl() => 'Delete',
543         ]);
544
545         $bookUrl = $ownChapter->book->getUrl();
546         $this->get($otherChapter->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
547         $this->get($otherChapter->getUrl('/delete'))->assertRedirect('/');
548         $this->get($ownChapter->getUrl());
549         $this->delete($ownChapter->getUrl())->assertRedirect($bookUrl);
550         $this->get($bookUrl)->assertElementNotContains('.book-content', $ownChapter->name);
551     }
552
553     public function test_chapter_delete_all_permission()
554     {
555         $this->giveUserPermissions($this->user, ['chapter-update-all']);
556         /** @var Chapter $otherChapter */
557         $otherChapter = Chapter::query()->first();
558         $this->checkAccessPermission('chapter-delete-all', [
559             $otherChapter->getUrl() . '/delete',
560         ], [
561             $otherChapter->getUrl() => 'Delete',
562         ]);
563
564         $bookUrl = $otherChapter->book->getUrl();
565         $this->get($otherChapter->getUrl());
566         $this->delete($otherChapter->getUrl())->assertRedirect($bookUrl);
567         $this->get($bookUrl)->assertElementNotContains('.book-content', $otherChapter->name);
568     }
569
570     public function test_page_create_own_permissions()
571     {
572         /** @var Book $book */
573         $book = Book::query()->first();
574         /** @var Chapter $chapter */
575         $chapter = Chapter::query()->first();
576
577         $entities = $this->createEntityChainBelongingToUser($this->user);
578         $ownBook = $entities['book'];
579         $ownChapter = $entities['chapter'];
580
581         $createUrl = $ownBook->getUrl('/create-page');
582         $createUrlChapter = $ownChapter->getUrl('/create-page');
583         $accessUrls = [$createUrl, $createUrlChapter];
584
585         foreach ($accessUrls as $url) {
586             $this->actingAs($this->user)->get($url)->assertRedirect('/');
587         }
588
589         $this->checkAccessPermission('page-create-own', [], [
590             $ownBook->getUrl()    => 'New Page',
591             $ownChapter->getUrl() => 'New Page',
592         ]);
593
594         $this->giveUserPermissions($this->user, ['page-create-own']);
595
596         foreach ($accessUrls as $index => $url) {
597             $resp = $this->actingAs($this->user)->get($url);
598             $expectedUrl = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
599             $resp->assertRedirect($expectedUrl);
600         }
601
602         $this->get($createUrl);
603         /** @var Page $draft */
604         $draft = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
605         $this->post($draft->getUrl(), [
606             'name' => 'test page',
607             'html' => 'page desc',
608         ])->assertRedirect($ownBook->getUrl('/page/test-page'));
609
610         $this->get($book->getUrl())->assertElementNotContains('.action-buttons', 'New Page');
611         $this->get($book->getUrl('/create-page'))->assertRedirect('/');
612
613         $this->get($chapter->getUrl())->assertElementNotContains('.action-buttons', 'New Page');
614         $this->get($chapter->getUrl('/create-page'))->assertRedirect('/');
615     }
616
617     public function test_page_create_all_permissions()
618     {
619         /** @var Book $book */
620         $book = Book::query()->first();
621         /** @var Chapter $chapter */
622         $chapter = Chapter::query()->first();
623         $createUrl = $book->getUrl('/create-page');
624
625         $createUrlChapter = $chapter->getUrl('/create-page');
626         $accessUrls = [$createUrl, $createUrlChapter];
627
628         foreach ($accessUrls as $url) {
629             $this->actingAs($this->user)->get($url)->assertRedirect('/');
630         }
631
632         $this->checkAccessPermission('page-create-all', [], [
633             $book->getUrl()    => 'New Page',
634             $chapter->getUrl() => 'New Page',
635         ]);
636
637         $this->giveUserPermissions($this->user, ['page-create-all']);
638
639         foreach ($accessUrls as $index => $url) {
640             $resp = $this->actingAs($this->user)->get($url);
641             $expectedUrl = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
642             $resp->assertRedirect($expectedUrl);
643         }
644
645         $this->get($createUrl);
646         /** @var Page $draft */
647         $draft = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
648         $this->post($draft->getUrl(), [
649             'name' => 'test page',
650             'html' => 'page desc',
651         ])->assertRedirect($book->getUrl('/page/test-page'));
652
653         $this->get($chapter->getUrl('/create-page'));
654         /** @var Page $draft */
655         $draft = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
656         $this->post($draft->getUrl(), [
657             'name' => 'new test page',
658             'html' => 'page desc',
659         ])->assertRedirect($book->getUrl('/page/new-test-page'));
660     }
661
662     public function test_page_edit_own_permission()
663     {
664         /** @var Page $otherPage */
665         $otherPage = Page::query()->first();
666         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
667         $this->checkAccessPermission('page-update-own', [
668             $ownPage->getUrl() . '/edit',
669         ], [
670             $ownPage->getUrl() => 'Edit',
671         ]);
672
673         $this->get($otherPage->getUrl())->assertElementNotContains('.action-buttons', 'Edit');
674         $this->get($otherPage->getUrl() . '/edit')->assertRedirect('/');
675     }
676
677     public function test_page_edit_all_permission()
678     {
679         /** @var Page $otherPage */
680         $otherPage = Page::query()->first();
681         $this->checkAccessPermission('page-update-all', [
682             $otherPage->getUrl('/edit'),
683         ], [
684             $otherPage->getUrl() => 'Edit',
685         ]);
686     }
687
688     public function test_page_delete_own_permission()
689     {
690         $this->giveUserPermissions($this->user, ['page-update-all']);
691         /** @var Page $otherPage */
692         $otherPage = Page::query()->first();
693         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
694         $this->checkAccessPermission('page-delete-own', [
695             $ownPage->getUrl() . '/delete',
696         ], [
697             $ownPage->getUrl() => 'Delete',
698         ]);
699
700         $parent = $ownPage->chapter ?? $ownPage->book;
701         $this->get($otherPage->getUrl())->assertElementNotContains('.action-buttons', 'Delete');
702         $this->get($otherPage->getUrl('/delete'))->assertRedirect('/');
703         $this->get($ownPage->getUrl());
704         $this->delete($ownPage->getUrl())->assertRedirect($parent->getUrl());
705         $this->get($parent->getUrl())->assertElementNotContains('.book-content', $ownPage->name);
706     }
707
708     public function test_page_delete_all_permission()
709     {
710         $this->giveUserPermissions($this->user, ['page-update-all']);
711         /** @var Page $otherPage */
712         $otherPage = Page::query()->first();
713
714         $this->checkAccessPermission('page-delete-all', [
715             $otherPage->getUrl() . '/delete',
716         ], [
717             $otherPage->getUrl() => 'Delete',
718         ]);
719
720         /** @var Entity $parent */
721         $parent = $otherPage->chapter ?? $otherPage->book;
722         $this->get($otherPage->getUrl());
723
724         $this->delete($otherPage->getUrl())->assertRedirect($parent->getUrl());
725         $this->get($parent->getUrl())->assertDontSee($otherPage->name);
726     }
727
728     public function test_public_role_visible_in_user_edit_screen()
729     {
730         /** @var User $user */
731         $user = User::query()->first();
732         $adminRole = Role::getSystemRole('admin');
733         $publicRole = Role::getSystemRole('public');
734         $this->asAdmin()->get('/settings/users/' . $user->id)
735             ->assertElementExists('[name="roles[' . $adminRole->id . ']"]')
736             ->assertElementExists('[name="roles[' . $publicRole->id . ']"]');
737     }
738
739     public function test_public_role_visible_in_role_listing()
740     {
741         $this->asAdmin()->get('/settings/roles')
742             ->assertSee('Admin')
743             ->assertSee('Public');
744     }
745
746     public function test_public_role_visible_in_default_role_setting()
747     {
748         $this->asAdmin()->get('/settings')
749             ->assertElementExists('[data-system-role-name="admin"]')
750             ->assertElementExists('[data-system-role-name="public"]');
751     }
752
753     public function test_public_role_not_deletable()
754     {
755         /** @var Role $publicRole */
756         $publicRole = Role::getSystemRole('public');
757         $resp = $this->asAdmin()->delete('/settings/roles/delete/' . $publicRole->id);
758         $resp->assertRedirect('/');
759
760         $this->get('/settings/roles/delete/' . $publicRole->id);
761         $resp = $this->delete('/settings/roles/delete/' . $publicRole->id);
762         $resp->assertRedirect('/settings/roles/delete/' . $publicRole->id);
763         $resp = $this->get('/settings/roles/delete/' . $publicRole->id);
764         $resp->assertSee('This role is a system role and cannot be deleted');
765     }
766
767     public function test_image_delete_own_permission()
768     {
769         $this->giveUserPermissions($this->user, ['image-update-all']);
770         /** @var Page $page */
771         $page = Page::query()->first();
772         $image = Image::factory()->create([
773             'uploaded_to' => $page->id,
774             'created_by'  => $this->user->id,
775             'updated_by'  => $this->user->id,
776         ]);
777
778         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
779
780         $this->giveUserPermissions($this->user, ['image-delete-own']);
781
782         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
783         $this->assertDatabaseMissing('images', ['id' => $image->id]);
784     }
785
786     public function test_image_delete_all_permission()
787     {
788         $this->giveUserPermissions($this->user, ['image-update-all']);
789         $admin = $this->getAdmin();
790         /** @var Page $page */
791         $page = Page::query()->first();
792         $image = Image::factory()->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
793
794         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
795
796         $this->giveUserPermissions($this->user, ['image-delete-own']);
797
798         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
799
800         $this->giveUserPermissions($this->user, ['image-delete-all']);
801
802         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
803         $this->assertDatabaseMissing('images', ['id' => $image->id]);
804     }
805
806     public function test_role_permission_removal()
807     {
808         // To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
809         /** @var Page $page */
810         $page = Page::query()->first();
811         $viewerRole = Role::getRole('viewer');
812         $viewer = $this->getViewer();
813         $this->actingAs($viewer)->get($page->getUrl())->assertOk();
814
815         $this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
816             'display_name' => $viewerRole->display_name,
817             'description'  => $viewerRole->description,
818             'permission'   => [],
819         ])->assertStatus(302);
820
821         $this->actingAs($viewer)->get($page->getUrl())->assertStatus(404);
822     }
823
824     public function test_empty_state_actions_not_visible_without_permission()
825     {
826         $admin = $this->getAdmin();
827         // Book links
828         $book = Book::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
829         $this->regenEntityPermissions($book);
830         $this->actingAs($this->getViewer())->get($book->getUrl())
831             ->assertDontSee('Create a new page')
832             ->assertDontSee('Add a chapter');
833
834         // Chapter links
835         $chapter = Chapter::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
836         $this->regenEntityPermissions($chapter);
837         $this->actingAs($this->getViewer())->get($chapter->getUrl())
838             ->assertDontSee('Create a new page')
839             ->assertDontSee('Sort the current book');
840     }
841
842     public function test_comment_create_permission()
843     {
844         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
845
846         $this->actingAs($this->user)
847             ->addComment($ownPage)
848             ->assertStatus(403);
849
850         $this->giveUserPermissions($this->user, ['comment-create-all']);
851
852         $this->actingAs($this->user)
853             ->addComment($ownPage)
854             ->assertOk();
855     }
856
857     public function test_comment_update_own_permission()
858     {
859         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
860         $this->giveUserPermissions($this->user, ['comment-create-all']);
861         $this->actingAs($this->user)->addComment($ownPage);
862         /** @var Comment $comment */
863         $comment = $ownPage->comments()->latest()->first();
864
865         // no comment-update-own
866         $this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
867
868         $this->giveUserPermissions($this->user, ['comment-update-own']);
869
870         // now has comment-update-own
871         $this->actingAs($this->user)->updateComment($comment)->assertOk();
872     }
873
874     public function test_comment_update_all_permission()
875     {
876         /** @var Page $ownPage */
877         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
878         $this->asAdmin()->addComment($ownPage);
879         /** @var Comment $comment */
880         $comment = $ownPage->comments()->latest()->first();
881
882         // no comment-update-all
883         $this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
884
885         $this->giveUserPermissions($this->user, ['comment-update-all']);
886
887         // now has comment-update-all
888         $this->actingAs($this->user)->updateComment($comment)->assertOk();
889     }
890
891     public function test_comment_delete_own_permission()
892     {
893         /** @var Page $ownPage */
894         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
895         $this->giveUserPermissions($this->user, ['comment-create-all']);
896         $this->actingAs($this->user)->addComment($ownPage);
897
898         /** @var Comment $comment */
899         $comment = $ownPage->comments()->latest()->first();
900
901         // no comment-delete-own
902         $this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
903
904         $this->giveUserPermissions($this->user, ['comment-delete-own']);
905
906         // now has comment-update-own
907         $this->actingAs($this->user)->deleteComment($comment)->assertOk();
908     }
909
910     public function test_comment_delete_all_permission()
911     {
912         /** @var Page $ownPage */
913         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
914         $this->asAdmin()->addComment($ownPage);
915         /** @var Comment $comment */
916         $comment = $ownPage->comments()->latest()->first();
917
918         // no comment-delete-all
919         $this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
920
921         $this->giveUserPermissions($this->user, ['comment-delete-all']);
922
923         // now has comment-delete-all
924         $this->actingAs($this->user)->deleteComment($comment)->assertOk();
925     }
926
927     private function addComment(Page $page): TestResponse
928     {
929         $comment = Comment::factory()->make();
930
931         return $this->postJson("/comment/$page->id", $comment->only('text', 'html'));
932     }
933
934     private function updateComment(Comment $comment): TestResponse
935     {
936         $commentData = Comment::factory()->make();
937
938         return $this->putJson("/comment/{$comment->id}", $commentData->only('text', 'html'));
939     }
940
941     private function deleteComment(Comment $comment): TestResponse
942     {
943         return $this->json('DELETE', '/comment/' . $comment->id);
944     }
945 }